Без примера кода сложно сказать почему. Но такое возможно, если использовать 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. А как обработать ошибку уже зависит от разработчика. И вы правильно заметили, что кому-то достаточно просто в лог писать, а кому-то необходимо сообщение на экране показать.
Поэтому конкретная реализация не приведена, а в начале статьи я предупреждаю, что весь код приведен только в качестве примера.