Misturando threads e corotinas no Unity3D Mobile

8

Eu tinha uma rotina no Unity3D que baixava um zip de um servidor, extraia-o no caminho de dados persistentes e carregava seu conteúdo na memória. O fluxo ficou mais ou menos assim:

IEnumerator LongCoroutine()
{
    yield return StartCoroutine(DownloadZip());
    ExtractZip();
    yield return StartCoroutine(LoadZipContent());
}

Mas o ExtractZip()método (que usa a biblioteca DotNetZip), é síncrono, leva muito tempo e não me deixa lugar para ceder durante o processo.

Isso resultava na morte do aplicativo (em dispositivos móveis) sempre que eu tentava extrair um zip grande, o que eu assumo ser devido ao fato de o thread principal ficar sem resposta por muito tempo.

Isso é algo que o sistema operacional móvel sabe fazer (interrompa o aplicativo se um quadro demorar muito)?

Portanto, presumi que extrair o zip em um thread separado poderia resolver o problema e parece ter funcionado. Eu criei esta classe:

public class ThreadedAction
{
    public ThreadedAction(Action action)
    {
        var thread = new Thread(() => {
            if(action != null)
                action();
            _isDone = true;
        });
        thread.Start();
    }

    public IEnumerator WaitForComplete()
    {
        while (!_isDone)
            yield return null;
    }

    private bool _isDone = false;
}

E eu uso assim:

IEnumerator LongCoroutine()
{
    yield return StartCoroutine(DownloadZip());
    var extractAction = new ThreadedAction(ExtractZip);
    yield return StartCoroutine(extractAction.WaitForComplete());
    yield return StartCoroutine(LoadZipContent());
}

Mas ainda não tenho certeza se essa é a melhor maneira de implementá-lo ou se preciso bloquear _isDone(não estou acostumado a multithreading).

Alguma coisa pode dar errado com isso / estou perdendo alguma coisa?

David Gouveia
fonte

Respostas:

4

Esta é uma solução realmente elegante para agrupar tarefas multithread em uma rotina bem feita :)

A combinação de corotinas e threads é perfeitamente segura, desde que você bloqueie corretamente o acesso aos recursos compartilhados entre o thread principal (no qual a corotina executa) e os threads de trabalho criados. Você não precisa bloquear _isDone de forma alguma, pois ele só é gravado pelo thread de trabalho e não há estado intermediário que possa causar o mau comportamento do thread principal.

Onde você precisa procurar por possíveis problemas é se algum recurso é gravado pelo ExtractZip e se

  1. gravados simultaneamente por uma função que está sendo chamada do seu thread principal ou
  2. sendo lido por uma função no encadeamento principal e espera-se que esteja em um estado seguro antes que o ExtractZip seja concluído.

Nesse caso em particular, minha preocupação seria que, se você não verificar se não está tentando baixar o mesmo arquivo no mesmo local duas vezes, poderá ter dois threads executando o ExtractZip simultaneamente que interferem um no outro.

FlintZA
fonte
1

Existe uma solução gratuita para isso na Asset Store:

Tópico Ninja

Com ele, você pode alternar facilmente entre o thread principal e o thread de segundo plano, o que gosta:

void Awake() {
    this.StartCoroutineAsync(AsyncCouroutine());
}

IEnumerator AsyncCoroutine() {
    // won't block
    Thread.Sleep(10000);

    yield return Ninja.JumpToUnity;

    // we're now on Unity's main thread
    var time = Time.time;

    yield return Ninja.JumpBack;

    // now on background thread again
    // ...
Ran QUAN
fonte
-1

Bem, também não sou um grande especialista, mas acho que no seu segundo exemplo, a função WaitForComplete () ainda bloqueará o segmento de chamada até que o segmento ThreadedAction seja concluído. O que você pode fazer é passar uma função de retorno de chamada para o Thread de processamento zip e fazer com que ela chame essa função quando terminar. Dessa forma, seu thread principal inicia o thread zip e continua fazendo as coisas dele (atualizando a GUI, o que você tem) e a função de retorno de chamada definirá algo no thread principal quando terminar (como booleano zipDone = true) e neste ponto, o thread principal pode reagir a isso (exibir "Zip extraído" na GUI etc.).

Shivan Dragon
fonte
2
Parece que sim, mas WaitForComplete é uma corrotina do Unity3D, portanto, ele executará apenas uma iteração do loop while a cada quadro, e então cederá para permitir que todo o resto seja atualizado no jogo. Não está bloqueando o fio :)
David Gouveia
-2

Você não precisa bloquear _isDone, mas precisa ser marcado como volátil.

Jess Winter
fonte