使用ConfigureAwait(false)和Task.Run有什么区别?

时间:2022-01-07 14:37:33

I understand that it's recommended to use ConfigureAwait(false) for awaits in library code so that subsequent code does not run in the caller's execution context, which could be a UI thread. I also understand that await Task.Run(CpuBoundWork) should be used instead of CpuBoundWork() for the same reason.

我理解,建议在库代码中使用ConfigureAwait(false)来等待后续代码不在调用者的执行上下文中运行,后者可能是UI线程。我也明白,出于同样的原因,应该使用等待Task.Run(CpuBoundWork)而不是CpuBoundWork()。

Example with 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
}

Example with 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
        });
}

What are the differences between these two approaches?

这两种方法有什么区别?

4 个解决方案

#1


67  

When you say Task.Run, you are saying that you have some CPU work to do that may take a long time, so it should always be run on a thread pool thread.

当你说Task.Run,​​你说你有一些CPU工作可能需要很长时间,所以它应该始终在线程池线程上运行。

When you say ConfigureAwait(false), you are saying that the rest of that async method does not need the original context. ConfigureAwait is more of an optimization hint; it does not always mean that the continuation is run on a thread pool thread.

当您说ConfigureAwait(false)时,您说该异步方法的其余部分不需要原始上下文。 ConfigureAwait更像是一个优化提示;它并不总是意味着继续在线程池线程上运行。

#2


16  

In this case, your Task.Run version will have a bit more overhead, as the first await call (await client.GetAsync(address)) will still marshal back into the calling context, as will the results of the Task.Run call.

在这种情况下,您的Task.Run版本将有更多的开销,作为第一个调用的await(等待client.GetAsync(地址))将仍然编组回调用环境,如将在本Task.Run调用的结果。

In the first example, on the other hand, your first Async() method is configured to not require marshaling back into the calling context, which allows the continuation to run on a background thread still. As such, there won't be any marshaling back into the caller's context.

另一方面,在第一个示例中,您的第一个Async()方法被配置为不需要编组回调用上下文,这允许继续在后台线程上运行。因此,不会有任何编组回到调用者的上下文中。

#3


2  

Agreed @Stephen answer, If still confusion see below screenshots 1# Without ConfigureAwait(false)
See below image Main thread trying to update Label 使用ConfigureAwait(false)和Task.Run有什么区别?

同意@Stephen的回答,如果还是混乱,请看下面的截图1#没有ConfigureAwait(false)见下图图片主线程试图更新Label

2# With ConfigureAwait(false)
See below image working thread trying to update label 使用ConfigureAwait(false)和Task.Run有什么区别?

2#With ConfigureAwait(false)参见下图图片工作线程试图更新标签

#4


1  

As a side note, in both cases LoadPage() could still block your UI thread, because await client.GetAsync(address) needs time to create a task to pass to ConfigureAwait(false). And your time consuming operation might have already started before task is returned.

作为旁注,在两种情况下,LoadPage()仍然可以阻止您的UI线程,因为等待client.GetAsync(地址)需要时间来创建传递给ConfigureAwait(false)的任务。在返回任务之前,您的耗时操作可能已经开始。

One possible solution is to use SynchronizationContextRemover from here:

一种可能的解决方案是从这里使用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


67  

When you say Task.Run, you are saying that you have some CPU work to do that may take a long time, so it should always be run on a thread pool thread.

当你说Task.Run,​​你说你有一些CPU工作可能需要很长时间,所以它应该始终在线程池线程上运行。

When you say ConfigureAwait(false), you are saying that the rest of that async method does not need the original context. ConfigureAwait is more of an optimization hint; it does not always mean that the continuation is run on a thread pool thread.

当您说ConfigureAwait(false)时,您说该异步方法的其余部分不需要原始上下文。 ConfigureAwait更像是一个优化提示;它并不总是意味着继续在线程池线程上运行。

#2


16  

In this case, your Task.Run version will have a bit more overhead, as the first await call (await client.GetAsync(address)) will still marshal back into the calling context, as will the results of the Task.Run call.

在这种情况下,您的Task.Run版本将有更多的开销,作为第一个调用的await(等待client.GetAsync(地址))将仍然编组回调用环境,如将在本Task.Run调用的结果。

In the first example, on the other hand, your first Async() method is configured to not require marshaling back into the calling context, which allows the continuation to run on a background thread still. As such, there won't be any marshaling back into the caller's context.

另一方面,在第一个示例中,您的第一个Async()方法被配置为不需要编组回调用上下文,这允许继续在后台线程上运行。因此,不会有任何编组回到调用者的上下文中。

#3


2  

Agreed @Stephen answer, If still confusion see below screenshots 1# Without ConfigureAwait(false)
See below image Main thread trying to update Label 使用ConfigureAwait(false)和Task.Run有什么区别?

同意@Stephen的回答,如果还是混乱,请看下面的截图1#没有ConfigureAwait(false)见下图图片主线程试图更新Label

2# With ConfigureAwait(false)
See below image working thread trying to update label 使用ConfigureAwait(false)和Task.Run有什么区别?

2#With ConfigureAwait(false)参见下图图片工作线程试图更新标签

#4


1  

As a side note, in both cases LoadPage() could still block your UI thread, because await client.GetAsync(address) needs time to create a task to pass to ConfigureAwait(false). And your time consuming operation might have already started before task is returned.

作为旁注,在两种情况下,LoadPage()仍然可以阻止您的UI线程,因为等待client.GetAsync(地址)需要时间来创建传递给ConfigureAwait(false)的任务。在返回任务之前,您的耗时操作可能已经开始。

One possible solution is to use SynchronizationContextRemover from here:

一种可能的解决方案是从这里使用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
}