Atualização: em 2018, o Unity está lançando um C # Job System como uma maneira de descarregar o trabalho e fazer uso de vários núcleos da CPU.
A resposta abaixo é anterior a este sistema. Ainda funcionará, mas pode haver melhores opções disponíveis no Unity moderno, dependendo de suas necessidades. Em particular, o sistema de tarefas parece resolver algumas das limitações sobre o que os threads criados manualmente podem acessar com segurança, descritos abaixo. Desenvolvedores que experimentam o relatório de visualização realizando raycasts e construindo malhas em paralelo , por exemplo.
Convido usuários com experiência no uso desse sistema de tarefas a adicionar suas próprias respostas, refletindo o estado atual do mecanismo.
Eu usei threading para tarefas pesadas no Unity no passado (geralmente processamento de imagem e geometria), e não é drasticamente diferente do que usar threads em outros aplicativos C #, com duas ressalvas:
Como o Unity usa um subconjunto um pouco mais antigo do .NET, existem alguns recursos e bibliotecas de encadeamento mais recentes que não podemos usar imediatamente, mas o básico está lá.
Como Almo observa em um comentário acima, muitos tipos de Unity não são seguros para threads e lançarão exceções se você tentar construí-las, usá-las ou mesmo compará-las na thread principal. Coisas para manter em mente:
Um caso comum é verificar se uma referência GameObject ou Monobehaviour é nula antes de tentar acessar seus membros. myUnityObject == null
chama um operador sobrecarregado para qualquer coisa descendente de UnityEngine.Object, mas System.Object.ReferenceEquals()
trabalha em torno disso até certo ponto - lembre-se de que um GameObject de Destroy () ed se compara como igual a nulo usando a sobrecarga, mas ainda não é ReferenceEqual como nulo.
A leitura de parâmetros dos tipos Unity geralmente é segura em outro segmento (pois não gera uma exceção imediatamente, desde que você tenha cuidado em verificar nulos como acima), mas observe o aviso de Philipp aqui de que o segmento principal pode estar modificando o estado enquanto você está lendo. Você precisará ser disciplinado sobre quem tem permissão para modificar o quê e quando, a fim de evitar a leitura de um estado inconsistente, o que pode levar a erros que podem ser incrivelmente difíceis de rastrear, porque dependem de tempos de menos de um milissegundo entre os threads que podemos reproduza à vontade.
Membros estáticos Random e Time não estão disponíveis. Crie uma instância de System.Random por thread, se você precisar de aleatoriedade, e System.Diagnostics.Stopwatch, se precisar de informações de tempo.
As funções Mathf, Vector, Matrix, Quaternion e Color structs funcionam bem em threads, para que você possa executar a maioria dos seus cálculos separadamente
Criar GameObjects, anexar Monobehaviours ou criar / atualizar Texturas, Malhas, Materiais, etc., tudo precisa acontecer no thread principal. No passado, quando eu precisava trabalhar com isso, configurei uma fila produtor-consumidor, na qual meu thread de trabalho prepara os dados brutos (como uma grande variedade de vetores / cores para aplicar a uma malha ou textura), e uma Atualização ou Coroutine no thread principal pesquisa dados e os aplica.
Com essas anotações fora do caminho, eis um padrão que frequentemente uso para trabalhos encadeados. Não garanto que seja um estilo de práticas recomendadas, mas ele faz o trabalho. (Comentários ou edições para melhorar são bem-vindos - eu sei que o encadeamento é um tópico muito profundo, do qual eu só sei o básico)
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.
}
}
Se você não precisa estritamente dividir o trabalho entre threads para obter velocidade, e está apenas procurando uma maneira de torná-lo sem bloqueio, para que o resto do seu jogo continue correndo, uma solução mais leve no Unity é a Coroutines . Essas são funções que podem fazer algum trabalho e, em seguida, devolvem o controle ao mecanismo para continuar o que está fazendo e retomar sem interrupções posteriormente.
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...
}
}
}
Isso não precisa de considerações especiais de limpeza, pois o mecanismo (até onde eu sei) se livra das corotinas dos objetos destruídos para você.
Todo o estado local do método é preservado quando ele produz e continua, portanto, para muitos propósitos, é como se estivesse sendo executado ininterruptamente em outro encadeamento (mas você tem todas as conveniências de executar no encadeamento principal). Você só precisa garantir que cada iteração seja curta o suficiente para que não diminua seu encadeamento principal de maneira irracional.
Ao garantir que operações importantes não sejam separadas por um rendimento, você pode obter a consistência do comportamento de thread único - sabendo que nenhum outro script ou sistema no thread principal pode modificar os dados nos quais você está trabalhando.
A linha de retorno de rendimento oferece algumas opções. Você pode...
... dependendo de quando você deseja que a corotina faça a próxima curva.
Ou yield break;
para interromper a corotina antes que ela chegue ao fim, da maneira que você pode usar return;
para sair mais cedo de um método convencional.