Без примера кода сложно сказать почему. Но такое возможно, если использовать UniTask.RunOnThreadPool или UniTask.SwitchToThreadPool. Тогда задача запустится на ThreadPool, а не на PlayerLoop.
Нужно же всё в одном. Множество запросов, множество задач, которые хотят использовать эти данные, а их использование зависит от других задач. ... Концептуально другое использование.
Можете привести рабочий пример с кодом? Не совсем понимаю, какое концептуально другое использование async/await тут может быть.
Я не вижу смысла в использовании UniTask повсеместно из-за его ограничений.
Про какие ограничения речь? Приведите пример с Task и покажите, почему то же самое невозможно реализовать с UniTask.
Спасибо за Ваши комментарии. Благодаря им моя следующая статья про детальный разбор отличий UniTask от Task будет куда богаче, так как становится понятнее, в чём заключается основное недопонимание работы с async/await и с UniTask в частности.
Касательно вопросов.
А если нужно множество запросов?
Оставляете начальную реализацию NetworkService без изменений.
А если ожидать данные с одного запроса нужно из нескольких мест и мы не контролируем момент получения результата и запроса?
Меняете реализацию NetworkService на предложенный вариант с AsyncLazy.
придётся генерить классы.
Реализация через Task и реализация через AsyncLazy будет генерировать одинаковое кол-во классов.
Такое ощущение, что Вам лично не приятен UniTask, но я же не заставляю Вас его использовать. Используйте Task. Но статья от этого актуальности не теряет, ведь основной упор сделан на работу с async/await, а что для этого использовать – решать Вам.
P.S. Если про неприязнь к UniTask я не прав, вместо догадок предлагаю открыть IDE и попробовать реализовать все Ваши идеи. И если останутся вопросы, то приходите с конкретными примерами. Но перед этим перечитайте ещё раз статью внимательнее. В ней есть ответы на все Ваши вопросы.
Для запуска вне основного потока надо использовать Task.Run(). Так называемая "настоящая асинхронность" потоков будет работать только в этом случае.
Не совсем так. В другой поток мы можем попасть и так:
private async Task StartOperationAsync()
{
await WaitAsync().ConfigureAwait(false);
// Not a main thread.
}
private async Task WaitAsync()
{
// Still the main thread.
await Task.Yield();
}
Если говорим про UniTask, то можем так:
private async UniTask StartOperationAsync()
{
await UniTask.SwitchToThreadPool();
// Not a main thread.
print(Thread.CurrentThread.ManagedThreadId);
await UniTask.SwitchToMainThread();
}
As a limitation, all UniTask objects cannot await twice, as they automatically return to the pool when the await completes.
Если ознакомиться со статьёй внимательнее, а именно с разделом Concurrency, то можно там найти следующее:
Обратите внимание, что поле _longServerRequestTask у нас типа AsyncLazy<Data>, а не UniTask<Data>. Всё потому, что UniTask не позволяет выполнить await задачи более одного раза, как и ValueTask. Связано это с тем, что UniTask – это переиспользуемая структура, и после await объект возвращается в пул. Повторный вызов await выбросит исключение, так как объект уже может использоваться в другом месте.
Там же приведен пример, как одну и туже задачу мы await'им сколь угодно раз, используя AsyncLazy.
Если коротко, то UniTask был написан специально для Unity, тесно интегрирован с игровым циклом и учитывает его особенности. Т.е. например, ситуации, когда запустил задачу, а потом вышел из Play Mode, а задача продолжает работать, не будет.
А как добавление суффикса Async к async void методу решает описанную проблему? Это только больше вводит в заблуждение. Почему метод асинхронный, а я не могу воспользоваться ключевым словом await? Это то же самое, что взять плохой код, но при этом назвать его красиво, например, «Photon».
По поводу добавления суффикса Async к методам, которые возвращают UniTask, Task и т.д. согласен. Во всех приведённых примерах эта конвенция наименования соблюдена.
P.S. Все названия и события вымышлены. Любые совпадения с реальными продуктами случайны.
Всё верно. А ещё GetCancellationTokenOnDestroy() под капотом вызывает TryGetComponent(). Поэтому, если планируете его использовать, лучше кэшировать CancellationToken на старте.
В C# async void методы обычно используются для сценариев «запустил и забыл». В идеале эти методы не должны быть блокирующими и, как правило, используются только в обработчиках событий или асинхронном коде верхнего уровня.
Минус очевиден - лишний код, в котором могут быть допущены ошибки. И вам все равно в UXML файле необходимо будет задать имена компонентов. Так проще там сразу задать свойство для связывания, чем городить такой огород.
Пример демонстрирует возможность обернуть асинхронный метод в блок try/catch. А как обработать ошибку уже зависит от разработчика. И вы правильно заметили, что кому-то достаточно просто в лог писать, а кому-то необходимо сообщение на экране показать.
Поэтому конкретная реализация не приведена, а в начале статьи я предупреждаю, что весь код приведен только в качестве примера.
Без примера кода сложно сказать почему. Но такое возможно, если использовать
UniTask.RunOnThreadPoolилиUniTask.SwitchToThreadPool. Тогда задача запустится на ThreadPool, а не на PlayerLoop.Можете привести рабочий пример с кодом? Не совсем понимаю, какое концептуально другое использование async/await тут может быть.
Про какие ограничения речь? Приведите пример с Task и покажите, почему то же самое невозможно реализовать с UniTask.
Спасибо за Ваши комментарии. Благодаря им моя следующая статья про детальный разбор отличий UniTask от Task будет куда богаче, так как становится понятнее, в чём заключается основное недопонимание работы с async/await и с UniTask в частности.
Касательно вопросов.
Оставляете начальную реализацию
NetworkServiceбез изменений.Меняете реализацию
NetworkServiceна предложенный вариант сAsyncLazy.Реализация через Task и реализация через AsyncLazy будет генерировать одинаковое кол-во классов.
Такое ощущение, что Вам лично не приятен UniTask, но я же не заставляю Вас его использовать. Используйте Task. Но статья от этого актуальности не теряет, ведь основной упор сделан на работу с async/await, а что для этого использовать – решать Вам.
P.S. Если про неприязнь к UniTask я не прав, вместо догадок предлагаю открыть IDE и попробовать реализовать все Ваши идеи. И если останутся вопросы, то приходите с конкретными примерами. Но перед этим перечитайте ещё раз статью внимательнее. В ней есть ответы на все Ваши вопросы.
Не совсем так. В другой поток мы можем попасть и так:
Если говорим про UniTask, то можем так:
Если ознакомиться со статьёй внимательнее, а именно с разделом Concurrency, то можно там найти следующее:
Там же приведен пример, как одну и туже задачу мы await'им сколь угодно раз, используя
AsyncLazy.Если коротко, то UniTask был написан специально для Unity, тесно интегрирован с игровым циклом и учитывает его особенности. Т.е. например, ситуации, когда запустил задачу, а потом вышел из Play Mode, а задача продолжает работать, не будет.
По поводу стека вызовов. Не совсем понял.
Следующий код:
Выдаст такой стек вызовов:
А как добавление суффикса
Asyncкasync voidметоду решает описанную проблему? Это только больше вводит в заблуждение. Почему метод асинхронный, а я не могу воспользоваться ключевым словомawait? Это то же самое, что взять плохой код, но при этом назвать его красиво, например, «Photon».По поводу добавления суффикса
Asyncк методам, которые возвращаютUniTask,Taskи т.д. согласен. Во всех приведённых примерах эта конвенция наименования соблюдена.P.S. Все названия и события вымышлены. Любые совпадения с реальными продуктами случайны.
Всё верно. А ещё
GetCancellationTokenOnDestroy()под капотом вызываетTryGetComponent(). Поэтому, если планируете его использовать, лучше кэшироватьCancellationTokenна старте.В статье про это есть)
Так что же вам не мешает это сделать?
Минус очевиден - лишний код, в котором могут быть допущены ошибки. И вам все равно в UXML файле необходимо будет задать имена компонентов. Так проще там сразу задать свойство для связывания, чем городить такой огород.
Пример демонстрирует возможность обернуть асинхронный метод в блок try/catch. А как обработать ошибку уже зависит от разработчика. И вы правильно заметили, что кому-то достаточно просто в лог писать, а кому-то необходимо сообщение на экране показать.
Поэтому конкретная реализация не приведена, а в начале статьи я предупреждаю, что весь код приведен только в качестве примера.