我很好奇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不会创建新线程或使用线程池中的其他线程。行。
我的问题:
async / await在哪里执行等待代码(在我们的示例中下载网站)导致控制产生到我们的程序和程序的下一行代码只是询问Task<string> getStringTask
的结果?我们知道没有新线程,没有使用线程池。
我是否正确地假设CLR只是在一个线程的范围内切换当前可执行代码和方法的等待部分?但是,更改加数的顺序并不会改变总和,并且可能会在一些不明显的时间内阻止UI。
答案 0 :(得分:9)
async / await在哪里执行等待代码(在我们的示例中下载网站)导致控制产生到我们的程序和程序的下一行代码只是询问Task getStringTask的结果?我们知道没有新线程,没有使用线程池。
如果操作是真正异步的,那么就没有代码来执行&#34;执行&#34;。您可以将其视为通过回调处理所有内容; HTTP请求被发送(同步),然后HttpClient
注册一个将完成Task<string>
的回调。下载完成后,将调用回调,完成任务。它比这更复杂,但这是一般的想法。
我有一篇博文,详细介绍how asynchronous operations can be threadless。
我愚蠢地假设CLR只是在一个线程的范围内切换当前可执行代码和方法的等待部分吗?
这是一个部分真实的心理模型,但它不完整。首先,当async
方法恢复时,其(前)调用堆栈不会随之恢复。因此,async
/ await
与fibers或co-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操作)的原因。
那么你的问题:
它在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
不需要线程,它使用系统时钟。所以你有“等待代码”“执行”30秒,但在那段时间内没有任何实际发生。该操作已开始,将来将完成30秒。