我已经阅读(和使用过)async / await已经有一段时间了,但是我还有一个问题我无法得到答案。说我有这个代码。
private async void workAsyncBtn_Click(object sender, EventArgs e)
{
var myTask = _asyncAwaitExcamples.DoHeavyWorkAsync(5);
await myTask;
statusTextBox.Text += "\r\n DoHeavyWorkAsync message";
}
从UI线程调用它并返回到UI线程。因此,我可以在此方法中和await myTask
之后执行特定于UI的操作。如果我使用了.ConfigureAwait(false)
,那么在执行statusTextBox.Text += "\r\n DoHeavyWorkAsync message";
时我会得到一个线程异常,因为我可以调用myTask
,可以从线程池中获取任何可用的线程。
我的问题。据我所知,在这种情况下我永远不会离开UI线程,它仍然是异步运行,UI仍然是响应式的,我可以同时启动多个任务,从而加快我的应用程序。如果我们只使用一个线程,它怎么能工作?
谢谢!
为Sievajet编辑
private async void workAsyncBtn_Click(object sender, EventArgs e)
{
await DoAsync();
}
private async Task DoAsync()
{
await Task.Delay(200);
statusTextBox.Text += "Call to form";
await Task.Delay(200);
}
答案 0 :(得分:10)
据我了解,我仍然不会在这种情况下离开UI线程 它异步运行,UI仍然响应,我可以开始 几个任务同时加快我的应用程序。 如果我们只使用一个线程,它怎么能工作?
首先,我建议阅读Stephan Clearys博客文章 - There is no thread。
为了理解如何完全运行多个工作单元,我们需要掌握一个重要的事实: async IO 绑定操作(几乎)没什么做线程。
怎么可能?好吧,如果我们一直深入到操作系统,我们会看到对设备驱动程序的调用 - 那些负责执行网络调用和写入等操作的人到磁盘,都是自然异步实现的,他们在工作时不会占用一个线程。这样,当设备驱动程序正在做它的事情时,不需要一个线程。只有在设备驱动程序完成执行后,它才会通过IOCP(I / O完成端口)向操作系统发出信号,然后执行方法调用的其余部分(这在.NET中完成)通过线程池,它有专用的IOCP线程。)
史蒂芬博客文章很好地证明了这一点:
一旦操作系统执行DPC(延迟过程调用)并对IRP(I / O请求数据包)进行排队,它的工作基本上完成,直到设备驱动程序用 I&#39发回信号为止。 ;完成消息,这会导致整个操作链(在博客文章中描述)执行,最终最终会调用您的代码。
需要注意的另一件事是,.NET会做一些"魔术"在使用async-await
模式时幕后使用。有一种称为"同步上下文" (你可以找到一个相当冗长的解释here)。这个同步上下文是负责再次调用UI线程上的继续(第一个await
之后的代码)(在存在这种上下文的地方)。
修改强>
应该注意的是,同步上下文的魔力也适用于CPU绑定操作(实际上也适用于任何等待对象),因此当您通过Task.Run
或Task.Factory.StartNew
使用线程池线程时,这也可以。
答案 1 :(得分:4)
TaskParallelLibrary(TPL)使用可以配置TaskScheduler
的{{1}}来返回SynchronizationContext,如下所示:
TaskScheduler.FromCurrentSynchronizationContext
当textBox1.Text = "Start";
// The SynchronizationContext is captured here
Factory.StartNew( () => DoSomeAsyncWork() )
.ContinueWith(
() =>
{
// Back on the SynchronizationContext it came from
textBox1.Text = "End";
},TaskScheduler.FromCurrentSynchronizationContext());
方法暂停在async
时,默认情况下它会捕获当前的await
并在等待它来自的SynchronizationContext之后对代码进行编组。
SynchronizationContext
异步并等待示例:
textBox1.Text = "Start";
// The SynchronizationContext is captured here
/* The implementation of DoSomeAsyncWork depends how it runs, this could run on the threadpool pool
or it could be an 'I/O operation' or an 'Network operation'
which doesnt use the threadpool */
await DoSomeAsyncWork();
// Back on the SynchronizationContext it came from
textBox1.Text = "End";
答案 2 :(得分:1)
当UI线程调用await时,它启动异步操作并立即返回。当异步操作完成时,它会从线程池通知一个线程,但异步的内部实现await将执行调度到UI线程,该线程将在等待之后继续执行代码。
Dispatch通过SynchronizationContext实现,而SynchronizationContext又调用System.Windows.Forms.Control.BeginInvoke。
CLR通过C#(第4版)(开发人员参考)第4版,Jeffrey Richter,第749页
实际上,Jeffrey与MS合作实现了他的AsyncEnumerator启发的async / await