异步/ AWAIT。继续执行方法的等待部分在哪里?

时间:2015-07-02 13:45:07

标签: c# asynchronous async-await task-parallel-library .net-4.5

我很好奇async / await如何让你的程序不被停止。 我真的很喜欢the way how Stephen Cleary explains async/await"我喜欢想到"等待"作为"异步等待"。也就是说,异步方法暂停直到等待完成(所以它等待),但实际线程没有被阻止(所以它是异步的)。"

我已经读过异步方法同步工作,直到compilator满足等待关键字。好。 如果编译器无法计算出等待,那么编译器会将等待和控制权控制器排队到调用方法AccessTheWebAsync的方法。确定。 在调用者(本例中的事件处理程序)内部,处理模式继续。在等待结果之前,调用者可能会做其他不依赖AccessTheWebAsync结果的工作,或者调用者可能会立即等待。事件处理程序正在等待AccessTheWebAsync,而AccessTheWebAsync正在等待GetStringAsync。让我们看看an msdn example

async Task<int> AccessTheWebAsync()
{ 
    // You need to add a reference to System.Net.Http to declare client.
    HttpClient client = new HttpClient();

    // GetStringAsync returns a Task<string>. That means that when you await the 
    // task you'll get a string (urlContents).
    Task<string> getStringTask = client.GetStringAsync("http://msdn.microsoft.com");

    // You can do work here that doesn't rely on the string from GetStringAsync.
    DoIndependentWork();

    // The await operator suspends AccessTheWebAsync. 
    //  - AccessTheWebAsync can't continue until getStringTask is complete. 
    //  - Meanwhile, control returns to the caller of AccessTheWebAsync. 
    //  - Control resumes here when getStringTask is complete.  
    //  - The await operator then retrieves the string result from getStringTask. 
    string urlContents = await getStringTask;

    // The return statement specifies an integer result. 
    // Any methods that are awaiting AccessTheWebAsync retrieve the length value. 
    return urlContents.Length;
}

Another article from msdn blog表示 async / await不会创建新线程或使用线程池中的其他线程。行。

我的问题:

  1. async / await在哪里执行等待代码(在我们的示例中下载网站)导致控制产生到我们的程序和程序的下一行代码只是询问Task<string> getStringTask的结果?我们知道没有新线程,没有使用线程池。

  2. 我是否正确地假设CLR只是在一个线程的范围内切换当前可执行代码和方法的等待部分?但是,更改加数的顺序并不会改变总和,并且可能会在一些不明显的时间内阻止UI。

3 个答案:

答案 0 :(得分:9)

  

async / await在哪里执行等待代码(在我们的示例中下载网站)导致控制产生到我们的程序和程序的下一行代码只是询问Task getStringTask的结果?我们知道没有新线程,没有使用线程池。

如果操作是真正异步的,那么就没有代码来执行&#34;执行&#34;。您可以将其视为通过回调处理所有内容; HTTP请求被发送(同步),然后HttpClient注册一个将完成Task<string>的回调。下载完成后,将调用回调,完成任务。它比这更复杂,但这是一般的想法。

我有一篇博文,详细介绍how asynchronous operations can be threadless

  

我愚蠢地假设CLR只是在一个线程的范围内切换当前可执行代码和方法的等待部分吗?

这是一个部分真实的心理模型,但它不完整。首先,当async方法恢复时,其(前)调用堆栈不会随之恢复。因此,async / awaitfibersco-routines非常不同,即使它们可用于完成类似的事情。

而不是将await视为&#34;切换到其他代码&#34;,将其视为&#34;返回一个不完整的任务&#34;。如果调用方法调用await,那么它也会返回一个不完整的任务,等等。最后,你要么将一个不完整的任务返回给一个框架(例如,ASP.NET) MVC / WebAPI / SignalR,或单元测试运行器);或者您将拥有async void方法(例如,UI事件处理程序)。

当操作正在进行时,你最终会得到一个&#34;堆栈&#34;任务对象。不是真正的堆栈,只是一个依赖树。每个async方法都由一个任务实例表示,他们都在等待异步操作完成。

  

在哪里继续进行方法的等待部分?

等待任务时,await默认情况下会在捕获的上下文中恢复其async方法。除非是SynchronizationContext.Current,否则此上下文为null,在此情况下为TaskScheduler.Current。实际上,这意味着在UI线程上运行的async方法将在该UI线程上恢复;处理ASP.NET请求的async方法将继续处理相同的ASP.NET请求(可能在不同的线程上);在大多数其他情况下,async方法将在线程池线程上恢复。

在您的问题的示例代码中,GetStringAsync将返回不完整的任务。下载完成后,该任务将完成。因此,当AccessTheWebAsync在该下载任务上调用await时(假设下载尚未完成),它将捕获其当前上下文,然后从AccessTheWebAsync返回不完整的任务。

当下载任务完成时,AccessTheWebAsync的继续将被安排到该上下文(UI线程,ASP.NET请求,线程池,...),它将提取Length在该上下文中执行时的结果当AccessTheWebAsync方法返回时,它会设置先前从AccessTheWebAsync返回的任务的结果。这反过来将恢复下一个方法等。

答案 1 :(得分:2)

通常,延续(await之后的方法部分)可以在任何地方运行。在实践中,它倾向于在UI线程(例如,在Windows应用程序中)或线程池(例如,在ASP .NET服务器中)上运行。在某些情况下,它也可以在调用程序线程上同步运行......实际上它取决于您调用的API类型以及正在使用的同步上下文。

您链接的博客文章表示延迟不在线程池线程上运行,它只是说将方法标记为async 并不会神奇地导致调用在单独的线程或线程池上运行的方法。

也就是说,他们只是试图告诉您,如果您有方法void Foo() { Console.WriteLine(); },将其更改为async Task Foo() { Console.WriteLine(); }并不会突然导致调用Foo();完全不同的行为 - 它仍然会同步执行。

答案 2 :(得分:2)

如果通过“等待代码”你的意思是实际的异步操作,那么你需要意识到它在CPU之外“执行”所以不需要线程而且没有代码可以运行

例如,当您下载网页时,大多数操作都会在您的服务器从Web服务器发送和接收数据时发生。发生这种情况时,没有代码可以执行。这就是你在等待Task获得实际结果之前可以“接管”线程和做其他事情(其他CPU操作)的原因。

那么你的问题:

  1. 它在CPU外“执行”(所以它并没有真正执行)。这可能意味着网络驱动程序,远程服务器等(主要是I / O)。

  2. 没有。 CLR不需要执行真正的异步操作。它们仅在未来启动并完成。

  3. 一个简单的例子是Task.Delay,它创建一个在间隔后完成的任务:

    var delay = Task.Delay(TimeSpan.FromSeconds(30));
    // do stuff
    await delay;
    

    Task.Delay在内部创建并设置System.Threading.Timer,它将在间隔后执行回调并完成任务。 System.Threading.Timer不需要线程,它使用系统时钟。所以你有“等待代码”“执行”30秒,但在那段时间内没有任何实际发生。该操作已开始,将来将完成30秒。