Должен ли я использовать configure await во всех методах или только в первом методе?
У меня есть библиотека с асинхронными методами, и я прочитал, что для библиотек рекомендуется использовать ConfigureAwait(false)
,
Например, если у меня есть что-то вроде:
public async Task myMethod01()
{
await myMethod02();
}
private async Task myMethod02()
{
await myMethod03();
}
private async Task myMethod03()
{
await anotherMetodAsync().ConfigureAwait(false);
}
Пользователь библиотеки может использовать только method01
остальные 2 метода являются закрытыми, потому что они являются вспомогательными методами для основного метода. method01()
,
Но я не знаю, нужно ли это использовать ConfigureAwait
только в первом методе цепного вызова или я должен использовать во всех из них.
2 ответа
ТЛ; др
Да, это необходимо для того, чтобы все ваши асинхронные продолжения в коде библиотеки выполнялись в потоке пула потоков (в зависимости от используемого SynchronizationContext/ TaskScheduler).
Хочешь узнать больше?
true
пытается маршалировать оставшуюся часть асинхронного метода обратно в исходный захваченный контекстfalse
планирует оставшуюся часть асинхронного метода в потоке пула потоков
Рассмотрим следующий пример WPF: WPF использует DispatcherSynchronizationContext для возобновления асинхронных продолжений в контексте пользовательского интерфейса, поскольку фоновый поток не может обновить содержимое элементов управления.
private async void Button_Click(object sender, RoutedEventArgs e)
{
logger.LogInformation(Thread.CurrentThread.IsThreadPoolThread); //false -> GUI context
await CompleteAsynchronously();
logger.LogInformation(Thread.CurrentThread.IsThreadPoolThread); //false -> GUI context
await CompleteAsynchronously().ConfigureAwait(false);
logger.LogInformation(Thread.CurrentThread.IsThreadPoolThread); //true
}
private async Task CompleteAsynchronously()
{
logger.LogInformation(Thread.CurrentThread.IsThreadPoolThread); //false -> GUI context
await Task.Delay(TimeSpan.FromMilliseconds(100)).ConfigureAwait(false);
logger.LogInformation(Thread.CurrentThread.IsThreadPoolThread); //true
}
Здесь вы видите, что continueOnCapturedContext
Флаг вызываемого метода не влияет на вызывающего. Тем не менее, вызываемый метод выполняется (или, по крайней мере, начинает работать) в потоке, в котором, конечно, был запущен вызывающий объект.
Тем не менее, захват текущего контекста (либо текущий SynchronizationContext
; если ноль, то текущий TaskScheduler
) происходит только тогда, когда ожидается незавершенное задание. Если задание выполняется синхронно, continueOnCapturedContext
не имеет никакого эффекта, и остальная часть метода продолжает работать синхронно в текущем потоке.
private async void Button_Click(object sender, RoutedEventArgs e)
{
logger.LogInformation(Thread.CurrentThread.IsThreadPoolThread); //false -> GUI context
await CompleteSynchronously().ConfigureAwait(false);
logger.LogInformation(Thread.CurrentThread.IsThreadPoolThread); //false -> GUI context
}
private async Task CompleteSynchronously()
{
await Task.Delay(0);
}
Итак, в коде вашей библиотеки (при условии, что вам никогда не требуется контекст), вы всегда должны использовать ConfigureAwait(false)
чтобы гарантировать отсутствие захвата контекста для асинхронных продолжений, независимо от платформы, вызывающей ваши сборки (например, WPF, ASP.NET Core, Console, ...).
Для получения более подробной информации ознакомьтесь с рекомендациями по асинхронному программированию (например, ConfigureAwait
) в этой статье MSDN Magazine Stephen Cleary.
Вы должны использовать ConfigureAwait(false)
на все асинхронные вызовы. Когда это не сделано, первый асинхронный вызов (без ConfigureAwait(false)
) будет принимать SynchronizationContext, и это может вызвать тупик в определенных условиях (например, в ASP.NET), когда вы будете синхронно ждать этого вызова.
Мой совет - прочитать эту статью, написанную Стивеном Клири. Интересующая вас часть:
С помощью
ConfigureAwait(false)
избегать тупиков - опасная практика. Вы должны были бы использоватьConfigureAwait(false)
за каждое ожидание в переходном закрытии всех методов, вызываемых кодом блокировки, включая весь сторонний и сторонний код. С помощьюConfigureAwait(false)
чтобы избежать тупика, в лучшем случае просто взломать).