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

В чем разница между .Net Task и Unity Task?

2.0 Middle🔥 123 комментариев
#C# и ООП#Асинхронность и многопоточность

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

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

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

Разница между .NET Task и Unity Task (UniTask)

Основное отличие заключается в том, что .NET Task — это стандартный механизм асинхронного программирования в экосистеме Microsoft .NET, в то время как Unity Task (чаще называемый UniTask) — это специализированная библиотека, созданная для высокопроизводительного и безаллокационного асинхронного программирования в среде Unity, учитывающая её однопоточную природу (основной цикл в основном потоке) и особые требования к сборщику мусора (Garbage Collection, GC).

Ключевые различия

1. Целевая среда и производительность

  • .NET Task (System.Threading.Tasks.Task): Предназначен для общего применения — веб-серверов, десктопных приложений. Может создавать аллокации памяти (например, при создании Task или использовании async/await с контекстом синхронизации), что в реальном времени (real-time) игровом цикле Unity (где частота вызовов 60 FPS и выше) приводит к частым и нежелательным паузам на сборку мусора (GC).
  • UniTask: Создан специально для Unity. Его главные цели — нулевые аллокации (zero allocation) и полная интеграция с циклом жизни Unity. Он не полагается на стандартный пул потоков .NET для продолжений (continuations), что делает его идеальным для Update, FixedUpdate и обработки ввода, где производительность критична.

2. Интеграция с Unity

  • .NET Task: Не имеет встроенной связи с MonoBehaviour. Для ожидания кадра или секунды нужно использовать Task.Delay, что не синхронизировано с игровым временем (Time.deltaTime). Для возврата в основной поток требуется SynchronizationContext или Dispatcher.
    // .NET Task в Unity - требует ручного управления потоком
    async Task MoveObject()
    {
        await Task.Delay(1000); // Задержка в реальных миллисекундах, не в игровом времени
        // Чтобы изменить Transform, нужно вернуться в главный поток:
        await Task.Run(() => SomeHeavyCalculation()); // Запуск в фоновом потоке
        // Но изменение GameObject должно быть в главном потоке:
        await UnitySynchronizationContext.Instance; // Если настроен контекст
        transform.position = new Vector3(1, 0, 0);
    }
    
  • UniTask: Имеет нативные интеграции. Позволяет ожидать кадры, игровое время, операции загрузки ресурсов и события Unity напрямую, без аллокаций.
    using Cysharp.Threading.Tasks;
    // UniTask - глубоко интегрирован с Unity
    async UniTaskVoid MoveObjectAsync() // UniTaskVoid не создает Task-объект
    {
        await UniTask.DelayFrame(30); // Ждем 30 кадров (зависит от FPS)
        await UniTask.Delay(TimeSpan.FromSeconds(1.5f), ignoreTimeScale: false); // Ждем 1.5 игровых секунд
    
        // Асинхронная операция, которая автоматически выполняется в пуле потоков, а продолжение — в главном
        var result = await UniTask.RunOnThreadPool(() => SomeHeavyCalculation());
        transform.position = new Vector3(result, 0, 0); // Уже в главном потоке! Без явного указания контекста.
    
        // Ожидание событий Unity
        await UniTask.WaitUntil(() => isPlayerReady);
        // Загрузка ассетов через Addressables с UniTask
        var prefab = await Addressables.LoadAssetAsync<GameObject>("PrefabKey").ToUniTask();
    }
    

3. Типы возвращаемых значений и аллокации

  • .NET Task: Метод async всегда возвращает Task, Task<T> или ValueTask<T>. Создание объекта Task — это аллокация в управляемой куче.
  • UniTask: Вводит типы-значения (value types) UniTask и UniTask<T>. Они являются struct, что значит:
    *   **Нет аллокаций в управляемой куче** при создании и ожидании.
    *   **Нельзя `await` дважды** (так как структура копируется), что обычно является корректным сценарием в играх.

4. Отмена операций (Cancellation)

  • .NET Task: Использует CancellationTokenSource и CancellationToken. Часто требует ручного создания и управления токеном.
  • UniTask: Прямо интегрируется с MonoBehaviour и GameObject. Можно легко отменить все задачи, связанные с уничтожением объекта, используя встроенный токен отмены.
    public class MyComponent : MonoBehaviour
    {
        async UniTaskVoid DownloadDataAsync(CancellationToken token = default)
        {
            // Комбинируем внешний токен и токен уничтожения этого GameObject.
            // Если объект будет уничтожен (Destroy) - задача автоматически отменится.
            var linkedToken = CancellationTokenSource.CreateLinkedTokenSource(
                token,
                this.GetCancellationTokenOnDestroy()
            ).Token;
    
            await UniTask.Delay(1000, cancellationToken: linkedToken);
            // Если Delay был отменен, здесь код не выполнится
        }
    }
    

Резюме и рекомендации по использованию

  • Используйте UniTask для всего, что связано с игровым циклом, UI, анимациями, загрузкой ресурсов и любыми частыми операциями. Он предсказуем, производителен и предотвращает просадки FPS из-за GC.
  • Используйте .NET Task для изолированных, фоновых вычислений, не требующих доступа к API Unity (например, сложная математика, работа с файлами на диске, сетевые запросы, где вы не планируете сразу модифицировать GameObject). Но даже тогда рассмотрите обертку в UniTask.RunOnThreadPool для лучшей интеграции.

Техническая причина фундаментального различия: Unity долгое время использовала устаревшую версию среды выполнения .NET (Mono), в которой не было полноценной поддержки async/await. UniTask изначально был создан как высокоэффективное решение, обходящее эти ограничения, и остался стандартом де-факто даже с приходом более современных версий .NET в Unity, благодаря своей оптимизированной для геймдева архитектуре.