web-dev-qa-db-de.com

Was ist der Unterschied zwischen der Verwendung von ConfigureAwait (false) und Task.Run?

Ich verstehe, dass es empfohlen wird, ConfigureAwait(false) für awaits im Bibliothekscode zu verwenden, damit nachfolgender Code nicht im Ausführungskontext des Aufrufers ausgeführt wird. Dies kann ein UI-Thread sein. Ich verstehe auch, dass await Task.Run(CpuBoundWork) aus demselben Grund anstelle von CpuBoundWork() verwendet werden sollte.

Beispiel mit 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
}

Beispiel mit 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
        });
}

Was sind die Unterschiede zwischen diesen beiden Ansätzen?

55
Sam

Wenn Sie Task.Run sagen, sagen Sie, dass Sie einige CPU-Arbeit haben, die etwas Zeit in Anspruch nehmen kann. Daher sollte sie immer in einem Threadpool-Thread ausgeführt werden.

Wenn Sie ConfigureAwait(false) sagen, sagen Sie, dass der Rest dieser async-Methode nicht den ursprünglichen Kontext benötigt. ConfigureAwait ist eher ein Optimierungshinweis; es bedeutet nicht immer , dass die Fortsetzung in einem Threadpool-Thread ausgeführt wird.

68
Stephen Cleary

In diesem Fall wird Ihre Task.Run-Version etwas mehr Aufwand verursachen, da der erste wartende Aufruf (await client.GetAsync(address)) ebenso wie die Ergebnisse des Task.Run-Aufrufs in den aufrufenden Kontext eingebettet wird.

Im ersten Beispiel dagegen ist Ihre erste Async()-Methode so konfiguriert, dass kein Marshalling in den aufrufenden Kontext erforderlich ist, sodass die Fortsetzung weiterhin in einem Hintergrundthread ausgeführt werden kann. Daher wird kein Marshalling in den Kontext des Anrufers erfolgen.

16
Reed Copsey

Übereinstimmende @Stephen-Antwort: Wenn immer noch Verwirrung herrscht, sehen Sie untenstehende Screenshots.
Siehe unteres Bild Haupt-Thread, der versucht, Label zu aktualisieren. enter image description here

2 # Mit ConfigureAwait (false)
Siehe untenstehender Image-Arbeitsthread, der versucht, das Label zu aktualisieren  enter image description here

3
Pankaj Rawat

Als Randbemerkung könnte LoadPage() in Ihrem UI-Thread noch blockieren sein, da await client.GetAsync(address) Zeit benötigt, um eine Aufgabe zu erstellen, die an ConfigureAwait(false) übergeben wird. Ihr zeitaufwändiger Vorgang wurde möglicherweise bereits gestartet, bevor die Aufgabe zurückgegeben wird.

Eine mögliche Lösung ist die Verwendung von SynchronizationContextRemover von hier :

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