Mixage des threads et des coroutines dans Unity3D Mobile


8

J'ai eu une coroutine dans Unity3D qui a téléchargé un zip à partir d'un serveur, l'a extrait dans le chemin de données persistant et a chargé son contenu en mémoire. Le flux ressemblait à ceci:

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

Mais la ExtractZip()méthode (qui utilise la bibliothèque DotNetZip), est synchrone, prend trop de temps et ne me laisse aucune place pour céder pendant le processus.

Cela a entraîné la suppression de l'application (sur les appareils mobiles) chaque fois que j'essayais d'extraire un gros zip, ce qui, je suppose, est dû au fait que le thread principal ne répond plus trop longtemps.

Est-ce quelque chose que le système d'exploitation mobile est connu pour faire (tuer l'application si un cadre prend trop de temps)?

J'ai donc supposé que l'extraction du zip sur un thread séparé pourrait résoudre le problème, et cela semble avoir fonctionné. J'ai créé cette 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;
}

Et je l'utilise comme ceci:

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

Mais je ne sais toujours pas si c'est la meilleure façon de l'implémenter, ou si j'ai besoin de verrouiller _isDone(pas trop habitué au multithreading).

Est-ce que quelque chose peut mal tourner / est-ce que je manque quelque chose?

Réponses:


4

C'est une solution vraiment élégante pour encapsuler des tâches multithread dans une coroutine, bravo :)

Le mélange des coroutines et des threads est parfaitement sûr, à condition de verrouiller correctement l'accès aux ressources partagées entre votre thread principal (sur lequel la coroutine s'exécute) et le ou les threads de travail que vous créez. Vous ne devriez en aucun cas avoir besoin de verrouiller _isDone, car il n'est écrit que par le thread de travail et aucun état intermédiaire ne peut entraîner un mauvais comportement du thread principal.

Lorsque vous devez rechercher des problèmes potentiels, c'est si des ressources sont écrites par ExtractZip et sont soit

  1. écrit simultanément par une fonction appelée à partir de votre thread principal ou
  2. étant lu par une fonction sur le thread principal et attendu dans un état sûr avant la fin d'ExtractZip.

Dans ce cas particulier, mon inquiétude serait que si vous ne vérifiez pas que vous n'essayez pas de télécharger deux fois le même fichier au même emplacement, vous pourriez avoir deux threads exécutant simultanément ExtractZip qui interfèrent l'un avec l'autre.


1

Il existe une solution gratuite pour cela sur Asset Store:

Thread Ninja

Avec lui, vous pouvez facilement basculer entre le fil principal et le fil d'arrière-plan, ce qui aime:

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

Eh bien, je ne suis pas non plus un expert majeur, mais je pense que dans votre 2ème exemple, la fonction WaitForComplete () bloquera toujours le thread appelant jusqu'à ce que le thread ThreadedAction se termine. Ce que vous pouvez faire est de passer une fonction de rappel à votre thread de traitement zip et de lui faire appeler cette fonction une fois terminé. De cette façon, votre thread principal démarre le thread zip, puis continue de faire son travail (mise à jour de l'interface graphique, qu'avez-vous), puis la fonction de rappel définira quelque chose dans le thread principal une fois terminé (comme booléen zipDone = true), et à ce stade, le thread principal peut réagir à cela (afficher "Zip extrait" dans l'interface graphique, etc.).


2
Il semblerait que oui, mais WaitForComplete est une coroutine Unity3D, donc il n'exécutera qu'une seule itération de la boucle while à chaque image, puis cédera pour laisser tout le reste dans la mise à jour du jeu. Ça ne bloque pas le fil :)
David Gouveia

-2

Vous n'avez pas besoin de verrouiller _isDone, mais il doit être marqué comme volatile.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.