首页 文章

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

提问于
浏览
3

我真的好奇async / await如何让你的程序不被停止 . 我真的很喜欢the way how Stephen Cleary explains async/await:"I like to think of "等待" as an "异步等待". That is to say, the async method pauses until the awaitable is complete(so it waits), but the actual thread is not blocked (so it's asynchornous)."

我已经读过异步方法同步工作,直到compilator满足等待关键字 . 好 . If compilator cannot figure out awaitable, then compilator queues the awaitable and yield control to the method that called method AccessTheWebAsync. 好的 . 在调用者(本例中的事件处理程序)内部,处理模式继续 . 在等待结果之前,调用者可能会执行其他不依赖于 AccessTheWebAsync 结果的工作,或者调用者可能会立即等待 . 事件处理程序正在等待 AccessTheWebAsyncAccessTheWebAsync 正在等待 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 blogasync/await does not create new thread or use other threads from thread pool . 好 .

我的问题:

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

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

3 回答

  • 2

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

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

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

    我是否正确地认为CLR只是在一个线程的范围内切换当前可执行代码和方法的等待部分?

    那个's a partially true mental model, but it'不完整 . 首先,当 async 方法恢复时,其(前)调用堆栈不会随之恢复 . 所以 async / awaitfibersco-routines非常不同,即使它们可用于完成类似的事情 .

    而不是将 await 视为"switch to other code",将其视为"return an incomplete task" . 如果调用方法也调用 await ,那么它也会返回一个不完整的任务等 . 最后,你 async void 方法有一个 async void 方法(例如,UI事件处理程序) .

    当操作正在进行时,您最终会得到一个"stack"的任务对象 . 不是真正的堆栈,只是一个依赖树 . 每个 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 返回的任务的结果 . 这反过来将恢复下一个方法等 .

  • 8

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

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

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

  • 2

    如果通过"awaitable code"表示实际的异步操作,那么您需要实现 it "executes" outside of the CPU so there's no thread needed and no code to run .

    例如,当您下载网页时,大多数操作都发生在您的服务器从Web服务器发送和接收数据时 . 在等待 Task 获得实际结果之前,你有's no code to execute while this happens. That'这个线程并做其他事情(其他CPU操作)的原因 .

    所以对你的问题:

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

    • 否 . 真正的异步操作不需要由CLR执行 . 它们仅在未来启动和完成 .


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

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

    Task.Delay 在内部创建并设置 System.Threading.Timer ,它将在间隔后执行回调并完成任务 . System.Threading.Timer 不需要线程,它使用系统时钟 . 所以你有"awaitable code"这个"executes"持续30秒,但那段时间没有任何实际发生 . 操作已开始,将来将完成30秒 .

相关问题