В чем разница между использованием ConfigureAwait (false) и Task.Run?
Я понимаю, что он рекомендовал использовать ConfigureAwait(false)
для await
в коде библиотеки, чтобы последующий код не выполнялся в контексте выполнения вызывающего, который может быть потоком пользовательского интерфейса. Я также понимаю, что 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
});
}
Каковы различия между этими двумя подходами?
Ответы
Ответ 1
Когда вы говорите Task.Run
, вы говорите, что у вас есть работа с ЦП, это может занять много времени, поэтому его всегда следует запускать в потоке пула потоков.
Когда вы говорите ConfigureAwait(false)
, вы говорите, что остальная часть этого метода async
не нуждается в исходном контексте. ConfigureAwait
- это скорее намек на оптимизацию; это не всегда означает, что продолжение выполняется в потоке пула потоков.
Ответ 2
В этом случае ваша версия Task.Run
будет иметь немного больше накладных расходов, так как первый вызов ожидания (await client.GetAsync(address)
) по-прежнему будет возвращаться в вызывающий контекст, как и результаты вызова Task.Run
.
В первом примере, с другой стороны, ваш первый метод Async()
настроен так, чтобы не требовать маршалинга обратно в вызывающий контекст, что позволяет продолжить выполнение в фоновом потоке. Таким образом, не будет никакого маршалинга обратно в контекст вызывающего абонента.
Ответ 3
Согласовано @Stephen ответ, Если все еще путаница, см. ниже скриншоты
1 # Без ConfigureAwait (false)
См. Ниже изображение Основная тема пытается обновить Label
![введите описание изображения здесь]()
2 # С ConfigureAwait (false)
См. Ниже рабочий поток изображения, пытающийся обновить ярлык
![введите описание изображения здесь]()
Ответ 4
В качестве стороннего примечания в обоих случаях LoadPage()
мог по-прежнему блокировать ваш поток пользовательского интерфейса, потому что await client.GetAsync(address)
требуется время для создания задачи для перехода на ConfigureAwait(false)
. И ваша трудоемкая операция, возможно, уже началась до возвращения задачи.
Одним из возможных решений является использование SynchronizationContextRemover
из here:
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
}