Comunidad orientada al desarrollo de videojuegos

UNITY3D–Tareas asíncronas (part. II)

En un post anterior vimos cómo lanzar un hilo para intentar evitar el bloqueo de la interfaz. En este post vamos a ver una de la formas que hay para comunicar al hilo de la interfaz que el hilo que lanzó ha terminado. Lo vamos a hacer usando eventos.

En esta ocasión vamos a lanzar el hilo de una forma diferente, que es utilizando la clase ThreadPool. La principal diferencia radica en que ThreadPool, como su nombre indica, es un pool con hilos ya creados, así que al utilizarlo no tenemos que crear un nuevo hilo, operación que es bastante costosa.

Al igual que en el otro post, tenemos un cubo que gira en la pantalla y dos botones, uno que hace una operación que tarda 20 segundos de forma asíncrona y otro que hace la misma operación de forma síncrona. La diferencia va a ser que cuando esta función se le llama de forma síncrona todo se bloquea, mientras que cuando se hace de forma asíncrona la tarea se ejecuta en segundo plano y nuestro hilo de interfaz (en el que se ejecuta Unity3D) no se bloquea y el cubo sigue girando.

¿Qué cosas se pueden hacer en segundo plano?

En segundo plano podemos hacer muchas cosas, como escribir información en disco, hacer una petición a un servicio web, obtener información del estado de una partida online, un chat para hablar en una partida online, etc. El límite está en tu imaginación Sonrisa

Pintando la UI

Primero vamos a pintar los botones que lanzarán nuestras funciones. Para ello creamos un EmptyGameObject, y le añadimos el siguiente script:

   1:  using UnityEngine;
   2:  using System.Collections;
   3:   
   4:  public class UI : MonoBehaviour
   5:  {
   6:          //enumeracion para definir los estados posibles
   7:          enum UIState
   8:          {
   9:                  ShowMenu,
  10:                  InProgress,
  11:                  Finished
  12:          }
  13:   
  14:          float _screenWidth;
  15:          float _screenHeight;
  16:          UIState _state;
  17:          Operations _operations;
  18:          float _buttonHeight = 20f;
  19:          float _buttonWidth = 0f;
  20:          float _topMargin = 10f;
  21:          float _bottomMargin = 5f;
  22:          float _leftMargin = 5f;
  23:          float _rightMargin = 5f;
  24:  
  25:          // Use this for initialization
  26:          void Start ()
  27:          {
  28:                  _screenWidth = Screen.width;
  29:                  _screenHeight = Screen.height;
  30:  
  31:                  _buttonWidth = _screenWidth / 2;
  32:  
  33:                  _state = UIState.ShowMenu;
  34:                  _operations = new Operations ();
  35:                  _operations.TaskCompleted += ((sender, e) => this._state = UIState.Finished);
  36:          }
  37:      
  38:          void OnGUI ()
  39:          {
  40:                  //segun el estado en el que estemos, pintamos una cosa u otra
  41:                  switch (_state) {
  42:                  case UIState.ShowMenu:
  43:                          PaintMenu ();
  44:                          break;
  45:                  case UIState.InProgress:
  46:                          PaintInProgress ();
  47:                          break;
  48:                  case UIState.Finished:
  49:                          PaintCompleted ();
  50:                          break;
  51:                  default:
  52:                          break;
  53:                  }
  54:          }
  55:   
  56:          void PaintMenu ()
  57:          {
  58:                  Rect buttonRect = new Rect (_leftMargin, _topMargin, _buttonWidth - _leftMargin - _rightMargin, _buttonHeight);
  59:   
  60:                  if (GUI.Button (buttonRect, "Funcion asincrona")) {
  61:                          _state = UIState.InProgress;
  62:                          _operations.AsyncTask ();
  63:                  }
  64:  
  65:                  buttonRect = new Rect (_leftMargin + _buttonWidth, _topMargin, _buttonWidth - _leftMargin - _rightMargin, _buttonHeight);
  66:                  if (GUI.Button (buttonRect, "Funcion sincrona")) {
  67:                          _state = UIState.InProgress;
  68:                          _operations.SyncTask ();
  69:                          _state = UIState.Finished;
  70:                  }
  71:          }
  72:  
  73:          void PaintInProgress ()
  74:          {
  75:                  Rect buttonRect = new Rect (_leftMargin, _topMargin, _buttonWidth - _leftMargin - _rightMargin, _buttonHeight);
  76:  
  77:                  GUI.Label (buttonRect, "En ejecucion");
  78:  
  79:          }
  80:  
  81:          void PaintCompleted ()
  82:          {
  83:                  PaintMenu ();
  84:  
  85:                  Rect buttonRect = new Rect (_leftMargin, _topMargin + _buttonHeight, _buttonWidth - _leftMargin - _rightMargin, _buttonHeight);
  86:  
  87:                  GUI.Label (buttonRect, "Ejecucion finalizada");
  88:  
  89:          }
  90:  
  91:  }

Aquí podemos ver que tenemos una variable para mantener el estado en el que estamos, si estamos en un estado inicial, ejecutando algo o con la ejecución ya finalizada. Tenemos un objeto que es el que hace las llamadas a las funcione que tardan tanto. Y finalmente el código que pinta los botones y responde a sus clicks. La forma en la que desde este behavior nos damos cuenta que el hilo ha terminado es gracias al evento TaskCompleted. Este evento se dispara cuando la función que tarda más tiempo termina, y nos subscribimos a él para notificar en la interfaz que el proceso ha terminado.

Las tareas (a)síncronas

He encapsulado en la clase Operations las llamadas síncrona y asíncrona a la función que bloquea el hilo durante 20 segundos. Lo más destacable de esta clase es el evento TaskCompleted que es el que notificará al otro hilo que la ejecución ha finalizado.

   1:  using System;
   2:  using UnityEngine;
   3:  using System.Collections;
   4:  using System.Threading;
   5:   
   6:  public class Operations
   7:  {
   8:  
   9:      #region events
  10:          internal event EventHandler TaskCompleted;
  11:      #endregion
  12:  
  13:      #region functions
  14:          internal void AsyncTask ()
  15:          {
  16:  
  17:                  ThreadPool.QueueUserWorkItem ((_) => {
  18:                          SyncTask ();
  19:                          if (TaskCompleted != null)
  20:                                  TaskCompleted (this, null);
  21:                  });
  22:  
  23:          }
  24:  
  25:          internal void SyncTask ()
  26:          {
  27:                  //duermo el hilo durante 20 segundos
  28:                  Thread.Sleep (TimeSpan.FromSeconds (20));
  29:          }
  30:      #endregion
  31:      
  32:  }

Esta clase en realidad no hace nada, pero comenté anteriormente, el límite que tenemos para hacer de forma asíncrona en nuestros juegos lo pone nuestra imaginación.

Con estas dos clases tenemos un ejemplo de cómo notificar Unity del fin de ejecución de una función en segundo plano.

En el próximo post veremos cómo comunicar el hilo con Unity durante la ejecución del mismo.

Aquí tenéis el código del proyecto de Unity3d.

Saludos y happy coding!!!!!!

, ,

Leave a Reply