本文介绍了为什么我需要在所有传递闭包中使用ConfigureAwait(false)?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

限时删除!!

我正在学习异步/等待,并且在阅读本文后不要阻止异步代码

I am learning async/await and after I read this article Don't Block on Async Code

,此​​是async/await适用于同时受IO和CPU约束的方法

我注意到@Stephen Cleary的文章中的一条提示.

I notice one Tip from @Stephen Cleary 's article.

正如我上面所附加的,它再次出现在帖子的代码中.

It appeared again in the code of the post as I have attached above.

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

据我所知,当我们使用ConfigureAwait(false)时,其余异步方法将在线程池中运行.为什么我们需要将其添加到每次等待中在传递闭包中?我自己只是认为这是我所知道的正确版本.

As my knowledge when we using ConfigureAwait(false) the rest of async method will be run in the thread pool. Why we need to add it into every awaitin transitive closure? I myself just think this is the correct version as what I knew.

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

这意味着在using块中第二次使用ConfigureAwait(false)是没有用的.请告诉我正确的方法.预先感谢.

It means the second use of ConfigureAwait(false) in using block is useless. Please tell me the correct way.Thanks in advance.

推荐答案

关闭,但是有一个重要的警告提示您缺少.在使用ConfigureAwait(false)等待任务后恢复时,将在任意线程上恢复.记下恢复时"一词.

Close, but there is an important caveat you are missing. When you resume after awaiting a task with ConfigureAwait(false), you will resume on an arbitrary thread. Take note of the words "when you resume."

让我给你看一些东西

public async Task<string> GetValueAsync()
{
    return "Cached Value";
}

public async Task Example1()
{
    await this.GetValueAsync().ConfigureAwait(false);
}

请考虑Example1中的await.尽管您正在等待async方法,但是该方法实际上并不执行任何异步工作.如果async方法什么都没有await,它会同步执行,并且等待者永远不会继续,因为它从一开始就不会被暂停.如本例所示,对ConfigureAwait(false)的调用可能是多余的:它们可能根本没有作用.在此示例中,输入Example1时所处的上下文都是在await之后所处的上下文.

Consider the await in Example1. Although you are awaiting an async method, that method does not actually perform any asynchronous work. If an async method doesn't await anything, it executes synchronously, and the awaiter never resumes because it never suspended in the first place. As this example shows, calls to ConfigureAwait(false) may be superfluous: they may have no effect at all. In this example, whatever context you were on when you enter Example1 is the context you will be on after the await.

不太符合您的期望,对吧?但是,这并非完全不寻常.许多async方法可能包含不需要调用者挂起的快速路径.缓存资源的可用性就是一个很好的例子(谢谢@jakub-dąbek!),但是async方法可能会提早保释,还有很多其他原因.我们经常在方法开始时检查各种条件,以查看是否可以避免做不必要的工作,并且async方法没有什么不同.

Not quite what you expected, right? And yet, it's not altogether unusual. Many async methods may contain fast paths that don't require the caller to suspend. The availability of a cached resource is a good example (thanks, @jakub-dąbek!), but there plenty of other reasons an async method might bail early. We often check for various conditions at the beginning of a method to see if we can avoid doing unnecessary work, and async methods are no different.

让我们看一下另一个示例,这一次来自WPF应用程序:

Let's look at another example, this time from a WPF application:

async Task DoSomethingBenignAsync()
{
    await Task.Yield();
}

Task DoSomethingUnexpectedAsync()
{
    var tcs = new TaskCompletionSource<string>();
    Dispatcher.BeginInvoke(Action(() => tcs.SetResult("Done!")));
    return tcs.Task;
}

async Task Example2()
{
    await DoSomethingBenignAsync().ConfigureAwait(false);
    await DoSomethingUnexpectedAsync();
}

看看Example2.我们await 始终的第一种方法是异步运行的.到第二个await时,我们知道我们正在线程池线程上运行,因此在第二个调用中不需要ConfigureAwait(false),对吗? 错误.尽管名称中包含Async并返回了Task,但我们的第二种方法并未使用asyncawait编写.相反,它执行自己的调度并使用TaskCompletionSource传达结果.从await恢复时,可能最终在提供结果的任何线程上运行,在本例中为WPF的分派器线程.哎呀.

Take a look at Example2. The first method we await always runs asynchronously. By the time we hit the second await, we know we're running on a thread pool thread, so there's no need for ConfigureAwait(false) on the second call, right? Wrong. Despite having Async in the name and returning a Task, our second method wasn't written using async and await. Instead, it performs its own scheduling and uses a TaskCompletionSource to communicate the result. When you resume from your await, you might end up running on whatever thread provided the result, which in this case is WPF's dispatcher thread. Whoops.

这里的关键要点是,您常常不完全确切地知道等待"方法的作用.如果使用而没有ConfigureAwait,则可能最终会运行在意外的地方.这可以在async调用堆栈的任何级别上发生,因此避免无意中获得单线程上下文所有权的最可靠方法是将ConfigureAwait(false) every await一起使用,即,在整个传递闭包中.

The key takeaway here is that you often don't know exactly what an 'awaitable' method does. With or without ConfigureAwait, you might end up running somewhere unexpected. This can happen at any level of an async call stack, so the surest way to avoid inadvertently taking ownership of a single-threaded context is to use ConfigureAwait(false) with every await, i.e., throughout the transitive closure.

当然,有时候您可能会想要在当前上下文中恢复,这很好.表面上,这就是为什么它是默认行为.但是,如果您不是真正地需要它,那么我建议默认情况下使用ConfigureAwait(false).对于库代码尤其如此.可以从任何地方调用库代码,因此最好遵循最少惊喜的原则.这意味着在不需要时,不要将其他线程锁定在调用者的上下文之外.即使您在库代码中的任何地方都使用ConfigureAwait(false),调用者仍然可以选择在原始上下文中恢复(如果他们想要的话).

Of course, there may be times when you want to resume on your current context, and that's fine. That is ostensibly why it's the default behavior. But if you don't genuinely need it, then I recommend using ConfigureAwait(false) by default. This is especially true for library code. Library code can get called from anywhere, so it's best adhere to the principle of least surprise. That means not locking other threads out of your caller's context when you don't need it. Even if you use ConfigureAwait(false) everywhere in your library code, your caller will still have the option to resume on their original context if that's what they want.

这篇关于为什么我需要在所有传递闭包中使用ConfigureAwait(false)?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

1403页,肝出来的..

09-06 13:24