Unity 6.3
0 онлайн 95 гостей 3 в системе
Вход
Программирование в Unity Шаг 124 из 210

Введение в асинхронное программирование с Awaitable

Awaitable класс — это собственный тип Unity, который можно ожидать и использовать как возвращаемый тип async в модели асинхронного программирования C#. Большинство асинхронных API Unity поддерживают async и await модели, включая:

Вы можете использовать Awaitable класс и с await оператора и в качестве async return тип в вашем собственном коде, следующим образом:

async Awaitable<List<Achievement>> GetAchievementsAsync()
{
    var apiResult = await SomeMethodReturningATask(); // or any await-compatible type
    List<Achievement> achievements = JsonConvert.DeserializeObject<List<Achievement>>(apiResult);
    return achievements;
}

async Awaitable ShowAchievementsView()
{
    ShowLoadingOverlay();
    List<Achievement> achievements = await GetAchievementsAsync();
    HideLoadingOverlay();
    ShowAchivementsList(achievements);
}

Ожидаемое по сравнению с задачей .NET

Awaitable разработан для того, чтобы предложить более эффективную альтернативу .NET Task для асинхронного кода в проектах Unity. Эффективность Awaitable имеет некоторые важные ограничения по сравнению с Task.

Наиболее существенным ограничением является то, что экземпляры Awaitable объединяются для ограничения выделений. Рассмотрим следующий пример:

class SomeMonoBehaviorWithAwaitable : MonoBehavior
{
    public async void Start()
    {
        while(true)
        {
            // do some work on each frame
            await Awaitable.NextFrameAsync();
        }
    }
}

Без объединения каждый экземпляр MonoBehavior в этом примере выделил бы Awaitable объект каждый кадр, увеличивая рабочая нагрузка сборщика мусора Для смягчения этого эффекта Unity возвращает Awaitable объект во внутренний Awaitable бассейн, как только он ожидал.

Важно: Пул экземпляров Awaitable означает, что никогда нельзя безопасно выполнять await более одного раза на экземпляре Awaitable. Это может привести к неопределенному поведению, например, к исключению или тупиковой ситуации.

Ожидаемое по сравнению с .NET ValueTask

.NET ValueTask<TResult> предлагает некоторые из тех же ключевых преимуществ и ограничений, что и Awaitable. Типичное рекомендуемое использование ValueTask — это для асинхронных рабочих нагрузок, которые, как ожидается, будут выполняться синхронно большую часть времени. Дополнительные сведения см. в Понимание почему, что и когда ValueTask.

Ожидаемое, Задача и сводка ValueTask

Следующая таблица резюмирует сравнение функций между Unity Awaitable класс и .NET Task и ValueTask:

Особенности Task ValueTask UnityEngine.Awaitable
Требуемые ассигнования Многие.
Выделяет при каждом вызове методу Task-returning, увеличивая использование памяти и нагрузку сборщика мусора.
По мере необходимости.
Может быть оптимизировано с помощью пулирования.
Минимальный по мере необходимости.
Вызов метода Awaitable-returning обычно не выделяет памяти, поскольку экземпляры Awaitable по умолчанию объединяются в пул.
Безопасный для ожидания несколько раз Да. Нет.
Необходимо преобразовать в Task с ValueTask.AsTask.
Нет.
Необходимо преобразовать в Task с помощью пользовательских методов расширения AsTask, см. Ожидание несколько раз в одном и том же методе в примерах кода.
Продолжения работают асинхронно Да.
Использование контекста синхронизации по умолчанию, в противном случае использование ThreadPool. Это увеличивает задержку при завершении на главном потоке в Unity, потому что код должен ждать следующего кадра Update для возобновления.
Да.
Оптимизирован для случаев, когда ожидаемые задачи завершаются синхронно. Если они завершаются асинхронно, поведение продолжения эквивалентно Task.
Нет.
Продолжение выполняется синхронно при инициировании завершения, то есть код немедленно возобновляется в том же кадре, в котором инициировано завершение. Дополнительную информацию см. в Ожидаемое завершение и продолжение.
Завершение можно запустить кодом Да.
Использование TaskCompletionSource.
Неприменимо в типичном сценарии использования, т.е. для задач, выполняемых в основном синхронно. Да.
Использование AwaitableCompletionSource.
может возвращать значение Да.
Использование Task<TResult>.
Да.
Использование ValueTask<TResult>.
Да.
Использование UnityEngine.Awaitable<T>.
Встроенная поддержка WaitAll и WaitAny Да. Нет.
Необходимо преобразовать в Task с помощью ValueTask.AsTask.
Нет.
Необходимо преобразовать в Task с помощью настраиваемых методов расширения AsTask, см. Ожидаемая обвязка в задаче .NET в справочнике примеров кода.
Unity планирование выполнения потоков и циклов обновления Нет. Нет. Да.
Вы можете указать, на какой поток возобновляется Awaitable с помощью Awaitable.BackgroundThreadAsync и Awaitable.MainThreadAsync. Вы также можете запланировать работу относительно циклов Update или FixedUpdate с помощью Awaitable.NextFrameAsync и Awaitable.FixedUpdateAsync. Для более подробной информации см. Ожидаемое завершение и продолжение.

Когда использовать Awaitable над Задачей или ValueTask

Выбор API зависит от профиля производительности вашего асинхронного кода, но в целом:

  • Task является единственным вариантом, когда нужно ждать несколько раз или от нескольких потребителей одновременно.
  • ValueTask является хорошим выбором, если у вас есть высокопроходный асинхронный код, который завершается синхронно большую часть времени.
  • Awaitable является хорошим выбором, когда:
    • Вам не нужно ждать свои методы несколько раз и ожидать, что они в основном завершатся асинхронно.
    • Вы хотите, чтобы ваши асинхронные задачи имели встроенную поддержку специфических для Unity концепций, таких как основной поток и циклы Обновление и FixedUpdate.

Ожидаемое по сравнению с iterator-based coroutines

Сопроцедуры Awaitable обычно более эффективны, чем сопроцедуры, основанные на итераторах, особенно в случаях, когда итератор возвращает ненулевые значения, такие как WaitForFixedUpdate.

Однако преимущество в производительности сопутствующих процедур Awaitable уменьшается, когда вы запускаете много из них одновременно. Например, MonoBehaviour, такой как в предыдущем примере кода, который ожидает Awaitable.NextFrameAsync в цикле while, может вызвать проблемы производительности, если он будет прикреплен к каждой GameObject в большом проекте.

Примечание: Вы можете безопасно yield return год Awaitable от традиционного iterator-основанного coroutine, но вы не можете yield return год Awaitable<T0>.

Дополнительные ресурсы