Actualización: en 2018, Unity está implementando un sistema de trabajo C # como una forma de descargar el trabajo y utilizar múltiples núcleos de CPU.
La respuesta a continuación es anterior a este sistema. Seguirá funcionando, pero puede haber mejores opciones disponibles en Unity moderno, según sus necesidades. En particular, el sistema de trabajo parece resolver algunas de las limitaciones sobre qué hilos creados manualmente pueden acceder de forma segura, como se describe a continuación. Los desarrolladores que experimentan con el informe de vista previa realizan emisiones de rayos y construyen mallas en paralelo , por ejemplo.
Invitaría a los usuarios con experiencia en el uso de este sistema de trabajo a agregar sus propias respuestas que reflejen el estado actual del motor.
He usado subprocesos para tareas pesadas en Unity en el pasado (generalmente procesamiento de imágenes y geometría), y no es drásticamente diferente que usar subprocesos en otras aplicaciones de C #, con dos advertencias:
Debido a que Unity usa un subconjunto algo más antiguo de .NET, hay algunas características y bibliotecas de subprocesos más nuevas que no podemos usar de fábrica, pero los conceptos básicos están ahí.
Como Almo señala en un comentario anterior, muchos tipos de Unity no son seguros para subprocesos y arrojarán excepciones si intenta construirlos, usarlos o incluso compararlos desde el hilo principal. Cosas a tener en cuenta:
Un caso común es verificar si una referencia de GameObject o Monobehaviour es nula antes de intentar acceder a sus miembros. myUnityObject == null
llama a un operador sobrecargado para cualquier cosa que descienda de UnityEngine.Object, pero System.Object.ReferenceEquals()
trabaja en torno a esto hasta cierto punto; solo recuerde que un GameObject Destroy () ed se compara como igual a nulo usando la sobrecarga, pero aún no es ReferenceEqual a nulo.
La lectura de los parámetros de los tipos de Unity generalmente es segura en otro subproceso (en el sentido de que no arrojará una excepción de inmediato siempre y cuando tenga cuidado de verificar los nulos como se indicó anteriormente), pero tenga en cuenta la advertencia de Philipp aquí que el subproceso principal podría estar modificando el estado mientras lo estás leyendo Tendrá que ser disciplinado sobre quién puede modificar qué y cuándo para evitar leer algún estado inconsistente, lo que puede conducir a errores que pueden ser muy difíciles de rastrear porque dependen de tiempos de sub-milisegundos entre hilos que podemos No reproducir a voluntad.
Los miembros estáticos aleatorios y de tiempo no están disponibles. Cree una instancia de System.Random por hilo si necesita aleatoriedad, y System.Diagnostics.Stopwatch si necesita información de sincronización.
Las estructuras Mathf, Vector, Matrix, Quaternion y Color funcionan bien en todos los subprocesos, por lo que puede hacer la mayoría de sus cálculos por separado
Crear GameObjects, adjuntar monobehaviours o crear / actualizar texturas, mallas, materiales, etc., todo debe suceder en el hilo principal. En el pasado, cuando necesitaba trabajar con estos, establecí una cola de productor-consumidor, donde mi hilo de trabajo prepara los datos en bruto (como una gran variedad de vectores / colores para aplicar a una malla o textura), y una actualización o rutina en el hilo principal sondea los datos y los aplica.
Con esas notas fuera del camino, aquí hay un patrón que uso a menudo para el trabajo enhebrado. No garantizo que sea un estilo de mejores prácticas, pero hace el trabajo. (Los comentarios o ediciones para mejorar son bienvenidos; sé que el enhebrado es un tema muy profundo del cual solo conozco los conceptos básicos)
using UnityEngine;
using System.Threading;
public class MyThreadedBehaviour : MonoBehaviour
{
bool _threadRunning;
Thread _thread;
void Start()
{
// Begin our heavy work on a new thread.
_thread = new Thread(ThreadedWork);
_thread.Start();
}
void ThreadedWork()
{
_threadRunning = true;
bool workDone = false;
// This pattern lets us interrupt the work at a safe point if neeeded.
while(_threadRunning && !workDone)
{
// Do Work...
}
_threadRunning = false;
}
void OnDisable()
{
// If the thread is still running, we should shut it down,
// otherwise it can prevent the game from exiting correctly.
if(_threadRunning)
{
// This forces the while loop in the ThreadedWork function to abort.
_threadRunning = false;
// This waits until the thread exits,
// ensuring any cleanup we do after this is safe.
_thread.Join();
}
// Thread is guaranteed no longer running. Do other cleanup tasks.
}
}
Si no necesitas dividir estrictamente el trabajo entre hilos para obtener velocidad, y solo estás buscando una manera de que no se bloquee para que el resto del juego siga funcionando, una solución más ligera en Unity es Coroutines . Estas son funciones que pueden hacer algo de trabajo y luego devolver el control al motor para continuar lo que está haciendo y reanudar sin problemas en un momento posterior.
using UnityEngine;
using System.Collections;
public class MyYieldingBehaviour : MonoBehaviour
{
void Start()
{
// Begin our heavy work in a coroutine.
StartCoroutine(YieldingWork());
}
IEnumerator YieldingWork()
{
bool workDone = false;
while(!workDone)
{
// Let the engine run for a frame.
yield return null;
// Do Work...
}
}
}
Esto no necesita ninguna consideración especial de limpieza, ya que el motor (por lo que puedo decir) elimina las rutinas de los objetos destruidos para usted.
Todo el estado local del método se conserva cuando cede y se reanuda, por lo que para muchos propósitos es como si se estuviera ejecutando sin interrupciones en otro hilo (pero tiene todas las comodidades de ejecutarse en el hilo principal). Solo necesita asegurarse de que cada iteración sea lo suficientemente corta como para que no ralentice su hilo principal sin razón.
Al garantizar que las operaciones importantes no estén separadas por un rendimiento, puede obtener la consistencia del comportamiento de un solo subproceso, sabiendo que ningún otro script o sistema en el hilo principal puede modificar los datos en los que está trabajando.
La línea de retorno de rendimiento le ofrece algunas opciones. Usted puede...
... dependiendo de cuándo quieras que la corutina tome su próximo turno.
O yield break;
para detener la corutina antes de que llegue al final, de la forma en que podría utilizarla return;
antes de un método convencional.