Что такое async в контексте корутин (Coroutines)?

«Что такое async в контексте корутин (Coroutines)?» — вопрос из категории Многопоточность, который задают на 25% собеседований C# Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

В Unity под "корутинами" (Coroutines) традиционно понимаются методы, возвращающие IEnumerator и использующие yield return для приостановки выполнения на определенный срок или до события. Ключевое слово async в C# представляет собой альтернативную, более мощную и современную модель асинхронного программирования, основанную на Task и Task<T>, которую также можно использовать в Unity.

Сравнение подходов:

Аспект Coroutines (IEnumerator) async / await
Основа Механизм Unity, тесно связан с циклом Update. Языковая и платформенная фича .NET.
Приостановка yield return new WaitForSeconds(1) await Task.Delay(1000)
Ожидание ресурса Нет нативной поддержки, нужны костыли. Прямая поддержка: await webRequest.SendWebRequest()
Возврат значения Через callback-стиль или глобальные переменные. Прямой возврат значения: var result = await operation
Обработка ошибок try/catch не работает с yield. Полноценная обработка через try/catch.
Контекст потока Всегда выполняется в основном потоке. По умолчанию возвращает управление в контекст синхронизации (основной поток в Unity).

Практический пример замены корутины на async:

using UnityEngine;
using System.Threading.Tasks;

public class AsyncExample : MonoBehaviour
{
    // Классическая корутина в Unity
    private IEnumerator LoadDataCoroutine()
    {
        Debug.Log("Загрузка началась...");
        yield return new WaitForSeconds(2f); // Блокирует основной поток на 2 секунды?
        Debug.Log("Данные загружены (через 2 сек).");
    }

    // Эквивалент с использованием async/await
    private async Task LoadDataAsync()
    {
        Debug.Log("Async загрузка началась...");
        await Task.Delay(2000); // Не блокирует основной поток!
        Debug.Log("Данные загружены асинхронно (через 2 сек).");
        // Можно безопасно работать с GameObject и компонентами здесь.
        gameObject.GetComponent<Renderer>().material.color = Color.green;
    }

    private async void Start() // Обработчики в Unity могут быть `async void`
    {
        // Запуск классической корутины
        StartCoroutine(LoadDataCoroutine());

        // Запуск асинхронной операции
        await LoadDataAsync();
        Debug.Log("Все операции завершены.");
    }
}

Рекомендация: Используйте async/await для операций, связанных с вводом-выводом (I/O), сетевыми запросами или длительными вычислениями, которые можно вынести в фоновый поток. Используйте корутины для последовательностей, жестко привязанных к кадрам (пошаговая анимация, плавное перемещение объекта между кадрами).