Должен ли я использовать 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).

Хочешь узнать больше?

Task.ConfigureAwait (Boolean)

  • 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) чтобы избежать тупика, в лучшем случае просто взломать).

Другие вопросы по тегам