Unity: корутины и UniTask — когда что использовать

от автора

Привет, разработчик! Сегодня разберем две важные вещи в Unity — корутины и UniTask. Представь, что ты готовишь обед. Корутины — это как готовить по старинке, а UniTask — как современная мультиварка. Давай разберемся, что лучше.

Что такое корутины?

Корутины — это способ Unity выполнять задачи по частям, не останавливая всю игру. Представь, что ты готовишь обед. Вместо того чтобы стоять у плиты и ждать, пока сварится суп, ты можешь поставить его вариться, пойти нарезать овощи для салата, потом вернуться и проверить суп, снова отойти и накрыть на стол. Так и корутины — они позволяют игре делать несколько дел одновременно.

Важно: Корутины работают только с MonoBehaviour (компонентами Unity).

using System.Collections; using UnityEngine;  public class SimpleCoroutine : MonoBehaviour {     void Start()     {         StartCoroutine(CountNumbers());     }      IEnumerator CountNumbers()     {         for (int i = 1; i <= 5; i++)         {             Debug.Log($"Число: {i}");             yield return new WaitForSeconds(1f);         }         Debug.Log("Счет закончен!");     } } 

Что здесь происходит:

  • Мы создали функцию CountNumbers(), которая возвращает IEnumerator

  • Внутри цикла мы выводим число и ждем секунду

  • yield return new WaitForSeconds(1f) говорит Unity: «Подожди секунду, потом продолжай»

  • Корутина выполняется по частям, не блокируя игру

Что такое UniTask?

UniTask — это современная библиотека для асинхронного программирования. Она работает быстрее корутин и дает больше возможностей. Представь, что корутины — это старый телефон, а UniTask — это смартфон.

Важно: UniTask нужно установить отдельно через Package Manager Unity. Это не встроенная функция, как корутины.

Примечание: В примерах мы используем async UniTaskVoid вместо async void для большей безопасности. async void может привести к необработанным исключениям.

using Cysharp.Threading.Tasks; using UnityEngine;  public class SimpleUniTask : MonoBehaviour {     async UniTaskVoid Start()     {         await CountNumbersAsync();     }      async UniTask CountNumbersAsync()     {         for (int i = 1; i <= 5; i++)         {             Debug.Log($"Число: {i}");             await UniTask.Delay(1000);         }         Debug.Log("Счет закончен!");     } } 

Что здесь происходит:

  • Мы используем async и await — современный способ писать асинхронный код

  • UniTask.Delay(1000) ждет 1000 миллисекунд (1 секунда)

  • Код выглядит как обычный, но выполняется асинхронно

  • UniTask работает быстрее корутин для сложных задач

Сравнение производительности

Корутины создают объекты в памяти, но Unity их кэширует (запоминает) для повторного использования. UniTask работает еще эффективнее и не создает лишних объектов. Представь разницу между покупкой новой коробки для каждой вещи и использованием одной коробки много раз.

// Корутина yield return new WaitForSeconds(1f);  // UniTask await UniTask.Delay(1000); 

Обработка ошибок

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

Проблема с корутинами

