Miscelazione di fili e coroutine in Unity3D Mobile


8

Avevo una coroutine in Unity3D che scaricava un zip da un server, lo estraeva nel percorso dati persistente e ne caricava il contenuto in memoria. Il flusso era simile al seguente:

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

Ma il ExtractZip()metodo (che utilizza la libreria DotNetZip) è sincrono, impiega troppo tempo e non mi lascia spazio per cedere durante il processo.

Ciò ha comportato la morte dell'applicazione (su dispositivi mobili) ogni volta che ho provato a estrarre un grande zip, che presumo sia dovuto al fatto che il thread principale non risponde troppo a lungo.

È noto che il sistema operativo mobile è noto (uccidere l'app se un frame impiega troppo tempo)?

Quindi, ho ipotizzato che l'estrazione della zip su un thread separato potesse risolvere il problema e sembra aver funzionato. Ho creato questa 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 lo uso così:

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

Ma non sono ancora sicuro se questo è il modo migliore per implementarlo, o se ho bisogno di bloccare _isDone(non troppo abituato al multithreading).

Qualcosa può andare storto con questo / mi sto perdendo qualcosa?

Risposte:


4

Questa è una soluzione davvero elegante per avvolgere le attività multithread in un coroutine, ben fatto :)

La miscelazione di coroutine e thread è perfettamente sicura, a condizione che si blocchi correttamente l'accesso alle risorse condivise tra il thread principale (su cui viene eseguita la routine) e i thread di lavoro creati. Non dovresti aver bisogno di bloccare _isDone in alcun modo, poiché è sempre e solo scritto dal thread di lavoro e non esiste uno stato intermedio che potrebbe causare un comportamento errato del thread principale.

Dove devi cercare potenziali problemi, è se qualche risorsa è scritta da ExtractZip e lo sono entrambe

  1. scritto contemporaneamente da una funzione chiamata dal tuo thread principale o
  2. essere letto da una funzione sul thread principale e dovrebbe essere in uno stato sicuro prima del completamento di ExtractZip.

In questo caso particolare, la mia preoccupazione sarebbe che se non verifichi che non stai provando a scaricare lo stesso file nella stessa posizione due volte, potresti avere due thread che eseguono contemporaneamente ExtractZip che interferiscono l'uno con l'altro.


1

Esiste una soluzione gratuita per questo sull'Asset Store:

Discussione Ninja

Con esso puoi passare facilmente tra il thread principale e il thread di sfondo, a cui piace:

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
    // ...

-1

Beh, non sono neanche un grande esperto, ma penso nel tuo secondo esempio, la funzione WaitForComplete () bloccherà comunque il thread chiamante fino al completamento del thread ThreadedAction. Quello che puoi fare è passare una funzione di callback al tuo thread di elaborazione zip e farlo chiamare al termine. In questo modo il tuo thread principale avvia il thread zip e quindi continua a fare le sue cose (aggiornando la GUI, che cosa hai), e quindi la funzione di richiamata imposterà qualcosa nel thread principale al termine (come booleano zipDone = true), e a questo punto il thread principale può reagire a questo (visualizzare "Zip extracted" nella GUI ecc.).


2
Sembrerebbe di sì, ma WaitForComplete è un coroutine di Unity3D, quindi eseguirà solo un'iterazione del ciclo while di ogni fotogramma, quindi cederà per consentire a tutto il resto del gioco di aggiornarsi. Non sta bloccando la discussione :)
David Gouveia,

-2

Non è necessario bloccare _isDone, ma deve essere contrassegnato come volatile.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.