如何使用async / await在UI线程上异步运行多个任务?

时间:2015-01-18 11:26:06

标签: c# .net asynchronous async-await threadpool

我已经阅读(和使用过)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);
}

3 个答案:

答案 0 :(得分:10)

  

据我了解,我仍然不会在这种情况下离开UI线程   它异步运行,UI仍然响应,我可以开始   几个任务同时加快我的应用程序。   如果我们只使用一个线程,它怎么能工作?

首先,我建议阅读Stephan Clearys博客文章 - There is no thread

为了理解如何完全运行多个工作单元,我们需要掌握一个重要的事实: async IO 绑定操作(几乎)没什么做线程。

怎么可能?好吧,如果我们一直深入到操作系统,我们会看到对设备驱动程序的调用 - 那些负责执行网络调用和写入等操作的人到磁盘,都是自然异步实现的,他们在工作时不会占用一个线程。这样,当设备驱动程序正在做它的事情时,不需要一个线程。只有在设备驱动程序完成执行后,它才会通过IOCP(I / O完成端口)向操作系统发出信号,然后执行方法调用的其余部分(这在.NET中完成)通过线程池,它有专用的IOCP线程。)

史蒂芬博客文章很好地证明了这一点:

Going down the async rabbit hole

一旦操作系统执行DPC(延迟过程调用)并对IRP(I / O请求数据包)进行排队,它的工作基本上完成,直到设备驱动程序用 I&#39发回信号为止。 ;完成消息,这会导致整个操作链(在博客文章中描述)执行,最终最终会调用您的代码。

需要注意的另一件事是,.NET会做一些"魔术"在使用async-await模式时幕后使用。有一种称为"同步上下文" (你可以找到一个相当冗长的解释here)。这个同步上下文是负责再次调用UI线程上的继续(第一个await之后的代码)(在存在这种上下文的地方)。

修改

应该注意的是,同步上下文的魔力也适用于CPU绑定操作(实际上也适用于任何等待对象),因此当您通过Task.RunTask.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