IEnumerator LoadDataCoroutine() {     // Если здесь произойдет ошибка, корутина просто остановится     // и никто об этом не узнает     yield return new WaitForSeconds(1f);          // Этот код может не выполниться из-за ошибки выше     Debug.Log("Данные загружены"); }  // Вызываем корутину void Start() {     StartCoroutine(LoadDataCoroutine());     // Мы не знаем, успешно ли выполнилась корутина } 

Что здесь происходит:

  • Если в корутине произойдет ошибка, она просто остановится

  • Код, который вызвал корутину, не узнает об ошибке

  • Нет способа узнать, что пошло не так

Решение с UniTask

using Cysharp.Threading.Tasks; using UnityEngine;  async UniTask LoadDataAsync() {     try     {         await UniTask.Delay(1000);         Debug.Log("Данные загружены");     }     catch (System.Exception e)     {         Debug.LogError($"Ошибка загрузки: {e.Message}");         // Можно попробовать загрузить данные снова         await RetryLoadData();     } }  async UniTask RetryLoadData() {     Debug.Log("Пробуем загрузить данные снова...");     await UniTask.Delay(2000);     Debug.Log("Данные загружены со второй попытки"); }  // Вызываем UniTask async UniTaskVoid Start() {     try     {         await LoadDataAsync();         Debug.Log("Все прошло успешно!");     }     catch (System.Exception e)     {         Debug.LogError($"Критическая ошибка: {e.Message}");     } } 

Что здесь происходит:

  • Если произойдет ошибка, мы сразу об этом узнаем

  • Можем обработать ошибку и попробовать снова

  • Код, который вызвал функцию, тоже узнает об ошибке

  • Есть полный контроль над тем, что делать при ошибке

Отмена операций

UniTask позволяет легко отменить операцию. Корутины этого не умеют.

using System.Threading; using Cysharp.Threading.Tasks; using UnityEngine;  public class CancellationExample : MonoBehaviour {     private CancellationTokenSource _cancellationTokenSource;      async UniTaskVoid Start()     {         _cancellationTokenSource = new CancellationTokenSource();         await LongOperation(_cancellationTokenSource.Token);     }      async UniTask LongOperation(CancellationToken cancellationToken)     {         for (int i = 0; i < 100; i++)         {             // Проверяем, не отменили ли операцию             cancellationToken.ThrowIfCancellationRequested();                          Debug.Log($"Шаг {i}");             await UniTask.Delay(100, cancellationToken: cancellationToken);         }     }      void OnDestroy()     {         // Отменяем операцию при уничтожении объекта         _cancellationTokenSource?.Cancel();     } } 

Что здесь происходит:

  • CancellationTokenSource — это как пульт управления для отмены операций

  • cancellationToken — это сигнал, который говорит «отмени операцию»

  • cancellationToken.ThrowIfCancellationRequested() — проверяет, не нажали ли кнопку отмены

  • Если операцию отменили, код выбрасывает исключение и останавливается

  • await UniTask.Delay(100, cancellationToken: cancellationToken) — ждет 100 миллисекунд, но может отмениться раньше

  • _cancellationTokenSource?.Cancel() — нажимает кнопку отмены при уничтожении объекта

Когда использовать корутины?

Корутины подходят для простых задач:

  • Анимации

  • Простые задержки

  • Когда не нужна отмена операции

  • Когда ты делаешь простую игру

  • Когда не хочешь устанавливать дополнительные библиотеки

Плюсы корутин:

  • Уже есть в Unity

  • Простые в использовании

  • Подходят для начинающих

Когда использовать UniTask?

UniTask лучше для сложных задач:

  • Загрузка данных из интернета

  • Работа с файлами

  • Когда нужна отмена операции

  • Когда важна производительность

  • Работа с ECS (Entity Component System)

  • Большие проекты

Плюсы UniTask:

  • Быстрее работает

  • Лучше обрабатывает ошибки

  • Можно отменять операции

  • Работает везде, не только с MonoBehaviour

ECS и асинхронность

ECS (Entity Component System) — это другой способ создавать игры в Unity. Вместо MonoBehaviour здесь используются чистые C# классы. Это как разница между готовкой по рецепту (MonoBehaviour) и готовкой по интуиции (ECS).

Проблема с корутинами в ECS

using System.Collections; using UnityEngine;  // Корутины работают только с MonoBehaviour public class PlayerSystem : MonoBehaviour {     private float playerHealth = 50f;     private float maxHealth = 100f;     private float healAmount = 10f;          IEnumerator HealPlayerCoroutine()     {         while (playerHealth < maxHealth)         {             playerHealth += healAmount;             yield return new WaitForSeconds(1f);         }     } }  // В ECS нет MonoBehaviour, поэтому корутины не работают! public class PlayerSystem : SystemBase {     // Здесь нельзя использовать корутины     // IEnumerator HealPlayerCoroutine() - НЕ РАБОТАЕТ! } 

Что здесь происходит:

  • Корутины требуют MonoBehaviour для работы

  • В ECS используются чистые C# классы без MonoBehaviour

  • Корутины просто не запустятся в ECS системах

Решение с UniTask в ECS

using Unity.Entities; using Unity.Collections; using Cysharp.Threading.Tasks; using UnityEngine;  public class PlayerSystem : SystemBase {     private bool _isHealing = false; // Флаг, чтобы не запускать лечение много раз      protected override void OnUpdate()     {         // Запускаем лечение только один раз         if (!_isHealing)         {             _isHealing = true;             _ = HealPlayerAsync(); // _ = означает "запустить и забыть"         }     }      private async UniTask HealPlayerAsync()     {         var playerQuery = GetEntityQuery(typeof(PlayerComponent));         var playerEntities = playerQuery.ToEntityArray(Allocator.Temp);          foreach (var entity in playerEntities)         {             var playerComponent = EntityManager.GetComponentData<PlayerComponent>(entity);                          while (playerComponent.health < playerComponent.maxHealth)             {                 playerComponent.health += playerComponent.healAmount;                 EntityManager.SetComponentData(entity, playerComponent);                                  await UniTask.Delay(1000); // Ждем 1 секунду             }         }          playerEntities?.Dispose();         _isHealing = false; // Сбрасываем флаг     } }  // Компонент для игрока public struct PlayerComponent : IComponentData {     public float health;     public float maxHealth;     public float healAmount; } 

Что здесь происходит:

  • UniTask работает в любом C# классе, включая ECS системы

  • Мы можем использовать await в ECS системах

  • Асинхронные операции работают с компонентами данных

  • Нет зависимости от MonoBehaviour

  • _ = означает «запустить задачу и не ждать её завершения»

  • Allocator.Temp — это способ выделить память для временных данных

Преимущества UniTask в ECS

using Unity.Entities; using Cysharp.Threading.Tasks; using UnityEngine;  public class GameManager : SystemBase {     private bool _isInitialized = false;      protected override void OnUpdate()     {         // Запускаем инициализацию только один раз         if (!_isInitialized)         {             _isInitialized = true;             _ = InitializeGameAsync();         }     }      private async UniTask InitializeGameAsync()     {         // Запускаем несколько асинхронных операций одновременно         var loadLevelTask = LoadLevelAsync();         var updateUITask = UpdateUIAsync();         var saveGameTask = SaveGameAsync();          // Ждем завершения всех задач         await UniTask.WhenAll(loadLevelTask, updateUITask, saveGameTask);         Debug.Log("Все задачи завершены!");     }      private async UniTask LoadLevelAsync()     {         // Загружаем уровень         await UniTask.Delay(2000);         Debug.Log("Уровень загружен");     }      private async UniTask UpdateUIAsync()     {         // Обновляем интерфейс         await UniTask.Delay(500);         Debug.Log("UI обновлен");     }      private async UniTask SaveGameAsync()     {         // Сохраняем игру         await UniTask.Delay(1000);         Debug.Log("Игра сохранена");     } } 

Что здесь происходит:

  • В ECS можно запускать несколько асинхронных операций одновременно

  • Каждая операция работает независимо

  • Нет блокировки основного потока

  • Код остается чистым и понятным

  • UniTask.WhenAll() ждет завершения всех задач

Установка UniTask

Чтобы использовать UniTask, нужно его установить:

  1. Открой Window → Package Manager в Unity

  2. Нажми «+» → «Add package from git URL»

  3. Введи: https://github.com/Cysharp/UniTask.git

  4. Нажми «Add»

Или через OpenUPM:

  1. Открой Window → Package Manager

  2. Нажми «+» → «Add package from git URL»

  3. Введи: com.cysharp.unitask

  4. Нажми «Add»

Или скачай с GitHub и добавь в проект вручную.

Заключение

Корутины — это старый, но надежный способ. UniTask — современный и быстрый.

Выбирай так:

  • Корутины — для простых игр, анимаций, когда ты только учишься

  • UniTask — для сложных проектов, когда нужна производительность и контроль

Помни: UniTask нужно установить отдельно, корутины уже есть в Unity.


ссылка на оригинал статьи https://habr.com/ru/articles/927308/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *