День триста двадцать первый. #AsyncAwaitFAQ
FAQ по async/await и ConfigureAwait
Async/await были добавлены в .NET более 7 лет назад. За это время было выпущено множество улучшений в инфраструктуре, дополнительных языковых конструкций, API-интерфейсов. Однако один из аспектов async/await, который продолжает вызывать вопросы, - это
1. Что такое контекст синхронизации?
Документация по
В 99,9% случаев
В Windows Forms переопределённый метод
WPF имеет свой собственный производный от
В Windows RunTime (WinRT) переопределённый метод
Это выходит за рамки простого «запуска этого делегата в UI-потоке». Любой может реализовать
Преимуществом такого подхода в том, что он предоставляет единый API, который можно использовать для постановки в очередь делегата для обработки, как того пожелает создатель реализации, без необходимости знать детали этой реализации. Итак, если я пишу библиотеку, и я хочу асинхронно выполнить некоторую работу, а затем поставить делегат в очередь обратно в «контекст» исходного местоположения, мне просто нужно захватить
Источник: https://devblogs.microsoft.com/dotnet/configureawait-faq/
FAQ по async/await и ConfigureAwait
Async/await были добавлены в .NET более 7 лет назад. За это время было выпущено множество улучшений в инфраструктуре, дополнительных языковых конструкций, API-интерфейсов. Однако один из аспектов async/await, который продолжает вызывать вопросы, - это
ConfigureAwait
. В серии постов #AsyncAwaitFAQ мы ответим на несколько наиболее часто задаваемых вопросов про контекст синхронизации в async/await.1. Что такое контекст синхронизации?
Документация по
System.Threading.SynchronizationContext
утверждает, что он «обеспечивает базовую функциональность для распространения контекста синхронизации в различных моделях синхронизации». Не совсем понятное описание.В 99,9% случаев
SynchronizationContext
- это тип, который предоставляет виртуальный метод Post
, принимающий делегат для асинхронного выполнения (в SynchronizationContext
есть множество других виртуальных членов, но они гораздо реже используются). Метод Post
базового типа просто вызывает ThreadPool.QueueUserWorkItem
для асинхронного вызова предоставленного делегата. Однако производные типы переопределяют метод Post
, чтобы разрешить выполнение этого делегата в наиболее подходящем месте и в наиболее подходящее время.В Windows Forms переопределённый метод
Post
, создаёт эквивалент Control.BeginInvoke
. То есть любые вызовы метода Post
приведут к тому, что делегат будет вызван позже в потоке, связанном с этим элементом управления, т.е. в UI-потоке. Windows Forms полагается на обработку сообщений Win32 и имеет «цикл сообщений», работающий в UI-потоке, который просто ожидает поступления новых сообщений для обработки (движения и щелчки мыши, ввод с клавиатур, системные события, делегаты и т. д.). Таким образом, имея экземпляр SynchronizationContext
для UI-потока приложения Windows Forms, чтобы получить делегат для выполнения в UI-потоке, просто нужно передать его в Post
.WPF имеет свой собственный производный от
SynchronizationContext
тип с переопределённым Post
, который аналогично «маршализирует» делегат в UI-поток (через Dispatcher.BeginInvoke
), в данном случае управляемый диспетчером WPF, а не элементом управления Windows Forms.В Windows RunTime (WinRT) переопределённый метод
Post
, также ставит делегат в очередь UI-потока через CoreDispatcher
.Это выходит за рамки простого «запуска этого делегата в UI-потоке». Любой может реализовать
SynchronizationContext
с методом Post
, который делает всё что угодно. Преимуществом такого подхода в том, что он предоставляет единый API, который можно использовать для постановки в очередь делегата для обработки, как того пожелает создатель реализации, без необходимости знать детали этой реализации. Итак, если я пишу библиотеку, и я хочу асинхронно выполнить некоторую работу, а затем поставить делегат в очередь обратно в «контекст» исходного местоположения, мне просто нужно захватить
SynchronizationContext
, сохранить его, а затем, когда я закончу свою работу, вызвать Post
в этом контексте и передать делегат, который я хочу вызвать. Мне не нужно знать, что для Windows Forms я должен взять Control
и использовать его BeginInvoke
, или для WPF я должен взять Dispatcher
и использовать его BeginInvoke
, и т.п. Мне просто нужно взять текущий SynchronizationContext
и использовать его позже. Чтобы достичь этого, SynchronizationContext
предоставляет свойство Current
, так что для достижения вышеупомянутой цели можно написать такой код:public void DoWork(Action worker, Action completion)Платформа, которая хочет предоставить свой контекст в свойстве
{
SynchronizationContext sc = SynchronizationContext.Current;
ThreadPool.QueueUserWorkItem(_ =>
{
try { worker(); }
finally { sc.Post(_ => completion(), null); }
});
}
Current
, использует метод SynchronizationContext.SetSynchronizationContext
.Источник: https://devblogs.microsoft.com/dotnet/configureawait-faq/
👍1
День триста двадцать третий. #AsyncAwaitFAQ
FAQ по async/await и ConfigureAwait
2. Что такое планировщик задач?
Планировщик задач (
Когда задачи представляют из себя делегаты, которые могут быть поставлены в очередь и выполнены, они связываются с
Класс
Как и
Источник: https://devblogs.microsoft.com/dotnet/configureawait-faq/
FAQ по async/await и ConfigureAwait
2. Что такое планировщик задач?
Планировщик задач (
TaskScheduler
) представляет собой объект, выполняющий низкоуровневую работу с очередями в потоках. Он гарантирует, что работа в задаче рано или поздно будет выполнена.Когда задачи представляют из себя делегаты, которые могут быть поставлены в очередь и выполнены, они связываются с
System.Threading.Tasks.TaskScheduler
. Так же, как SynchronizationContext
предоставляет виртуальный метод Post
для постановки в очередь вызова делегата (с реализацией, позже вызывающей делегат с помощью типичных механизмов вызова делегата), TaskScheduler
предоставляет абстрактный метод QueueTask
(с реализацией, позже вызывающей эту задачу через метод ExecuteTask
).Класс
TaskScheduler
также служит точкой расширения для всей настраиваемой логики планирования. Планировщик по умолчанию, возвращаемый TaskScheduler.Default
, является пулом потоков, но можно унаследовать от TaskScheduler
и переопределить соответствующие методы для изменения того, когда и где вызывается Task
. Например, в базовых библиотеках есть тип System.Threading.Tasks.ConcurrentExclusiveSchedulerPair
. Экземпляр этого класса предоставляет два свойства типа TaskScheduler
: ExclusiveScheduler
и ConcurrentScheduler
. Задачи, запланированные через ConcurrentScheduler
, могут выполняться одновременно, но до определённого предела, назначенного ConcurrentExclusiveSchedulerPair
при его создании. Но никакие задачи ConcurrentScheduler
не будут выполняться, когда выполняется задача, запланированная для ExclusiveScheduler
, в котором только одна задача может выполняться в определённый момент времени. Таким образом, поведение ExclusiveScheduler
очень похоже на блокировку чтения/записи.Как и
SynchronizationContext
, TaskScheduler
также имеет свойство Current
, которое возвращает «текущий» TaskScheduler
. Однако, в отличие от SynchronizationContext
, здесь нет способа установки текущего планировщика. Вместо этого текущий планировщик - тот, который связан с текущей выполняющейся задачей, и он предоставляется системе как часть процесса запуска задачи. Так, например, следующая программа выведет «True», так как лямбда-выражение в StartNew
выполняется в ExclusiveScheduler
объекта ConcurrentExclusiveSchedulerPair
, и оно «увидит», что на этот планировщик установлен TaskScheduler.Current
:using System;
using System.Threading.Tasks;
class Program {
static void Main() {
var cesp = new ConcurrentExclusiveSchedulerPair();
Task.Factory.StartNew(() => {
Console.WriteLine(TaskScheduler.Current == cesp.ExclusiveScheduler);
}, default, TaskCreationOptions.None, cesp.ExclusiveScheduler).Wait();
}
}
TaskScheduler
также предоставляет статический метод FromCurrentSynchronizationContext
, возвращающий новый TaskScheduler
, связанный с SynchronizationContext.Current
. Все экземпляры Task
, поставленные в очередь возвращённого планировщика, будут выполнены с помощью вызова метода Post в этом контексте.Источник: https://devblogs.microsoft.com/dotnet/configureawait-faq/
День триста двадцать пятый. #AsyncAwaitFAQ
FAQ по async/await и ConfigureAwait
3. Как SynchronizationContext и TaskScheduler связаны с await?
Представьте кнопку в приложении. Нажав на кнопку, мы хотим загрузить текст с веб-сайта и установить его в качестве текста кнопки. Доступ к кнопке возможен только из UI-потока, которому она принадлежит, поэтому, когда мы успешно загрузили новый текст и хотим установить его как текст кнопки, мы должны сделать это из потока, которому принадлежит элемент управления (из UI-потока). Если мы этого не сделаем, мы получим исключение:
Когда вы ожидаете чего-либо в C#, компилятор использует паттерн awaitable и просит «ожидаемый» (awaitable) объект (в данном случае
Источник: https://devblogs.microsoft.com/dotnet/configureawait-faq/
FAQ по async/await и ConfigureAwait
3. Как SynchronizationContext и TaskScheduler связаны с await?
Представьте кнопку в приложении. Нажав на кнопку, мы хотим загрузить текст с веб-сайта и установить его в качестве текста кнопки. Доступ к кнопке возможен только из UI-потока, которому она принадлежит, поэтому, когда мы успешно загрузили новый текст и хотим установить его как текст кнопки, мы должны сделать это из потока, которому принадлежит элемент управления (из UI-потока). Если мы этого не сделаем, мы получим исключение:
System.InvalidOperationException: 'The calling thread cannot access this object because a different thread owns it.' (Вызывающий поток не может получить доступ к этому объекту, поскольку он принадлежит другому потоку).Если бы мы писали это вручную, мы могли бы использовать
SynchronizationContext
, чтобы выполнить установку текста в исходном контексте, например через TaskScheduler
:private static readonly HttpClient сlient = new HttpClient();Либо можно напрямую использовать
private void downloadBtn_Click(object sender, RoutedEventArgs e)
{
сlient.GetStringAsync("http://example.com/gettext")
.ContinueWith(downloadTask => {
downloadBtn.Text = downloadTask.Result;
}, TaskScheduler.FromCurrentSynchronizationContext());
}
SynchronizationContext
: private void downloadBtn_Click(object sender, RoutedEventArgs e)Однако оба этих подхода явно используют функции обратного вызова. Вместо этого мы можем написать код естественным образом с помощью async/await:
{
SynchronizationContext sc = SynchronizationContext.Current;
сlient.GetStringAsync("http://example.com/gettext")
.ContinueWith(downloadTask => {
sc.Post(delegate {
downloadBtn.Text = downloadTask.Result;
}, null);
});
}
private async void downloadBtn_Click(object sender, RoutedEventArgs e)Этот код «просто работает», успешно устанавливая текст в UI-потоке, потому что, как и в случае с вручную реализованными версиями выше, ожидание задачи по умолчанию запоминает текущий контекст (
{
string text = await сlient.GetStringAsync("http://example.com/gettext");
downloadBtn.Text = text;
}
SynchronizationContext.Current
) или текущий планировщик (TaskScheduler.Current
). Когда вы ожидаете чего-либо в C#, компилятор использует паттерн awaitable и просит «ожидаемый» (awaitable) объект (в данном случае
Task
) предоставить ему объект «ожидателя» (awaiter) – в данном случае TaskAwaiter<string>
. Этот «ожидатель» отвечает за создание метода обратного вызова (часто называемого «продолжением»), который «вернёт выполнение в исходный код» после того, как задача завершит работу. И делает он это с захватом текущего контекста/планировщика. В упрощённом виде это выглядит так: object scheduler = SynchronizationContext.Current;Другими словами, сначала проверяется, установлен ли
if (scheduler is null && TaskScheduler.Current != TaskScheduler.Default)
{
scheduler = TaskScheduler.Current;
}
SynchronizationContext
, и, если нет, установлен ли «нестандартный» TaskScheduler
. Если таким образом контекст/планировщик будет захвачен, он будет использован для выполнения метода обратного вызова соответственно в контексте или через планировщик. В противном случае чаще всего метод обратного вызова выполнится в том же контексте, что и ожидаемая задача.Источник: https://devblogs.microsoft.com/dotnet/configureawait-faq/
День триста двадцать восьмой. #AsyncAwaitFAQ
FAQ по async/await и ConfigureAwait
4. Что делает ConfigureAwait(false)?
Метод
Зачем использовать ConfigureAwait(false)?
Этот метод используется, чтобы избежать принудительного вызова метода обратного вызова в исходном контексте или планировщике. Это даёт несколько преимуществ:
1. Улучшает производительность. Требуется дополнительная работа, чтобы поставить в очередь метод обратного вызова вместо того, чтобы просто вызывать его. Кроме того, некоторые оптимизации времени выполнения, не могут быть использованы. Иногда даже проверка текущих контекста и планировщика (что включает в себя доступ к статическим потокам) могут добавить измеримые накладные расходы. Если код после
2. Позволяет избежать взаимных блокировок. Взаимная блокировка чаще всего возникает при использовании кода, блокирующего текущий поток (при вызове
Зачем использовать ConfigureAwait(true)?
Поскольку захват контекста происходит по умолчанию, то
Источник: https://devblogs.microsoft.com/dotnet/configureawait-faq/
FAQ по async/await и ConfigureAwait
4. Что делает ConfigureAwait(false)?
Метод
ConfigureAwait
, принимающий параметр continueOnCapturedContext
(«продолжитьВЗахваченномКонтексте
»), возвращает структуру ConfiguredTaskAwaitable
, которая содержит специальным образом настроенный «ожидатель» (awaiter). Поскольку await
может использоваться с любым типом, соответствующим паттерну awaitable (не только с Task
напрямую), возврат другого типа вместо задачи позволяет изменить логику захвата контекста/планировщика (см. предыдущий пост) на что-то вроде этого: object scheduler = null;Другими словами, получая значение
if (continueOnCapturedContext)
{
// захват контекста/планировщика
}
false
, объект не захватывает текущий контекст/планировщик, даже если они существуют.Зачем использовать ConfigureAwait(false)?
Этот метод используется, чтобы избежать принудительного вызова метода обратного вызова в исходном контексте или планировщике. Это даёт несколько преимуществ:
1. Улучшает производительность. Требуется дополнительная работа, чтобы поставить в очередь метод обратного вызова вместо того, чтобы просто вызывать его. Кроме того, некоторые оптимизации времени выполнения, не могут быть использованы. Иногда даже проверка текущих контекста и планировщика (что включает в себя доступ к статическим потокам) могут добавить измеримые накладные расходы. Если код после
await
фактически не требует запуска в исходном контексте, использование ConfigureAwait(false)
поможет избежать всех этих затрат.2. Позволяет избежать взаимных блокировок. Взаимная блокировка чаще всего возникает при использовании кода, блокирующего текущий поток (при вызове
.Wait()
, .Result
или .GetAwaiter().GetResult()
на задаче, внутри которой используется await). Если в текущем контексте явно или неявно ограничено число параллельных операций, либо если код блокирует UI-поток, получается ситуация, когда контекст/поток заблокирован в ожидании завершения асинхронной задачи, а метод обратного вызова этой задачи ставится в очередь для вызова на заблокированном контексте/потоке и поэтому не может быть выполнен (взаимная блокировка). Такая ситуация может возникать, когда ресурсы ограничены любым способом и так или иначе оказываются заняты. Если задача использует ConfigureAwait(false)
, метод обратного вызова не ставится в очередь в исходный контекст, что позволяет избежать сценариев взаимоблокировки.Зачем использовать ConfigureAwait(true)?
Поскольку захват контекста происходит по умолчанию, то
ConfigureAwait(true)
практически никогда не используется, за исключением случаев, когда вы хотите показать, что вы намеренно не используете ConfigureAwait(false)
(например, чтобы отключить предупреждения статического анализа кода). Метод ConfigureAwait
принимает логическое значение, поскольку существуют некоторые нишевые ситуации, в которых вы хотите передать переменную для управления конфигурацией. Но в 99% случаев используется жестко закодированное значение false
. Поэтому вызовы ConfigureAwait(true)
можно безболезненно удалять.Источник: https://devblogs.microsoft.com/dotnet/configureawait-faq/
День триста двадцать девятый. #AsyncAwaitFAQ
FAQ по async/await и ConfigureAwait
5. Когда использовать ConfigureAwait(false)?
Это зависит от того, реализуете ли вы код уровня приложения или код библиотеки общего назначения. При написании приложений обычно предпочтительно поведение по умолчанию. Если модель приложения или среда (например, Windows Forms, WPF, ASP.NET Core и т. д.) публикует собственный
И наоборот, библиотеки общего назначения являются «универсальными» отчасти потому, что их не волнует среда, в которой они используются. Вы можете использовать их в веб-приложении, в клиентском приложении или в тесте, это не имеет значения, поскольку код библиотеки не зависит от модели приложения, в которой он может быть использован. Эта независимость также означает, что код не собирается делать что-то, что должно взаимодействовать с моделью приложения строго определённым образом. Например, он не будет получать доступ к элементам управления UI, потому что библиотека общего назначения ничего не знает о них. Поскольку отпадает необходимость запускать код в какой-либо конкретной среде, мы можем избежать принудительного возврата продолжений/обратных вызовов в исходный контекст через
В любых правилах, конечно, могут быть исключения. Например, стоит обдумать решение для библиотек общего назначения с API, принимающим делегаты. В таких случаях пользователь библиотеки передаёт для вызова в библиотеке код приложения. Рассмотрим, например, асинхронную версию LINQ метода
Гарантирует ли ConfigureAwait(false), что обратный вызов не будет выполняться в исходном контексте?
Нет. Он гарантирует, что он не будет поставлен в очередь в исходный контекст… но это не означает, что код после
Источник: https://devblogs.microsoft.com/dotnet/configureawait-faq/
FAQ по async/await и ConfigureAwait
5. Когда использовать ConfigureAwait(false)?
Это зависит от того, реализуете ли вы код уровня приложения или код библиотеки общего назначения. При написании приложений обычно предпочтительно поведение по умолчанию. Если модель приложения или среда (например, Windows Forms, WPF, ASP.NET Core и т. д.) публикует собственный
SynchronizationContext
, почти наверняка для этого есть веская причина: контекст позволяет коду правильно взаимодействовать с моделью приложения/средой. Поэтому, если вы пишете обработчик событий в приложении Windows Forms, модульный тест в xunit, код в контроллере ASP.NET MVC, то независимо от того, опубликовала ли модель приложения SynchronizationContext
, вы предпочли бы использовать его, если он существует. Поэтому по умолчанию используется ConfigureAwait(true)
. Вы просто используете await
, и обратные вызовы/продолжения отправляются обратно в исходный контекст, как и должны. Общее правило: если вы пишете код уровня приложения, не используйте ConfigureAwait(false)
. Если вернуться к обработчику события Click в примере с кнопкой, то кодdownloadBtn.Text = text;должен быть выполнен в исходном контексте. В этом примере вызов асинхронного кода с
ConfigureAwait(false)приводил бы к ошибке. То же самое относится и к коду в классическом приложении ASP.NET, зависящем от
HttpContext.Current
. Использование ConfigureAwait(false)
, а затем попытка использования HttpContext.Current
скорее всего приведёт к проблемам.И наоборот, библиотеки общего назначения являются «универсальными» отчасти потому, что их не волнует среда, в которой они используются. Вы можете использовать их в веб-приложении, в клиентском приложении или в тесте, это не имеет значения, поскольку код библиотеки не зависит от модели приложения, в которой он может быть использован. Эта независимость также означает, что код не собирается делать что-то, что должно взаимодействовать с моделью приложения строго определённым образом. Например, он не будет получать доступ к элементам управления UI, потому что библиотека общего назначения ничего не знает о них. Поскольку отпадает необходимость запускать код в какой-либо конкретной среде, мы можем избежать принудительного возврата продолжений/обратных вызовов в исходный контекст через
ConfigureAwait(false)
, получая преимущества как в производительности, так и в надёжности. Здесь общее правило такое: если вы пишете код библиотеки общего назначения, используйте ConfigureAwait(false)
. Вот почему, например, практически любое (за некоторыми исключениями) ожидание в библиотеках среды выполнения .NET Core использует ConfigureAwait(false)
.В любых правилах, конечно, могут быть исключения. Например, стоит обдумать решение для библиотек общего назначения с API, принимающим делегаты. В таких случаях пользователь библиотеки передаёт для вызова в библиотеке код приложения. Рассмотрим, например, асинхронную версию LINQ метода
Where
:public static async IAsyncEnumerable<T> WhereAsync(this IAsyncEnumerable<T> source, Func<T, bool> predicate);Нужно ли здесь вызывать predicate в исходном
SynchronizationContext
вызывающей стороны? Это зависит от реализации WhereAsync
. По этой причине можно отказаться от использования ConfigureAwait(false)
.Гарантирует ли ConfigureAwait(false), что обратный вызов не будет выполняться в исходном контексте?
Нет. Он гарантирует, что он не будет поставлен в очередь в исходный контекст… но это не означает, что код после
await task.ConfigureAwait(false)не будет исполнен в исходном контексте. Это связано с тем, что код, расположенный после уже завершённых задач, просто продолжает исполняться синхронно, а не ставится в продолжение/обратный вызов. Таким образом, если вы ожидаете задачу, которая уже выполнена к тому времени, когда вызывается await, независимо от того, использовали ли вы
ConfigureAwait(false)
, последующий код сразу продолжит выполняться в текущем потоке, в текущем контексте.Источник: https://devblogs.microsoft.com/dotnet/configureawait-faq/
День триста тридцатый. #AsyncAwaitFAQ
FAQ по async/await и ConfigureAwait
6. Можно ли использовать ConfigureAwait(false) только в первом ожидании методе, но не в остальных?
В общем случае нет. Смотрите предыдущий пост. Если ожидание
Исключением является ситуация, когда вы точно знаете, что первое ожидание всегда завершается асинхронно, а обратному вызову задачи не нужен текущий контекст и планировщик задач. Например,
Можно ли использовать Task.Run, чтобы избежать использования ConfigureAwait(false)?
Да. В следующем коде
Также заметим, что при этом подходе необходимо создавать/ставить в очередь дополнительный объект задачи. Это может иметь или не иметь значения для вашего приложения или библиотеки в зависимости от того, критична ли для вас производительность.
Кроме того, имейте в виду, что такие уловки могут вызвать больше проблем, чем принесут пользы, и иметь другие непредвиденные последствия. Например, инструменты статического анализа (например, анализаторы Roslyn) были написаны для отметки ожиданий, которые не используют
Источник: https://devblogs.microsoft.com/dotnet/configureawait-faq/
FAQ по async/await и ConfigureAwait
6. Можно ли использовать ConfigureAwait(false) только в первом ожидании методе, но не в остальных?
В общем случае нет. Смотрите предыдущий пост. Если ожидание
await task.ConfigureAwait(false)включает в себя задачу, которая уже завершена (что на самом деле случается очень часто), то
ConfigureAwait(false)
будет бессмысленным, и поток продолжит выполнять код метода дальше в том же контексте.Исключением является ситуация, когда вы точно знаете, что первое ожидание всегда завершается асинхронно, а обратному вызову задачи не нужен текущий контекст и планировщик задач. Например,
CryptoStream
в библиотеках .NET Runtime хочет, чтобы его потенциально требовательный к вычислительным ресурсам код не выполнялся как часть синхронного вызова вызывающей стороны, поэтому он использует настраиваемый ожидатель, чтобы весь код после первого ожидания выполнялся в потоке из пула. Однако даже в этом случае можно заметить, что в следующем ожидании всё равно используется ConfigureAwait(false)
. Технически это не является необходимым, но это делает обзор кода намного проще, так как при чтении этого кода не нужно ломать голову, почему в остальных случаях нет вызова ConfigureAwait(false)
.Можно ли использовать Task.Run, чтобы избежать использования ConfigureAwait(false)?
Да. В следующем коде
Task.Run(async delegate {использование
await SomethingAsync();
});
ConfigureAwait(false)
для SomethingAsync
не имеет смысла, т.к. делегат, переданный в Task.Run
, будет выполняться в потоке из пула без учёта контекста. Кроме того, Task.Run
неявно использует TaskScheduler.Default
, что означает, что запрос TaskScheduler.Current
внутри делегата также возвратит Default
. Это означает, что ожидание будет демонстрировать одинаковое поведение независимо от того, использовался ли ConfigureAwait(false)
. Кроме того, нет никаких гарантий относительно того, что может делать код внутри этого делегата. Если у вас есть код:Task.Run(async delegateтогда код внутри
{
SynchronizationContext.SetSynchronizationContext(new SomeCoolSyncCtx());
await SomethingAsync();
});
SomethingAsync
на самом деле увидит в SynchronizationContext.Current
экземпляр SomeCoolSyncCtx
, и как это ожидание, так и любые ненастроенные ожидания внутри SomethingAsync
будут отправлять обратные вызовы в этот контекст. Поэтому, чтобы использовать этот подход, вам необходимо понимать, что может и что не может делать код, который вы ставите в очередь, и могут ли его действия что-то испортить.Также заметим, что при этом подходе необходимо создавать/ставить в очередь дополнительный объект задачи. Это может иметь или не иметь значения для вашего приложения или библиотеки в зависимости от того, критична ли для вас производительность.
Кроме того, имейте в виду, что такие уловки могут вызвать больше проблем, чем принесут пользы, и иметь другие непредвиденные последствия. Например, инструменты статического анализа (например, анализаторы Roslyn) были написаны для отметки ожиданий, которые не используют
ConfigureAwait(false)
, предупреждением CA2007. Если вы включили такой анализатор, но затем применили хитрость, подобную этой, просто чтобы избежать использования ConfigureAwait
, есть большая вероятность, что анализатор всё равно пометит его предупреждением.Источник: https://devblogs.microsoft.com/dotnet/configureawait-faq/
День триста тридцать первый. #AsyncAwaitFAQ
FAQ по async/await и ConfigureAwait
7. Нужно ли использовать ConfigureAwait(false) при вызове GetAwaiter().GetResult()?
Нет.
Говорят, что ConfigureAwait(false) больше не требуется в .NET Core. Это правда?
Нет. Он необходим при работе в .NET Core по тем же причинам, что и при работе в .NET Framework. В этом отношении ничего не изменилось.
Однако изменилось поведение некоторых сред в части публикации собственного контекста. Например, если ASP.NET в .NET Framework имеет свой собственный
Но это не означает, что пользовательский
Можно ли использовать ConfigureAwait в await foreach для IAsyncEnumerable?
Да.
Можно ли использовать ConfigureAwait в await using для IAsyncDisposable?
Да, хотя есть небольшой нюанс. Как и в случае
Источник: https://devblogs.microsoft.com/dotnet/configureawait-faq/
FAQ по async/await и ConfigureAwait
7. Нужно ли использовать ConfigureAwait(false) при вызове GetAwaiter().GetResult()?
Нет.
ConfigureAwait
влияет только на обратные вызовы. В частности, паттерн awaiter требует, чтобы ожидатели предоставили свойство IsCompleted
, метод GetResult
и метод OnCompleted
. ConfigureAwait влияет только на поведение OnCompleted
, поэтому, если вы просто напрямую вызываете метод GetResult()
разницы в поведении не будет. Поэтому можно заменитьtask.ConfigureAwait(false).GetAwaiter().GetResult();на
task.GetAwaiter().GetResult();если, конечно, вы действительно хотите блокировать текущий поток.
Говорят, что ConfigureAwait(false) больше не требуется в .NET Core. Это правда?
Нет. Он необходим при работе в .NET Core по тем же причинам, что и при работе в .NET Framework. В этом отношении ничего не изменилось.
Однако изменилось поведение некоторых сред в части публикации собственного контекста. Например, если ASP.NET в .NET Framework имеет свой собственный
SynchronizationContext
, то в ASP.NET Core нет. Это означает, что код, работающий в приложении ASP.NET Core по умолчанию, не увидит пользовательский SynchronizationContext
, что исключает необходимость вызова ConfigureAwait(false)
в такой среде.Но это не означает, что пользовательский
SynchronizationContext
или TaskScheduler
никогда не будут предоставлены. Если какой-либо пользовательский код (или код библиотеки), используемый вашим приложением, задаёт пользовательский контекст, то даже в ASP.NET Core ваши вызовы await могут захватывать контекст или планировщик, что может повлечь необходимость использования ConfigureAwait(false)
.Можно ли использовать ConfigureAwait в await foreach для IAsyncEnumerable?
Да.
await foreach
работает с шаблоном, поэтому он может использоваться для перечисления не только IAsyncEnumerable<T>
, но и всего, что соответствует шаблону. Библиотеки .NET Runtime включают метод расширения ConfigureAwait
для IAsyncEnumerable<T>
, который возвращает сконфигурированную структуру, соответствующую шаблону. Когда компилятор генерирует вызовы методов MoveNextAsync
и DisposeAsync
перечислителя, структура выполняет ожидание желаемым способом.Можно ли использовать ConfigureAwait в await using для IAsyncDisposable?
Да, хотя есть небольшой нюанс. Как и в случае
IAsyncEnumerable<T>
, библиотеки .NET Runtime предоставляют метод расширения ConfigureAwait
для IAsyncDisposable
. И await using
будет успешно работать с ним, поскольку он реализует соответствующий шаблон (предоставляет метод DisposeAsync
):await using (var c = new MyAsyncDisposableClass().ConfigureAwait(false)) {…}Проблема здесь в том, что тип c теперь не
MyAsyncDisposableClass
, а System.Runtime.CompilerServices.ConfiguredAsyncDisposable
, возвращаемый из метода расширения ConfigureAwait
. Чтобы обойти это, вам нужно написать одну дополнительную строку:var c = new MyAsyncDisposableClass();Это расширяет область действия
await using (c.ConfigureAwait(false)){…}
c
, и, если это критично, можно обернуть всё это в фигурные скобки.Источник: https://devblogs.microsoft.com/dotnet/configureawait-faq/
День четыреста восемьдесят пятый. #PerformanceTips
Лучшие Практики по Производительности в C#. Начало
Недавно нашёл статью со списком «шаблонов кода, которых следует избегать, потому что они плохо работают в смысле производительности». Автор пишет, что все пункты в списке так или иначе вызывали проблемы с производительностью. Хотя, стоит оговориться, что не все советы могут работать во всех случаях.
1. Синхронное ожидание асинхронного кода
Не ожидайте синхронно незавершённых задач. Включая, но не ограничиваясь:
2. ConfigureAwait
Если ваш код может быть исполнен без захвата контекста синхронизации, используйте
3. async void
Никогда не используйте
По привычке вы можете написать:
Продолжение следует…
Источник: https://medium.com/@kevingosse/performance-best-practices-in-c-b85a47bdd93a
Лучшие Практики по Производительности в C#. Начало
Недавно нашёл статью со списком «шаблонов кода, которых следует избегать, потому что они плохо работают в смысле производительности». Автор пишет, что все пункты в списке так или иначе вызывали проблемы с производительностью. Хотя, стоит оговориться, что не все советы могут работать во всех случаях.
1. Синхронное ожидание асинхронного кода
Не ожидайте синхронно незавершённых задач. Включая, но не ограничиваясь:
Task.Wait
, Task.Result
, Task.GetAwaiter().GetResult()
, Task.WaitAny
, Task.WaitAll
. Более общий совет: любая синхронная зависимость между двумя потоками пула может вызвать истощение пула потоков. 2. ConfigureAwait
Если ваш код может быть исполнен без захвата контекста синхронизации, используйте
ConfigureAwait(false)
для каждого вызова await
. Однако обратите внимание, что ConfigureAwait
имеет смысл только при использовании ключевого слова await
. Например, этот код не имеет смысла:var result = ProcessAsync().ConfigureAwait(false).GetAwaiter().GetResult();Подробнее о
ConfigureAwait
в серии постов с тегом #AsyncAwaitFAQ3. async void
Никогда не используйте
async void
. Исключение, выброшенное в таком методе, распространяется в контекст синхронизации и обычно приводит к сбою всего приложения. Если вы не можете вернуть задачу в свой метод (например, потому что вы реализуете интерфейс), переместите асинхронный код метод-обёртку и вызовите его:interface IInterface {4. По возможности избегайте слова async
void DoSomething();
}
class Implementation : IInterface {
public void DoSomething() {
_ = DoSomethingAsync();
}
private async Task DoSomethingAsync() {
await Task.Delay(100);
}
}
По привычке вы можете написать:
public async Task CallAsync() {Хотя код семантически корректен, использование ключевого слова
var client = new Client();
return await client.GetAsync();
}
async
здесь не требуется и может привести к значительным накладным расходам в «горячих путях». Попробуйте удалить его, если это возможно:public Task CallAsync() {Однако имейте в виду, что вы не можете использовать эту оптимизацию, когда ваш код упакован в блоки (например,
var client = new Client();
return client.GetAsync();
}
try
/catch
или using
):public async Task Correct() {В методе
using (var client = new Client()) {
return await client.GetAsync();
}
}
public Task Incorrect() {
using (var client = new Client()) {
return client.GetAsync();
}
}
Incorrect
, поскольку задача не ожидается внутри блока using
, клиент может быть удален до завершения вызова GetAsync
.Продолжение следует…
Источник: https://medium.com/@kevingosse/performance-best-practices-in-c-b85a47bdd93a
День шестьсот двадцать восьмой. #ЗаметкиНаПолях #AsyncAwaitFAQ
Самые Частые Ошибки при Работе с async/await. Начало 1-4
1. Лишний async/await
Использование ключевых слов async/await приводит к неявному выделению памяти под конечный автомат, который отвечает за организацию асинхронных вызовов. Когда ожидаемое выражение является единственным или последним оператором в функции, его можно пропустить. Однако с этим есть несколько проблем, о которых вам следует знать.
❌ Неверно
Если мы используем асинхронный код, мы всегда должны использовать асинхронные методы, если они существуют. Многие API предлагают асинхронные аналоги своих хорошо известных синхронных методов.
❌ Неверно
Есть две причины, по которым
- Вызывающий код не может ожидать метод
- Невозможно обработать исключение, вызванное этим методом. Если возникает исключение, ваш процесс падает!
Вы всегда должны использовать
❌ Неверно
Если передать асинхронную лямбда-функцию в качестве аргумента типа
- изменить тип параметра с
- реализовать этот делегат синхронно.
Стоит отметить, что некоторые API-интерфейсы уже предоставляют аналоги своих методов, которые принимают
Источник: https://cezarypiatek.github.io/post/async-analyzers-p1/
Самые Частые Ошибки при Работе с async/await. Начало 1-4
1. Лишний async/await
Использование ключевых слов async/await приводит к неявному выделению памяти под конечный автомат, который отвечает за организацию асинхронных вызовов. Когда ожидаемое выражение является единственным или последним оператором в функции, его можно пропустить. Однако с этим есть несколько проблем, о которых вам следует знать.
❌ Неверно
async Task DoSomethingAsync(){✅ Верно
await Task.Yield();
}
Task DoSomethingAsync() {2. Вызов синхронного метода внутри асинхронного метода
return Task.Yield();
}
Если мы используем асинхронный код, мы всегда должны использовать асинхронные методы, если они существуют. Многие API предлагают асинхронные аналоги своих хорошо известных синхронных методов.
❌ Неверно
async Task DoAsync(Stream file, byte[] buffer) {✅ Верно
file.Read(buffer, 0, 10);
}
async Task DoAsync(Stream file, byte[] buffer) {3. Использование async void
await file.ReadAsync(buffer, 0, 10);
}
Есть две причины, по которым
async void
вреден:- Вызывающий код не может ожидать метод
async void
.- Невозможно обработать исключение, вызванное этим методом. Если возникает исключение, ваш процесс падает!
Вы всегда должны использовать
async Task
вместо async void
, если только это не обработчик событий, но тогда вы должны гарантировать себе, что метод не выбросит исключения.❌ Неверно
async void DoSomethingAsync() {✅ Верно
await Task.Yield();
}
async Task DoSomethingAsync() {4. Неподдерживаемые асинхронные делегаты
await Task.Yield();
}
Если передать асинхронную лямбда-функцию в качестве аргумента типа
Action
, компилятор сгенерирует метод async void
, недостатки которого были описаны выше. Есть два решения этой проблемы:- изменить тип параметра с
Action
на Func<Task>
, - реализовать этот делегат синхронно.
Стоит отметить, что некоторые API-интерфейсы уже предоставляют аналоги своих методов, которые принимают
Func<Task>.
void DoSomething() {
Callback(async() => {
await Task.Yield();
});
}
❌ Неверноvoid Callback(Action action){…}✅ Верно
void Callback(Func<Task> action){…}Продолжение следует…
Источник: https://cezarypiatek.github.io/post/async-analyzers-p1/
👍1
День шестьсот двадцать девятый. #ЗаметкиНаПолях #AsyncAwaitFAQ
Самые Частые Ошибки при Работе с async/await. Продолжение 5-7
Начало
5. Не ожидать Task в выражении using
❌ Неверно
Если мы пропустим ключевое слово
❌ Неверно
Отсутствие ключевого слова await перед асинхронной операцией приведет к завершению метода до завершения данной асинхронной операции. Метод будет вести себя непредсказуемо, и результат будет отличаться от ожиданий. Ещё хуже, если неожидаемое выражение выбросит исключение. Оно остается незамеченным и не вызовет сбоя процесса, что ещё больше затруднит его обнаружение. Вы всегда должны ожидать асинхронное выражение или присвоить возвращаемую задачу переменной и убедиться, что в итоге она где-то ожидается.
❌ Неверно
Источник: https://cezarypiatek.github.io/post/async-analyzers-p1/
Самые Частые Ошибки при Работе с async/await. Продолжение 5-7
Начало
5. Не ожидать Task в выражении using
System.Threading.Tasks.Task
реализует интерфейс IDisposable
. Вызов метода, возвращающего Task
, непосредственно в выражении using приводит к удалению задачи в конце блока using
, что никогда не является ожидаемым поведением.❌ Неверно
using (CreateDisposableAsync()) {✅ Верно
…
}
using (await CreateDisposableAsync()) {6. Не ожидать Task в блоке using
…
}
Если мы пропустим ключевое слово
await
для асинхронной операции внутри блока using
, то объект может быть удалён до завершения асинхронного вызова. Это приведёт к неправильному поведению и очень часто заканчивается исключением времени выполнения, сообщающим о том, что мы пытаемся работать с уже удалённым объектом.❌ Неверно
private Task<int> DoSomething() {✅ Верно
using (var service = CreateService()) {
return service.GetAsync();
}
}
private async Task<int> DoSomething() {7. Не ожидать результата асинхронного метода
using (var service = CreateService()) {
return await service.GetAsync();
}
}
Отсутствие ключевого слова await перед асинхронной операцией приведет к завершению метода до завершения данной асинхронной операции. Метод будет вести себя непредсказуемо, и результат будет отличаться от ожиданий. Ещё хуже, если неожидаемое выражение выбросит исключение. Оно остается незамеченным и не вызовет сбоя процесса, что ещё больше затруднит его обнаружение. Вы всегда должны ожидать асинхронное выражение или присвоить возвращаемую задачу переменной и убедиться, что в итоге она где-то ожидается.
❌ Неверно
async Task DoSomethingAsync() {✅ Верно
await DoSomethingAsync1();
DoSomethingAsync2();
await DoSomethingAsync3();
}
async Task DoSomethingAsync() {Продолжение следует…
await DoSomethingAsync1();
await DoSomethingAsync2();
await DoSomethingAsync3();
}
Источник: https://cezarypiatek.github.io/post/async-analyzers-p1/
День шестьсот тридцатый. #ЗаметкиНаПолях #AsyncAwaitFAQ
Самые Частые Ошибки при Работе с async/await. Продолжение 8-10
Начало
Часть 2
8. Синхронные ожидания
Если вы хотите ожидать асинхронное выражение в синхронном методе, вы должны переписать всю цепочку вызовов на асинхронную. Кажется, что более легкое решение - вызвать
❌ Неверно
По умолчанию, когда мы ожидаем асинхронную операцию, продолжение планируется с использованием захваченного
❌ Неверно
Возврат
❌ Неверно
Источник: https://cezarypiatek.github.io/post/async-analyzers-p2/
Самые Частые Ошибки при Работе с async/await. Продолжение 8-10
Начало
Часть 2
8. Синхронные ожидания
Если вы хотите ожидать асинхронное выражение в синхронном методе, вы должны переписать всю цепочку вызовов на асинхронную. Кажется, что более легкое решение - вызвать
Wait
или Result
для возвращаемой задачи. Но это будет стоить вам заблокированного потока и может привести к взаимной блокировке. Сделайте всю цепочку асинхронной. В консольном приложении допустим асинхронный метод Main, в контроллерах ASP.NET – асинхронные методы действия.❌ Неверно
void DoSomething() {✅ Верно
Thread.Sleep(1);
Task.Delay(2).Wait();
var result1 = GetAsync().Result;
var result2 = GetAsync().GetAwaiter().
GetResult();
var unAwaitedResult3 = GetAsync();
var result3 = unAwaitedResult3.Result;
}
async Task DoSomething() {9. Отсутствие ConfigureAwait(bool)
await Task.Delay(1);
await Task.Delay(2);
var result1 = await GetAsync();
var result2 = await GetAsync();
}
По умолчанию, когда мы ожидаем асинхронную операцию, продолжение планируется с использованием захваченного
SynchronizationContext
или TaskScheduler
. Это приводит к дополнительным затратам и может привести к взаимной блокировке, особенно в WindowsForms, WPF и старых приложениях ASP.NET. Вызывая ConfigureAwait(false)
, мы гарантируем, что текущий контекст игнорируется при вызове продолжения. Подробнее о ConfigureAwait
в серии постов с тегом #AsyncAwaitFAQ❌ Неверно
async Task DoSomethingAsync() {✅ Верно
await DoSomethingElse();
}
async Task DoSomethingAsync() {10. Возврат null из метода, возвращающего Task
await DoSomethingElse().
ConfigureAwait(false);
}
Возврат
null
значения из синхронного метода типа Task
/Task<>
приводит к исключению NullReferenceException
, если кто-то ожидает этот метод. Чтобы этого избежать, всегда возвращайте результат этого метода с помощью помощников Task.CompletedTask
или Task.FromResult<T>(null)
.❌ Неверно
Task DoAsync() {✅ Верно
return null;
}
Task<object> GetSomethingAsync() {
return null;
}
Task<HttpResponseMessage>
TryGetAsync(HttpClient httpClient) {
return httpClient?.
GetAsync("/some/endpoint");
}
Task DoAsync() {Продолжение следует…
return Task.CompletedTask;
}
Task<object> GetSomethingAsync() {
return Task.FromResult<object>(null);
}
Task<HttpResponseMessage>
TryGetAsync(HttpClient httpClient) {
return httpClient?.
GetAsync("/some/endpoint") ??
Task.FromResult(default(
HttpResponseMessage));
}
Источник: https://cezarypiatek.github.io/post/async-analyzers-p2/
День шестьсот тридцать первый. #ЗаметкиНаПолях #AsyncAwaitFAQ
Самые Частые Ошибки при Работе с async/await. Продолжение 11-13
Начало
Часть 2
Часть 3
11. Передача токена отмены
Если вы забудете передать токен отмены, это может привести к большим проблемам. Запущенная длительная операция без токена отмены может заблокировать действие по остановке приложения или привести к нехватке потоков при большом количестве отменённых веб-запросов. Чтобы избежать этого, всегда создавайте и передавайте токен отмены методам, которые его принимают, даже если это необязательный параметр.
❌ Неверно
Эта ошибка похожа на предыдущую, но относится исключительно к
❌ Неверно
И наоборот, имена синхронных методов не должны заканчиваться на
❌ Неверно
Источник: https://cezarypiatek.github.io/post/async-analyzers-p2/
Самые Частые Ошибки при Работе с async/await. Продолжение 11-13
Начало
Часть 2
Часть 3
11. Передача токена отмены
Если вы забудете передать токен отмены, это может привести к большим проблемам. Запущенная длительная операция без токена отмены может заблокировать действие по остановке приложения или привести к нехватке потоков при большом количестве отменённых веб-запросов. Чтобы избежать этого, всегда создавайте и передавайте токен отмены методам, которые его принимают, даже если это необязательный параметр.
❌ Неверно
public async Task<string>✅ Верно
GetSomething(HttpClient http) {
var response = await
http.GetAsync(new Uri("/endpoint/"));
return await
response.Content.ReadAsStringAsync();
}
public async Task<string>12. Использование токена отмены с IAsyncEnumerable
GetSomething(HttpClient http,
CancellationToken ct) {
var response = await
http.GetAsync(new Uri("/endpoint/"),
ct);
return await
response.Content.ReadAsStringAsync();
}
Эта ошибка похожа на предыдущую, но относится исключительно к
IAsyncEnumerable
и её довольно легко допустить. Это может быть неочевидно, но передача токена отмены в асинхронный перечислитель выполняется путем вызова метода WithCancellation()
.❌ Неверно
async Task Iterate(✅ Верно
IAsyncEnumerable<string> e) {
await foreach (var item in e) {
…
}
}
async Task Iterate(13. Имена асинхронных методов должны заканчиваться на Async
IAsyncEnumerable<string> e,
CancellationToken ct) {
await foreach (var item in
e.WithCancellation(ct))
{
…
}
}
И наоборот, имена синхронных методов не должны заканчиваться на
Async
. Так обычно происходит при невнимательном рефакторинге. Это не строгое обязательство и не ошибка. Вообще, это соглашение об именовании имеет смысл только тогда, когда класс предоставляет как синхронные, так и асинхронные версии метода.❌ Неверно
async Task DoSomething() {✅ Верно
await Task.Yield();
}
async Task DoSomethingAsync() {Продолжение следует…
await Task.Yield();
}
Источник: https://cezarypiatek.github.io/post/async-analyzers-p2/
День шестьсот тридцать второй. #ЗаметкиНаПолях #AsyncAwaitFAQ
Самые Частые Ошибки при Работе с async/await. Продолжение 14-16
Начало
Часть 2
Часть 3
Часть 4
14. Злоупотребление Task.Run в простых случаях
Для предварительно вычисленных или легко вычисляемых результатов нет необходимости вызывать
❌ Неверно
Хотя методы продолжений по-прежнему можно использовать, обычно рекомендуется использовать async/await вместо
❌ Неверно
Конструкторы синхронны. Если вам нужно инициализировать некоторую логику, которая может быть асинхронной, использование
Источник: https://github.com/davidfowl/AspNetCoreDiagnosticScenarios/blob/master/AsyncGuidance.md
Самые Частые Ошибки при Работе с async/await. Продолжение 14-16
Начало
Часть 2
Часть 3
Часть 4
14. Злоупотребление Task.Run в простых случаях
Для предварительно вычисленных или легко вычисляемых результатов нет необходимости вызывать
Task.Run
, который поставит задачу в очередь пула потоков, а задача немедленно завершится. Вместо этого используйте Task.FromResult
, чтобы создать задачу, обёртывающую уже вычисленный результат.❌ Неверно
public Task<int> AddAsync(int a, int b) {✅ Верно
return Task.Run(() => a + b);
}
public Task<int> AddAsync(int a, int b) {15. Await лучше, чем ContinueWith
return Task.FromResult(a + b);
}
Хотя методы продолжений по-прежнему можно использовать, обычно рекомендуется использовать async/await вместо
ContinueWith
. Кроме того, надо отметить, что ContinueWith
не захватывает SynchronizationContext
и поэтому семантически отличается от async/await.❌ Неверно
public Task<int> DoSomethingAsync() {✅ Верно
return CallDependencyAsync()
.ContinueWith(task => {
return task.Result + 1;
});
}
public async Task<int> DoSomethingAsync() {16. Синхронизация асинхронного кода в конструкторах
var result =
await CallDependencyAsync();
return result + 1;
}
Конструкторы синхронны. Если вам нужно инициализировать некоторую логику, которая может быть асинхронной, использование
Task.Result
для синхронизации в конструкторе может привести к истощению пула потоков и взаимным блокировкам. Лучше использовать статическую фабрику.public interface IRemoteConnFactory {❌ Неверно
Task<IRemoteConn> ConnectAsync();
}
public interface IRemoteConn { … }
public class Service : IService {✅ Верно
private readonly IRemoteConn _conn;
public Service(IRemoteConnFactory cf) {
_conn = cf.ConnectAsync().Result;
}
}
public class Service : IService {Окончание следует…
private readonly IRemoteConn _conn;
private Service(IRemoteConn conn) {
_conn = conn;
}
public static async Task<Service>
CreateAsync(IRemoteConnFactory cf)
{
return new Service(
await cf.ConnectAsync());
}
}
…
var cf = new ConnFactory();
var srv = await Service.CreateAsync(cf);
Источник: https://github.com/davidfowl/AspNetCoreDiagnosticScenarios/blob/master/AsyncGuidance.md
День шестьсот тридцать третий. #ЗаметкиНаПолях #AsyncAwaitFAQ
Анализаторы кода для асинхронного программирования
Большинство ошибок, описанных в предыдущих постах, можно обнаружить с помощью анализаторов кода, представленных в следующих пакетах. Многие из них не ограничены поиском ошибок в асинхронном коде, поэтому для каждого приведены правила, проверяющие перечисленные ранее ошибки.
1. Microsoft.CodeAnalysis.FxCopAnalyzers
CA2007 - Consider calling ConfigureAwait on the awaited task
CA2008 - Do not create tasks without passing a TaskScheduler
Полный список правил
2. Microsoft.VisualStudio.Threading.Analyzers
VSTHRD002 - Avoid problematic synchronous waits
VSTHRD100 - Avoid async void methods
VSTHRD101 - Avoid unsupported async delegates
VSTHRD107 - Await Task within using expression
VSTHRD111 - Use .ConfigureAwait(bool)
VSTHRD112 - Implement System.IAsyncDisposable
VSTHRD114 - Avoid returning null from a Task-returning method
VSTHRD200 - Use Async naming convention
Полный список правил
3. AsyncFixer
AsyncFixer01 - Unnecessary async/await usage
AsyncFixer03 - Fire & forget async void methods
AsyncFixer04 - Fire & forget async call inside a using block
Полный список правил
4. Roslyn.Analyzers
ASYNC0001 - Asynchronous method names should end with Async
ASYNC0002 - Non asynchronous method names shouldn’t end with Async
ASYNC0003 - Avoid void returning asynchronous method
ASYNC0004 - Use ConfigureAwait(false) on await expression
Полный список правил
5. Meziantou.Analyzer
MA0004 - Use .ConfigureAwait(false)
MA0022 - Return Task.FromResult instead of returning null
MA0032 - Use an overload that have cancellation token
MA0040 - Flow the cancellation token when available
MA0045 - Do not use blocking call (make method async)
MA0079 - Flow a cancellation token using .WithCancellation()
MA0080 - Specify a cancellation token using .WithCancellation()
Полный список правил
6. Roslynator
RCS1046 - Asynchronous method name should end with Async
RCS1047 - Non-asynchronous method name should not end with Async
RCS1090 - Call ConfigureAwait(false)
RCS1174 - Remove redundant async/await
RCS1210 - Return Task.FromResult instead of returning null
RCS1229 - Use async/await when necessary
Полный список правил
7. Asyncify
AsyncifyInvocation - This invocation could benefit from the use of Task async
AsyncifyVariable - This variable access could benefit from the use of Task async
Источник: https://cezarypiatek.github.io/post/async-analyzers-p1/
Анализаторы кода для асинхронного программирования
Большинство ошибок, описанных в предыдущих постах, можно обнаружить с помощью анализаторов кода, представленных в следующих пакетах. Многие из них не ограничены поиском ошибок в асинхронном коде, поэтому для каждого приведены правила, проверяющие перечисленные ранее ошибки.
1. Microsoft.CodeAnalysis.FxCopAnalyzers
CA2007 - Consider calling ConfigureAwait on the awaited task
CA2008 - Do not create tasks without passing a TaskScheduler
Полный список правил
2. Microsoft.VisualStudio.Threading.Analyzers
VSTHRD002 - Avoid problematic synchronous waits
VSTHRD100 - Avoid async void methods
VSTHRD101 - Avoid unsupported async delegates
VSTHRD107 - Await Task within using expression
VSTHRD111 - Use .ConfigureAwait(bool)
VSTHRD112 - Implement System.IAsyncDisposable
VSTHRD114 - Avoid returning null from a Task-returning method
VSTHRD200 - Use Async naming convention
Полный список правил
3. AsyncFixer
AsyncFixer01 - Unnecessary async/await usage
AsyncFixer03 - Fire & forget async void methods
AsyncFixer04 - Fire & forget async call inside a using block
Полный список правил
4. Roslyn.Analyzers
ASYNC0001 - Asynchronous method names should end with Async
ASYNC0002 - Non asynchronous method names shouldn’t end with Async
ASYNC0003 - Avoid void returning asynchronous method
ASYNC0004 - Use ConfigureAwait(false) on await expression
Полный список правил
5. Meziantou.Analyzer
MA0004 - Use .ConfigureAwait(false)
MA0022 - Return Task.FromResult instead of returning null
MA0032 - Use an overload that have cancellation token
MA0040 - Flow the cancellation token when available
MA0045 - Do not use blocking call (make method async)
MA0079 - Flow a cancellation token using .WithCancellation()
MA0080 - Specify a cancellation token using .WithCancellation()
Полный список правил
6. Roslynator
RCS1046 - Asynchronous method name should end with Async
RCS1047 - Non-asynchronous method name should not end with Async
RCS1090 - Call ConfigureAwait(false)
RCS1174 - Remove redundant async/await
RCS1210 - Return Task.FromResult instead of returning null
RCS1229 - Use async/await when necessary
Полный список правил
7. Asyncify
AsyncifyInvocation - This invocation could benefit from the use of Task async
AsyncifyVariable - This variable access could benefit from the use of Task async
Источник: https://cezarypiatek.github.io/post/async-analyzers-p1/
День семьсот двадцать третий. #Оффтоп
Вытащу несколько вещей из чата. Во-первых, чтобы самому не забыть, а во-вторых, для тех, кто не следит за чатом. Сегодня вещь вторая. См. вещь первая.
Разговор зашёл о вопросах на собеседовании и, в частности, про TAP (Task-based Asynchronous Pattern), про которую очень любят спрашивать, например, в Epam. Поскольку тема обширная и запутанная, или, цитируя один из источников ниже: «При попытках понять асинхронность наблюдается чередование состояний "теперь понятно" и "теперь снова непонятно".», - в этом посте сохраню несколько источников для изучения темы.
1. Книга «Конкурентность в C#» Стивена Клири
Как обычно, выкладываю ссылку только на магазин, кто захочет нахаляву, тот найдёт. Сам я её пока не читал, но те, кто читал, рекомендуют. Однако, она не для изучения темы с нуля. Поэтому для начала можно почитать следующее.
2. «The Task-based Asynchronous Pattern» Stephen Toub, Microsoft, 2012
Статья около 35 страниц. Довольно старая, но концептуально ничего не поменялось.
3. «Async/await в C#: концепция, внутреннее устройство, полезные приемы»
Полезная статья, возможно немного сумбурная, потому что сжато. Мне показалось, что это вольный пересказ 5й главы книги «С# In Depth» Джона Скита. Для любителей читать в печатном варианте, см. вложение ниже.
4. Также скромно порекомендую полистать посты на канале, например, по тегу #AsyncAwaitFAQ.
Вытащу несколько вещей из чата. Во-первых, чтобы самому не забыть, а во-вторых, для тех, кто не следит за чатом. Сегодня вещь вторая. См. вещь первая.
Разговор зашёл о вопросах на собеседовании и, в частности, про TAP (Task-based Asynchronous Pattern), про которую очень любят спрашивать, например, в Epam. Поскольку тема обширная и запутанная, или, цитируя один из источников ниже: «При попытках понять асинхронность наблюдается чередование состояний "теперь понятно" и "теперь снова непонятно".», - в этом посте сохраню несколько источников для изучения темы.
1. Книга «Конкурентность в C#» Стивена Клири
Как обычно, выкладываю ссылку только на магазин, кто захочет нахаляву, тот найдёт. Сам я её пока не читал, но те, кто читал, рекомендуют. Однако, она не для изучения темы с нуля. Поэтому для начала можно почитать следующее.
2. «The Task-based Asynchronous Pattern» Stephen Toub, Microsoft, 2012
Статья около 35 страниц. Довольно старая, но концептуально ничего не поменялось.
3. «Async/await в C#: концепция, внутреннее устройство, полезные приемы»
Полезная статья, возможно немного сумбурная, потому что сжато. Мне показалось, что это вольный пересказ 5й главы книги «С# In Depth» Джона Скита. Для любителей читать в печатном варианте, см. вложение ниже.
4. Также скромно порекомендую полистать посты на канале, например, по тегу #AsyncAwaitFAQ.
День 1191.
Подборка тегов, используемых в постах на канале, чтобы облегчить поиск. Не могу гарантировать, что все 1190 постов идеально и корректно помечены тегами, но всё-таки, эта подборка должна помочь.
Общие
Эти посты на совершенно разные темы, помечены этими тегами только с целью различать общую направленность поста.
#ЗаметкиНаПолях – технические посты. Краткие описания теории, особенности языка C# и платформы .NET, примеры кода, и т.п.
#Шпаргалка - примеры кода, команды для утилит и т.п.
#Юмор – шутки, комиксы и просто весёлые тексты или ссылки на видео.
#Оффтоп – всё прочее.
Специализированные
Эти теги более тематические, выделяют основную тему поста.
#Карьера – советы по повышению продуктивности, карьерному росту, прохождению собеседований и т.п.
#Книги – обзоры книг, которые (чаще всего) я лично прочитал, либо ещё нет, но советую прочитать.
#Курсы – обзоры и ссылки на онлайн курсы.
#МоиИнструменты – различные программы, утилиты и расширения IDE, которые я использую в работе.
#ЧтоНовенького – новости из мира .NET.
Узкоспециализированные
Эти теги относятся к определённой узкой теме.
#AsyncTips – серия постов из книги Стивена Клири “Конкурентность в C#”
#AsyncAwaitFAQ – серия постов “Самые Частые Ошибки при Работе с async/await.”
#BestPractices – советы по лучшим практикам, паттернам разработки.
#DesignPatterns – всё о паттернах проектирования, SOLID, IDEALS и т.п.
#DotNetAZ – серия постов с описанием терминов из мира .NET.
#GC – серия постов “Топ Вопросов о Памяти в .NET.” от Конрада Кокосы.
#MoreEffectiveCSharp – серия постов из книги Билла Вагнера “More Effective C#”.
#Testing – всё о тестировании кода.
#TipsAndTricks – советы и трюки, в основном по функционалу Visual Studio.
#Quiz - опросы в виде викторины.
#97Вещей – серия постов из книги “97 Вещей, Которые Должен Знать Каждый Программист”.
#ВопросыНаСобеседовании – тег говорит сам за себя, самые часто задаваемые вопросы на собеседовании по C#, ASP.NET и .NET.
#ЗадачиНаСобеседовании – похоже на вопросы, но здесь больше приводятся практические задачи. Чаще всего это 2 поста: собственно задача и ответ с разбором.
#КакСтатьСеньором – серия постов «Как Стать Сеньором» с советами о продвижении по карьерной лестнице.
Помимо этого, можно просто воспользоваться поиском по постам и попробовать найти то, что вам нужно.
Подборка тегов, используемых в постах на канале, чтобы облегчить поиск. Не могу гарантировать, что все 1190 постов идеально и корректно помечены тегами, но всё-таки, эта подборка должна помочь.
Общие
Эти посты на совершенно разные темы, помечены этими тегами только с целью различать общую направленность поста.
#ЗаметкиНаПолях – технические посты. Краткие описания теории, особенности языка C# и платформы .NET, примеры кода, и т.п.
#Шпаргалка - примеры кода, команды для утилит и т.п.
#Юмор – шутки, комиксы и просто весёлые тексты или ссылки на видео.
#Оффтоп – всё прочее.
Специализированные
Эти теги более тематические, выделяют основную тему поста.
#Карьера – советы по повышению продуктивности, карьерному росту, прохождению собеседований и т.п.
#Книги – обзоры книг, которые (чаще всего) я лично прочитал, либо ещё нет, но советую прочитать.
#Курсы – обзоры и ссылки на онлайн курсы.
#МоиИнструменты – различные программы, утилиты и расширения IDE, которые я использую в работе.
#ЧтоНовенького – новости из мира .NET.
Узкоспециализированные
Эти теги относятся к определённой узкой теме.
#AsyncTips – серия постов из книги Стивена Клири “Конкурентность в C#”
#AsyncAwaitFAQ – серия постов “Самые Частые Ошибки при Работе с async/await.”
#BestPractices – советы по лучшим практикам, паттернам разработки.
#DesignPatterns – всё о паттернах проектирования, SOLID, IDEALS и т.п.
#DotNetAZ – серия постов с описанием терминов из мира .NET.
#GC – серия постов “Топ Вопросов о Памяти в .NET.” от Конрада Кокосы.
#MoreEffectiveCSharp – серия постов из книги Билла Вагнера “More Effective C#”.
#Testing – всё о тестировании кода.
#TipsAndTricks – советы и трюки, в основном по функционалу Visual Studio.
#Quiz - опросы в виде викторины.
#97Вещей – серия постов из книги “97 Вещей, Которые Должен Знать Каждый Программист”.
#ВопросыНаСобеседовании – тег говорит сам за себя, самые часто задаваемые вопросы на собеседовании по C#, ASP.NET и .NET.
#ЗадачиНаСобеседовании – похоже на вопросы, но здесь больше приводятся практические задачи. Чаще всего это 2 поста: собственно задача и ответ с разбором.
#КакСтатьСеньором – серия постов «Как Стать Сеньором» с советами о продвижении по карьерной лестнице.
Помимо этого, можно просто воспользоваться поиском по постам и попробовать найти то, что вам нужно.
1👍59👎1
День 1770. #ЗаметкиНаПолях #AsyncAwaitFAQ
ConfigureAwait в .NET 8. Начало
Мы привыкли использовать ConfigureAwait(false), чтобы предотвратить захват контекста. Изначально его рекомендовали использовать везде, но со временем рекомендация поменялась на «используйте его в коде библиотек, но не в коде приложения». Это довольно простое правило. Недавно (в частности, из-за того, что SynchronizationContext был удалён из ASP.NET Core), многие решили вовсе отказаться от ConfigurationAwait(false), даже в коде библиотек.
Есть несколько распространенных заблуждений относительно ConfigureAwait(false):
1. Это не лучший способ избежать взаимоблокировок. Это не его цель. Чтобы избежать взаимоблокировок, необходимо убедиться, что весь асинхронный код использует ConfigureAwait(false), включая код в библиотеках и среде выполнения. Да и просто это не очень удобное в обслуживании решение.
2. ConfigureAwait настраивает ожидание, а не задачу. Например, функция ConfigureAwait(false) в
ничего не делает. Аналогично, ожидание в
по-прежнему продолжает использовать захваченный контекст, полностью игнорируя ConfigureAwait(false).
3. ConfigureAwait(false) не означает «запустить остальную часть этого метода в потоке из пула потоков» или «запустить остальную часть этого метода в другом потоке». Он работает только в том случае, если await передаёт управление, а затем возобновляет асинхронный метод. В частности, await не передаст управление, когда задача уже завершена, ConfigureAwait не будет иметь никакого эффекта, поскольку ожидание продолжится синхронно.
Теперь посмотрим, что изменилось в ConfigureAwait в .NET 8.
Ничто из существующего поведения не изменилось. Поведение по умолчанию то же, и ConfigurationAwait(false) имеет то же поведение. Добавлена новая перегрузка.
ConfigureAwait(ConfigureAwaitOptions)
ConfigurationAwaitOptions — это новый тип, который предоставляет все возможные способы настройки ожидаемых объектов:
Во-первых, это битовый флаг, т.е. может использоваться любая комбинация этих опций.
Во-вторых, пока это доступно только в Task и Task<T>, по крайней мере, для .NET 8. В ValueTask/ValueTask<T> это пока не добавлено.
Далее рассмотрим каждую из опций.
Окончание следует…
Источник: https://blog.stephencleary.com/2023/11/configureawait-in-net-8.html
ConfigureAwait в .NET 8. Начало
Мы привыкли использовать ConfigureAwait(false), чтобы предотвратить захват контекста. Изначально его рекомендовали использовать везде, но со временем рекомендация поменялась на «используйте его в коде библиотек, но не в коде приложения». Это довольно простое правило. Недавно (в частности, из-за того, что SynchronizationContext был удалён из ASP.NET Core), многие решили вовсе отказаться от ConfigurationAwait(false), даже в коде библиотек.
Есть несколько распространенных заблуждений относительно ConfigureAwait(false):
1. Это не лучший способ избежать взаимоблокировок. Это не его цель. Чтобы избежать взаимоблокировок, необходимо убедиться, что весь асинхронный код использует ConfigureAwait(false), включая код в библиотеках и среде выполнения. Да и просто это не очень удобное в обслуживании решение.
2. ConfigureAwait настраивает ожидание, а не задачу. Например, функция ConfigureAwait(false) в
SomethingAsync().ConfigureAwait(false).GetAwaiter().GetResult()
ничего не делает. Аналогично, ожидание в
var task = SomethingAsync();
task.ConfigureAwait(false);
await task;
по-прежнему продолжает использовать захваченный контекст, полностью игнорируя ConfigureAwait(false).
3. ConfigureAwait(false) не означает «запустить остальную часть этого метода в потоке из пула потоков» или «запустить остальную часть этого метода в другом потоке». Он работает только в том случае, если await передаёт управление, а затем возобновляет асинхронный метод. В частности, await не передаст управление, когда задача уже завершена, ConfigureAwait не будет иметь никакого эффекта, поскольку ожидание продолжится синхронно.
Теперь посмотрим, что изменилось в ConfigureAwait в .NET 8.
Ничто из существующего поведения не изменилось. Поведение по умолчанию то же, и ConfigurationAwait(false) имеет то же поведение. Добавлена новая перегрузка.
ConfigureAwait(ConfigureAwaitOptions)
ConfigurationAwaitOptions — это новый тип, который предоставляет все возможные способы настройки ожидаемых объектов:
[Flags]
public enum ConfigureAwaitOptions
{
None = 0x0,
ContinueOnCapturedContext = 0x1,
SuppressThrowing = 0x2,
ForceYielding = 0x4,
}
Во-первых, это битовый флаг, т.е. может использоваться любая комбинация этих опций.
Во-вторых, пока это доступно только в Task и Task<T>, по крайней мере, для .NET 8. В ValueTask/ValueTask<T> это пока не добавлено.
Далее рассмотрим каждую из опций.
Окончание следует…
Источник: https://blog.stephencleary.com/2023/11/configureawait-in-net-8.html
👍12👎2
День 1771. #ЗаметкиНаПолях #AsyncAwaitFAQ
ConfigureAwait в .NET 8. Окончание
Начало
None и ContinueOnCapturedContext
Эти опции очевидны, с небольшим замечанием.
ContinueOnCapturedContext — то же самое, что true, т.е. ожидание захватит контекст и возобновит выполнение асинхронного метода в этом контексте.
None — то же самое, что false, т.е. ожидание не будет захватывать контекст.
Замечание. По умолчанию контекст НЕ захватывается. Если явно не добавить ContinueOnCapturedContext, контекст не будет захвачен.
Поведение по умолчанию не изменилось: отсутствие ConfigureAwait аналогично true или ContinueOnCapturedContext. Но при использовании других опций ConfigureAwaitOptions, для захвата контекста нужно будет добавлять флаг ContinueOnCapturedContext (см. ниже).
SuppressThrowing
Этот флаг подавляет исключения, которые в противном случае могли бы возникнуть при ожидании задачи. В нормальных условиях await отслеживает исключения в задачах, повторно выбрасывая их в момент ожидания. Обычно это именно то поведение, которое вам нужно, но бывают ситуации, когда вы просто хотите дождаться завершения задачи и вас не волнует, завершится ли она успешно или с исключением. SuppressThrowing позволяет дождаться завершения задачи, не наблюдая за её результатом.
Это скорее всего будет полезно при отмене задач. Иногда нужно вызвать отмену задачи, дождаться её, а затем выполнить другую задачу вместо отменённой. Тогда отменяемую задачу можно ожидать с SuppressThrowing, и метод продолжит работу после завершения задачи, независимо от того, была ли она успешной, отменённой или вызвала исключение.
Замечание: эта семантика не работает для Task<T>, поскольку в этом случае ожидание должно возвращать значение типа T. Неясно, какое значение будет уместно вернуть в случае игнорируемого исключения, поэтому текущее поведение заключается в выдаче исключения ArgumentOutOfRangeException во время выполнения. Было добавлено новое предупреждение:
CA2261: The ConfigureAwaitOptions.SuppressThrowing is only supported with the non-generic Task (ConfigureAwaitOptions.SuppressThrowing поддерживается только с необобщённым Task).
Это предупреждение лучше сделать ошибкой, поскольку во время выполнения оно всегда будет давать сбой.
ForceYielding
ForceYielding – флаг, который может быть полезен в очень редких случаях, но в этих случаях он будет очень удобен. Он похож на Task.Yield. Yield возвращает специальную ожидаемую задачу, которая всегда утверждает, что она не завершена, но немедленно планирует продолжение. Обычно await проверяет, завершена ли ожидаемая задача и, если да, продолжает синхронно. ForceYielding предотвращает такое синхронное поведение, заставляя ожидание вести себя асинхронно. Это может быть полезно, например, при модульном тестировании.
Обратите внимание, что сам по себе флаг ForceYielding также подразумевает отказ от продолжения работы с захваченным контекстом, поэтому это то же самое, что сказать «запланировать остальную часть этого метода для выполнения в пуле потоков» или «переключиться на поток из пула потоков».
Task.Yield возобновит работу в захваченном контексте, поэтому это не то же самое, что ForceYielding. Task.Yield аналогично комбинации флагов
Конечно, реальная ценность ForceYielding в том, что его можно применить к любой задаче. Раньше приходилось либо добавлять отдельный
Источник: https://blog.stephencleary.com/2023/11/configureawait-in-net-8.html
ConfigureAwait в .NET 8. Окончание
Начало
None и ContinueOnCapturedContext
Эти опции очевидны, с небольшим замечанием.
ContinueOnCapturedContext — то же самое, что true, т.е. ожидание захватит контекст и возобновит выполнение асинхронного метода в этом контексте.
None — то же самое, что false, т.е. ожидание не будет захватывать контекст.
Замечание. По умолчанию контекст НЕ захватывается. Если явно не добавить ContinueOnCapturedContext, контекст не будет захвачен.
Поведение по умолчанию не изменилось: отсутствие ConfigureAwait аналогично true или ContinueOnCapturedContext. Но при использовании других опций ConfigureAwaitOptions, для захвата контекста нужно будет добавлять флаг ContinueOnCapturedContext (см. ниже).
SuppressThrowing
Этот флаг подавляет исключения, которые в противном случае могли бы возникнуть при ожидании задачи. В нормальных условиях await отслеживает исключения в задачах, повторно выбрасывая их в момент ожидания. Обычно это именно то поведение, которое вам нужно, но бывают ситуации, когда вы просто хотите дождаться завершения задачи и вас не волнует, завершится ли она успешно или с исключением. SuppressThrowing позволяет дождаться завершения задачи, не наблюдая за её результатом.
Это скорее всего будет полезно при отмене задач. Иногда нужно вызвать отмену задачи, дождаться её, а затем выполнить другую задачу вместо отменённой. Тогда отменяемую задачу можно ожидать с SuppressThrowing, и метод продолжит работу после завершения задачи, независимо от того, была ли она успешной, отменённой или вызвала исключение.
Замечание: эта семантика не работает для Task<T>, поскольку в этом случае ожидание должно возвращать значение типа T. Неясно, какое значение будет уместно вернуть в случае игнорируемого исключения, поэтому текущее поведение заключается в выдаче исключения ArgumentOutOfRangeException во время выполнения. Было добавлено новое предупреждение:
CA2261: The ConfigureAwaitOptions.SuppressThrowing is only supported with the non-generic Task (ConfigureAwaitOptions.SuppressThrowing поддерживается только с необобщённым Task).
Это предупреждение лучше сделать ошибкой, поскольку во время выполнения оно всегда будет давать сбой.
ForceYielding
ForceYielding – флаг, который может быть полезен в очень редких случаях, но в этих случаях он будет очень удобен. Он похож на Task.Yield. Yield возвращает специальную ожидаемую задачу, которая всегда утверждает, что она не завершена, но немедленно планирует продолжение. Обычно await проверяет, завершена ли ожидаемая задача и, если да, продолжает синхронно. ForceYielding предотвращает такое синхронное поведение, заставляя ожидание вести себя асинхронно. Это может быть полезно, например, при модульном тестировании.
Обратите внимание, что сам по себе флаг ForceYielding также подразумевает отказ от продолжения работы с захваченным контекстом, поэтому это то же самое, что сказать «запланировать остальную часть этого метода для выполнения в пуле потоков» или «переключиться на поток из пула потоков».
Task.Yield возобновит работу в захваченном контексте, поэтому это не то же самое, что ForceYielding. Task.Yield аналогично комбинации флагов
ForceYielding | ContinueOnCapturedContext
.Конечно, реальная ценность ForceYielding в том, что его можно применить к любой задаче. Раньше приходилось либо добавлять отдельный
await Task.Yield();
либо создавать собственный ожидаемый объект. Теперь в этом больше нет необходимости, поскольку ForceYielding можно применять к любой задаче. Источник: https://blog.stephencleary.com/2023/11/configureawait-in-net-8.html
👍9