- Могу ли я использовать Task.Run, чтобы избежать использования ConfigurationAwait(false)?
- Могу ли я использовать SynchronizationContext.SetSynchronizationContext, чтобы избежать использования ConfigurationAwait(false)?
- Я использую GetAwaiter().GetResult(). Нужно ли мне использовать ConfigurationAwait(false)?
Могу ли я использовать Task.Run, чтобы избежать использования ConfigurationAwait(false)?
Да. Если вы напишете:
тогда a ConfigureAwait(false) в этом SomethingAsync() вызове будет nop, потому что переданный делегат Task.Run будет выполнен в потоке пула потоков, без пользовательского кода выше в стеке, так что он SynchronizationContext.Current вернет null. Кроме того, Task.Run неявно используется TaskScheduler.Default, что означает, что запрос TaskScheduler.Current внутри делегата также вернет Default. Это означает, что он await будет вести себя одинаково независимо от того, ConfigureAwait(false) использовался ли он. Он также не дает никаких гарантий относительно того, что может делать код внутри этой лямбды. Если у вас есть код:
Тогда код внутри SomethingAsyncфактически будет восприниматься SynchronizationContext.Current как этот SomeCoolSyncCtx экземпляр, и как этот, await так и любые ненастроенные ожидания внутри SomethingAsync будут отправлены обратно в него. Поэтому, чтобы использовать этот подход, вам необходимо понимать, что может или не может делать весь код, который вы ставите в очередь, и могут ли его действия помешать вашим.
Этот подход также достигается за счет необходимости создания/постановки в очередь дополнительного объекта задачи. Это может иметь или не иметь значения для вашего приложения или библиотеки в зависимости от вашей чувствительности к производительности.
Также имейте в виду, что такие уловки могут вызвать больше проблем, чем пользы, и иметь другие непредвиденные последствия. Например, инструменты статического анализа (например, анализаторы Roslyn) были написаны для пометки ожиданий, которые не используют ConfigureAwait(false), например CA2007 . Если вы включите такой анализатор, а затем примените подобный трюк, чтобы избежать использования ConfigureAwait, есть большая вероятность, что анализатор пометит это и фактически потребует от вас больше работы. Так что, возможно, вы затем отключите анализатор из-за его шумности, и теперь вы пропустите другие места в кодовой базе, где вам действительно следовало использовать ConfigureAwait(false).
Могу ли я использовать SynchronizationContext.SetSynchronizationContext, чтобы избежать использования ConfigurationAwait(false)?
Нет. Ну, может быть. Это зависит от задействованного кода.
Некоторые разработчики пишут такой код:
в надежде, что это заставит код внутри CallCodeThatUsesAwaitAsync видеть текущий контекст как null. И так и будет. Однако вышеизложенное никак не повлияет на то, что видит await, TaskScheduler.Current поэтому, если этот код выполняется на каком-то пользовательском TaskScheduler, awaits внутри CallCodeThatUsesAwaitAsync(и который не использует ConfigureAwait(false)) все равно увидит и вернется в очередь к этому пользовательскому TaskScheduler.
Применяются все те же предостережения, что и в предыдущем Task.RunFAQ: такое обходное решение влияет на производительность, и код внутри try также может помешать этим попыткам, установив другой контекст (или вызвав код с нестандартным TaskScheduler). .
При такой картине также нужно быть осторожным с небольшими вариациями:
Видите проблему? Это немного сложно увидеть, но потенциально очень впечатляет. Нет никакой гарантии, что await в конечном итоге вызовется обратный вызов/продолжение в исходном потоке, что означает, что сброс возврата SynchronizationContext к исходному состоянию на самом деле может не произойти в исходном потоке, что может привести к тому, что последующие рабочие элементы в этом потоке увидят неправильную информацию. контекст (чтобы противодействовать этому, хорошо написанные модели приложений, которые устанавливают собственный контекст, обычно добавляют код для его ручного сброса перед вызовом любого дальнейшего пользовательского кода). И даже если это произойдет в том же потоке, может пройти некоторое время, прежде чем это произойдет, так что контекст не будет должным образом восстановлен в течение некоторого времени. А если он выполняется в другом потоке, он может в конечном итоге установить для этого потока неправильный контекст. И так далее. Очень далеко от идеала.
Я использую GetAwaiter().GetResult(). Нужно ли мне использовать ConfigurationAwait(false)?
Нет. ConfigureAwait влияет только на обратные вызовы. В частности, шаблон ожидания требует, чтобы ожидающие предоставили IsCompleted свойство, GetResult метод и OnCompleted метод (необязательно с UnsafeOnCompleted методом). ConfigureAwait влияет только на поведение {Unsafe}OnCompleted, поэтому, если вы просто напрямую вызываете метод awaiter GetResult(), независимо от того, делаете ли вы это на TaskAwaiter или ConfiguredTaskAwaitable.ConfiguredTaskAwaiter разница в поведении не имеет значения. Итак, если вы видите task.ConfigureAwait(false).GetAwaiter().GetResult() в коде, вы можете заменить его на task.GetAwaiter().GetResult() (а также подумать, действительно ли вы хотите, чтобы такая блокировка была такой).
Я знаю, что работаю в среде, в которой никогда не будет специального SynchronizationContext или пользовательского TaskScheduler. Могу ли я пропустить использование ConfigurationAwait(false)?
Может быть. Это зависит от того, насколько вы уверены в части «никогда». Как упоминалось в предыдущих ответах на часто задаваемые вопросы, тот факт, что модель приложения, в котором вы работаете, не устанавливает пользовательские настройки SynchronizationContext и не вызывает ваш код в соответствии с пользовательскими настройками, TaskScheduler не означает, что какой-либо другой пользовательский или библиотечный код этого не делает. Поэтому вам нужно быть уверенным, что это не так, или, по крайней мере, признать риск, если он может быть.
Я слышал, что ConfigurationAwait(false) больше не нужен в .NET Core. Это так?
ЛОЖЬ. Это необходимо при работе на .NET Core по тем же причинам, что и при работе на .NET Framework. В этом отношении ничего не изменилось.
Однако изменилось то, публикуют ли определенные среды свои собственные файлы SynchronizationContext. В частности, в то время как классический ASP.NET на .NET Framework имеет свой собственный SynchronizationContext , в отличие от ASP.NET Core его нет. Это означает, что код, выполняющийся в приложении ASP.NET Core, по умолчанию не будет видеть пользовательский файл SynchronizationContext, что уменьшает необходимость ConfigureAwait(false) запуска в такой среде.
Однако это не означает, что никогда не будет обычая SynchronizationContext или TaskScheduler. Если какой-либо пользовательский код (или другой библиотечный код, который использует ваше приложение) устанавливает пользовательский контекст и вызывает ваш код или вызывает ваш код в Task запланированном для пользовательского TaskScheduler, то даже в ASP.NET Core ваши ожидания могут видеть контекст, отличный от стандартного. или планировщик, который заставит вас захотеть использовать ConfigureAwait(false). Конечно, в таких ситуациях, если вы избегаете синхронной блокировки (которой вам следует избегать в веб-приложениях независимо от этого) и если вы не возражаете против небольших издержек производительности в таких ограниченных случаях, вы, вероятно, можете обойтись без использования ConfigureAwait(false).
Могу ли я использовать ConfigurationAwait, когда «ожидаю foreach» IAsyncEnumerable?
Да. Пример см. в этой статье журнала MSDN Magazine .
await foreach привязывается к шаблону, поэтому, хотя его можно использовать для перечисления IAsyncEnumerable<T>, его также можно использовать для перечисления чего-то, что предоставляет нужную область API. Библиотеки времени выполнения .NET включают ConfigureAwait метод расширения , IAsyncEnumerable<T> который возвращает пользовательский тип, который обертывает IAsyncEnumerable<T>и Boolean и предоставляет правильный шаблон. Когда компилятор генерирует вызовы перечислителя MoveNextAsync и DisposeAsync методов, эти вызовы относятся к возвращаемому сконфигурированному типу структуры перечислителя, а он, в свою очередь, выполняет ожидания желаемым настроенным способом.
Могу ли я использовать ConfigurationAwait, когда «ожидаю использования» IAsyncDisposable?
Да, хотя и с небольшим осложнением.
Как IAsyncEnumerable<T>описано в предыдущем разделе часто задаваемых вопросов, библиотеки времени выполнения .NET предоставляют ConfigureAwait метод расширения IAsyncDisposable и await using будут с радостью работать с ним, поскольку он реализует соответствующий шаблон (а именно, предоставляя соответствующий DisposeAsync метод):
Проблема здесь в том, что теперь типом является не , MyAsyncDisposableClass а System.Runtime.CompilerServices.ConfiguredAsyncDisposable тип, возвращаемый из этого ConfigureAwait метода расширения в IAsyncDisposable.
Чтобы обойти это, вам нужно написать одну дополнительную строку:
Теперь тип "c" снова желаемый MyAsyncDisposableClass. Это также приводит к увеличению объема "c"; если это важно, вы можете заключить все это в фигурные скобки.
Я использовал ConfigurationAwait(false), но мой AsyncLocal все еще переходит в код после ожидания. Это ошибка?
Нет, это ожидаемо. AsyncLocal<T>потоки данных являются частью ExecutionContext, которая отделена от SynchronizationContext. Если вы явно не отключили ExecutionContext поток с помощью ExecutionContext.SuppressFlow(), ExecutionContext(и, следовательно, AsyncLocal<T>данные) всегда будут передаваться через awaits, независимо от того, ConfigureAwaitиспользуется ли он для предотвращения захвата исходного SynchronizationContext. Дополнительную информацию можно найти в этой записи блога .