Подтвердить что ты не робот

В чем разница между использованием 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
        });
}

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

4b9b3361

Ответ 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
}