it-swarm.com.ru

Каковы различия между использованием ConfigureAwait (false) и Task.Run?

Я понимаю, что рекомендуется использовать ConfigureAwait(false) для awaits в коде библиотеки, чтобы последующий код не выполнялся в контексте выполнения вызывающей стороны, который может быть потоком пользовательского интерфейса. Я также понимаю, что await Task.Run(CpuBoundWork) следует использовать вместо CpuBoundWork() по той же причине.

Пример с ConfigureAwait

public async Task<HtmlDocument> LoadPage(Uri address)
{
    using (var client = new HttpClient())
    using (var httpResponse = await client.GetAsync(address).ConfigureAwait(false))
    using (var responseContent = httpResponse.Content)
    using (var contentStream = await responseContent.ReadAsStreamAsync().ConfigureAwait(false))
        return LoadHtmlDocument(contentStream); //CPU-bound
}

Пример с Task.Run

public async Task<HtmlDocument> LoadPage(Uri address)
{
    using (var client = new HttpClient())
    using (var httpResponse = await client.GetAsync(address))
        return await Task.Run(async () =>
        {
            using (var responseContent = httpResponse.Content)
            using (var contentStream = await responseContent.ReadAsStreamAsync())
                return LoadHtmlDocument(contentStream); //CPU-bound
        });
}

Каковы различия между этими двумя подходами?

55
Sam

Когда вы говорите Task.Run, вы говорите, что у вас есть некоторая работа с процессором, которая может занять много времени, поэтому она всегда должна выполняться в потоке пула потоков.

Когда вы говорите ConfigureAwait(false), вы говорите, что остальная часть этого метода async не нуждается в исходном контексте. ConfigureAwait - это скорее подсказка по оптимизации; это не всегда означает, что продолжение выполняется в потоке пула потоков.

68
Stephen Cleary

В этом случае ваша версия Task.Run будет иметь немного больше накладных расходов, так как первый вызов await (await client.GetAsync(address)) все равно будет перенаправлен обратно в контекст вызова, как и результаты вызова Task.Run.

В первом примере, с другой стороны, ваш первый метод Async() настроен так, чтобы не требовать маршалинга обратно в вызывающий контекст, что позволяет продолжать выполнение в фоновом потоке. Таким образом, не будет никакого маршалинга обратно в контекст вызывающего.

16
Reed Copsey

Согласованный ответ @Stephen, если все еще путаница, см. Скриншоты ниже 1 # Без ConfigureAwait (false)
См. Изображение ниже Основная тема пытается обновить Label  enter image description here

2 # С ConfigureAwait (false)
См. Изображение ниже рабочего потока пытается обновить метку  enter image description here

3
Pankaj Rawat

Напомним, что в обоих случаях LoadPage() может по-прежнему блокировать ваш поток пользовательского интерфейса, поскольку await client.GetAsync(address) требуется время для создания задачи для передачи в ConfigureAwait(false). И ваша трудоемкая операция, возможно, уже началась до того, как задание будет возвращено.

Одним из возможных решений является использование SynchronizationContextRemover из здесь :

public async Task<HtmlDocument> LoadPage(Uri address)
{
    await new SynchronizationContextRemover();

    using (var client = new HttpClient())
    using (var httpResponse = await client.GetAsync(address))
    using (var responseContent = httpResponse.Content)
    using (var contentStream = await responseContent.ReadAsStreamAsync())
        return LoadHtmlDocument(contentStream); //CPU-bound
}
1
Roman Gudkov