← Назад к вопросам

Как работает UniTask?

1.7 Middle🔥 173 комментариев
#Unity Core#Асинхронность и многопоточность

Комментарии (3)

🐱
deepseek-v3.2PrepBro AI6 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

UniTask: Современная библиотека для асинхронного программирования в Unity

UniTask — это высокопроизводительная библиотека для C#, переосмысливающая асинхронное программирование в Unity, построенная на основе async/await паттерна. Она была создана для устранения недостатков стандартного System.Threading.Tasks.Task в контексте игрового движка, где критичны производительность, отсутствие аллокаций в куче (heap allocations) и глубокая интеграция с циклом жизни Unity.

Ключевые принципы работы UniTask

В отличие от стандартного Task, который аллоцирует память в куче и зависит от SynchronizationContext, UniTask реализует структурный тип значения (value-type struct). Это фундаментальное отличие приносит несколько преимуществ:

  • Нулевые аллокации в куче (Zero Heap Allocations): Поскольку UniTask является структурой, её создание и ожидание (await) не приводит к выделению памяти в управляемой куче (если операция не требует состояния после завершения). Это критически важно для поддержания плавного FPS и минимизации сборок мусора (GC).
  • Прямая интеграция с циклом обновления Unity: UniTask предоставляет собственные "авайтеры" (awaiters), которые работают с PlayerLoop Unity. Это позволяет ожидать (await) кадры, фиксированные обновления, конец кадра и другие специфичные для игрового движка события без дополнительных затрат.
  • Отмена операций через CancellationToken: Поддержка отмены асинхронных операций через CancellationToken и собственный CancellationTokenSource, связанный с GameObject, который автоматически отменяется при уничтожении объекта.

Основные способы использования

  1. Ожидание специфичных для Unity событий:

    using Cysharp.Threading.Tasks;
    using UnityEngine;
    
    public class Example : MonoBehaviour
    {
        async UniTaskVoid Start()
        {
            // Ждём один кадр (аналог yield return null)
            await UniTask.NextFrame();
    
            // Ждём окончания текущего кадра (аналог WaitForEndOfFrame)
            await UniTask.WaitForEndOfFrame();
    
            // Ждём 1 секунду с игровым временем (scaled time)
            await UniTask.Delay(1000); // миллисекунды
    
            // Ждём 2 секунды с НЕигровым временем (unscaled time, игнорирует Time.timeScale)
            await UniTask.Delay(2000, ignoreTimeScale: true);
    
            // Ждём, пока условие не станет true (проверяется каждый кадр)
            await UniTask.WaitUntil(() => transform.position.y > 10);
        }
    }
    
  2. Возврат результата из асинхронного метода:

    // UniTask<T> используется для возврата значений, UniTask — для void-подобных операций.
    public async UniTask<int> LoadDataAsync(string url)
    {
        await UniTask.Delay(500);
        // Имитация загрузки...
        return 42;
    }
    
    async UniTaskVoid ProcessData()
    {
        int result = await LoadDataAsync("http://example.com/data");
        Debug.Log($"Data loaded: {result}");
    }
    
  3. Управление жизненным циклом и отмена:

    public async UniTaskVoid DownloadAssetAsync(CancellationToken token)
    {
        try
        {
            // Метод будет отменён, если token вызовет отмену ДО завершения задержки.
            await UniTask.Delay(3000, cancellationToken: token);
            Debug.Log("Download complete!");
        }
        catch (OperationCanceledException)
        {
            Debug.Log("Download was cancelled.");
        }
    }
    
    void Start()
    {
        // Создаём CancellationToken, привязанный к жизненному циклу этого GameObject.
        var cancellationToken = this.GetCancellationTokenOnDestroy();
        DownloadAssetAsync(cancellationToken).Forget(); // Forget() для "огне-и-забудь" задач
    }
    
  4. Асинхронные операции над ресурсами Unity:

    // Awaitable-версии стандартных операций Unity
    async UniTaskVoid LoadSceneAsync()
    {
        await SceneManager.LoadSceneAsync("Scene2").ToUniTask();
    }
    
    async UniTask<Texture> LoadTextureAsync(string path)
    {
        // ResourceRequest также может быть ожидаемым
        var resourceRequest = Resources.LoadAsync<Texture>(path);
        return (Texture)await resourceRequest;
    }
    

Важные особенности и лучшие практики

  • UniTaskVoid: Используется для async методов, которые не нужно ожидать (аналогично async void). Предпочтительнее async void, так как позволяет корректно ловить исключения и интегрируется с системой отмены UniTask.
  • Не требуется StartCoroutine / yield return: Весь код выполняется в одном методе, что делает его линейным и удобочитаемым, в отличие от корутин.
  • Forget(): Метод для запуска UniTask или UniTaskVoid, когда результат не нужно ждать. Он автоматически логирует необработанные исключения (по умолчанию в консоль Unity), что безопаснее, чем игнорировать задачу.
  • UniTask.Run: Запуск тяжёлых вычислений на фоновом потоке пула с последующим возвратом результата в основной поток Unity.
  • UniTask.WhenAll / UniTask.WhenAny: Аналоги Task.WhenAll/WhenAny для параллельного ожидания нескольких UniTask, но без аллокаций в куче.

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

Как работает UniTask? | PrepBro