Комментарии 27
В принципе стандартные советы. Единственный спорный момент — это Предпочитайте return Task вместо return await.
Если возвращать Task от вызова асинхронного метода без использования await, то метод, который это делает не попадёт в стектрейс, выброшенный внутри вызываемого метода. И порой это может помешать пониманию хода программы. Плюс учитывая следующий совет о неработающих try-catch и using в таких методах, то я бы сформулировал совет наоборот (у нас на работе он был в стайлгайде): всегда делайте await в середине асинхронного стека вызовов вместо return Task.
А я разгребал, и не вижу никаких сложностей.
И, однако, сам факт того, что вы лично не увидели никаких сложностей где-то там у себя — совершенно не означает, что их ни у кого нет.
Так то, что вы их увидели себя — ровно также не означает, что у других они будут. Даже интересно узнать что за случай у вас там такой, по которому вы не могли понять стектрейс.
Пример:
Class1. var result = await class2.Get();
Class2. return class3.Get();
Class3. Бросает ошибку.
Стектрейс будет вида:
Возник эксепшен некоторого типа.
в class3.Get()
в class1.Method()
По цепочке вызова можно понять что и где произошло. Можно понять прочитав тип и сообщение ошибки. Если у вас и возниакют проблемы, то уж извините, но скорее всего проблема в вашем коде…
P.S. Расскажите о своём опыте, пожалуйста, возможно я ошибаюсь.
Сталкивался с такой ситуацией https://dotnetfiddle.net/EsWoQL. Тут по трейсу не поймешь через какой метод прошло выполнение.
Но как вывод для себя:
Использовать return await…; повсеместно не стоит, т.к. проблема не понять стектрейс — крайне мала, а создавать на каждый чих конечный автомат — не лучшая идея, с точки зрения производительности. Если писать красивый и понятный код, следовать банальным принципам в программирвоании — то проблема с непонимание сткетрейса пропадёт.
alhimik45, спасибо за пример!
создавать на каждый чих конечный автомат — не лучшая идея, с точки зрения производительности
Один запрос к БД нивелирует эти наносекунды выигрыша. Всё таки слишком уж premature optimization сразу лепить return Task и потенциально получать проблемы с разбором стектрейсов и дальнейшем написанием try-catch/using. Да и при наличии проблем с производительностью этот финт точно не в числе первых применяемых должен быть.
Используйте Task.WaitAll, чтобы дождаться завершения всех задач.
Если, есть несколько тасков и нужно из каждого из них получить результат, то я бы не рекомендовал использовать Task.WaitAll, так как во многих случаях применение этой функции избыточно и ведет к более громоздкому коду.
Вот такого подхода обычно бывает достаточно:
Task<string> nameRequest = GetNameAsync();
Task<int> ageRequest = GetAgeAsync();
//Таски работают параллельно
string name = await nameRequest ;
int age = await ageRequest;
так как во многих случаях применение этой функции избыточно и ведет к более громоздкому коду.
Ответь на пацана, что код приведенный выше менее громоздкой чем:
var nameRequest = GetNameAsync();
var ageRequest = GetAgeAsync();
Task.WaitAll(nameRequest,ageRequest);
Или такой:
Task.WaitAll(GetNameAsync(),GetAgeAsync());
Когда используется Task.ConfigureAwait(false), код больше не пытается возобновить с того места, где он был раньшеЧто значит «не пытается возобновить»? Что-то вы не то говорите.
Если метод асинхронный, добавьте суффикс Async к его имениЭто имеет смысл, когда в классе/интерфейсе есть такие же синхронные методы, но бездумное добавление суффикса Async просто загрязняет код.
//Проверка запрошена ли отмена
if (ct != null)
Но CancellationToken — это структура и она всегда будет не null. Или я где-то ошибся?
1. Отличный FAQ по ConfigureAwait — devblogs.microsoft.com/dotnet/configureawait-faq
2. Если асинхронные вызовы внутри метода поддерживают CancellationToken — обязательно добавляйте
, CancellationToken cancellationToken = default
последним параметром метода и передавайте его внутрь этих методов.3. Для асинхронных методов интерфейсов всегда указывайте
, CancellationToken cancellationToken = default
как последний параметр.4. При отмене с помощью CancellationToken ловить исключение отмены нужно с помощью OperationCanceledException — это базовый класс для всех исключений после отмены.
5. Если нужно добавить тайм-аут или отмену, используйте CancellationTokenSource в дополнение к уже переданному токену-аргументу с помощью такого кода:
using var registration = cancellationToken.Register(() => cancellationTokenSource.Cancel());
Этот код также отменит регистрацию после вызова Dispose()
6. В сложных асинхронных классах имеет смысл добавлять реализацию
IAsyncDisposable.DisposeAsync()
, если нужно что-то ждать для очищения ресурсов.Использование возможно с помощью конструкции await using вместо обычного using.
await using var registration = cancellationTokenSource.Token.Register(() => completionSource.TrySetCanceled());
Работа, связанная с процессором: ваш код будет выполнять сложные вычисления. В этом случае вы должны использовать Async/Await, но запустить работу нужно в другом потоке с помощью Task.Run
А зачем делать CPU-bound операции асинхронными, все равно процессор будет занят именно этой операцией? Единственное, что приходит на ум — десктопное приложение, но это же только частный случай
Потому что вызывающий код будет ожидать, что ваш метод вернет управление быстро.
Например, там может быть написано что-то такое:
await Task.WhenAll(Enumerable.Range(0, n).Select(i => FooAsync(i)));
Если FooAsync решит по-считать что-то тяжелое в том же потоке — этот код отработает совершенно не так как задумывалось.
Task FooAsync(int i)
{
return Task.Run(() =>
{
// cpu-bound операция, зависящая от i
});
}
Я не понял, что вы мне хотели сказать об асинхронности, приведя пример распараллеливания. В данном случае на каждый вызов числа из коллекции от 0 до n будет браться поток из пула и выполнение программы ускориться. Но если мы возьмем однократный вызов данного метода, это не даст никаких преимуществ, только лишний поток из пула будет выделен.
Лучше просто оставить так
void Foo(int i)
{
// cpu-bound операция, зависящая от i
}
Вызвавшему потокe нужен результат выполнения, и ему все равно, сам он выполнит эту операцию или другой поток (в случае с десктопным приложением, действительно, будет предпочтительнее выделение потока, чтобы поток UI не блокировался). Асинхронная операция подразумевает, что процессору нечего делать, и пускай он другим чем-нибудь займется, а не будет просто ждать. В случае с cpu-bound процессору как раз есть чем заниматься
В двух словах: Лучшие практики Async/Await в .NET