Смешивание потоков и сопрограмм в Unity3D Mobile

8

У меня была сопрограмма в Unity3D, которая загрузила zip-файл с сервера, извлекла его в постоянный путь данных и загрузила его содержимое в память. Поток выглядел примерно так:

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

Но ExtractZip()метод (который использует библиотеку DotNetZip), является синхронным, занимает слишком много времени и не оставляет мне места для отдачи во время процесса.

Это приводило к удалению приложения (на мобильных устройствах) всякий раз, когда я пытался извлечь большой zip-файл, что, как я полагаю, связано с тем, что основной поток слишком долго не отвечал.

Известно ли, что это то, что делает мобильная ОС (убить приложение, если кадр занимает слишком много времени)?

Итак, я предположил, что извлечение почтового индекса в отдельном потоке может решить проблему, и, похоже, это сработало. Я создал этот класс:

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;
}

И я использую это так:

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

Но я до сих пор не уверен, является ли это лучшим способом реализовать его, или мне нужно блокировать _isDone(не слишком привык к многопоточности).

Может ли что-то пойти не так с этим / я что-то упустил?

Дэвид Гувея
источник

Ответы:

4

Это действительно элегантное решение для упаковки многопоточных задач в сопрограмму, хорошо сделанное :)

Смешивание сопрограмм и потоков совершенно безопасно, при условии, что вы правильно блокируете доступ к ресурсам, совместно используемым между вашим основным потоком (в котором выполняется сопрограмма) и рабочими потоками, которые вы создаете. Вам не нужно каким-либо образом блокировать _isDone, поскольку он записывается только рабочим потоком, и нет промежуточного состояния, которое могло бы привести к неправильной работе основного потока.

Вам нужно следить за потенциальными проблемами, если ExtractZip записывает какие-либо ресурсы и

  1. одновременно записывается функцией, вызываемой из вашего основного потока или
  2. читается функцией в главном потоке и, как ожидается, будет в безопасном состоянии до завершения ExtractZip.

В этом конкретном случае меня беспокоит то, что если вы не убедитесь, что не пытаетесь загрузить один и тот же файл дважды в одно и то же место, у вас может быть два потока, одновременно запускающих ExtractZip, которые мешают друг другу.

FlintZA
источник
1

Для этого в Asset Store есть бесплатное решение:

Нить ниндзя

С его помощью вы можете легко переключаться между основным потоком и фоновым потоком, что нравится:

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

Ну, я тоже не большой эксперт, но я думаю, что во втором примере функция WaitForComplete () будет по-прежнему блокировать вызывающий поток, пока поток ThreadedAction не завершится. Что вы можете сделать, это передать функцию обратного вызова в ваш поток обработки zip, и заставить его вызвать эту функцию, когда это будет сделано. Таким образом, ваш основной поток запускает поток zip, а затем продолжает выполнять свои действия (обновляя GUI, что у вас есть), а затем функция обратного вызова установит что-то в основном потоке, когда это будет сделано (например, boolean zipDone = true), и в этот момент основной поток может среагировать на это (отобразить «Zip extract» в графическом интерфейсе и т. д.).

Дракон Шиван
источник
2
Казалось бы, но WaitForComplete является сопрограммой Unity3D, поэтому он будет запускать только одну итерацию цикла while каждый кадр, а затем уступать, чтобы все остальное в игре обновлялось. Это не блокирует поток :)
Дэвид Гувейя
-2

Вам не нужно блокировать _isDone, но он должен быть помечен как volatile.

Джесс Винтер
источник