如何正确理解.NET 4.5和C#5.0中的async/await异步编程模式

如题所述

相对于之前Begin/End模式和事件模式,async/await模式让程序员得以用同步的代码结构进行异步编程。async/await入门很方便,但是深入理解却涉及很多领域,如线程池、同步上下文等等。我断断续续接触了几个月,稍微有一些心得:


await的作用是等待异步Task完成,并不是阻塞的。举个例子,一个异步方法:

public async Task Caller()
{
    Action0();
    await Method();
    Action3();
}

public async Task Method()
{
    Action1();
    await Task.Delay(1000);
    Action2();
}

A. å½“你在非UI线程A上执行Caller(),将完成以下几件事:

    [线程A]执行Action0()

    [线程A]调用await Method()

    [线程A]执行Action1()

    [线程A]启动任务Task.Delay(1000),并在线程池里安插一个新任务,在Task.Delay(1000)完成后,由另一个线程执行6

    [线程A]去处理别的事情

    [线程B]执行Action2()

    [线程B]await Method()返回

    [线程B]执行Action3()

    å…¶ä¸­ï¼Œçº¿ç¨‹A和线程B并不保证是同一个线程。如果你在await前后打印线程ID,你会发现ID是不同的。


B. å½“你在UI线程上执行Caller(),过程有了变化:

    [UI线程]执行Action0()

    [UI线程]调用await Method()

    [UI线程]执行Action1()

    [UI线程]启动任务Task.Delay(1000),并在线程池里安插一个新任务,在Task.Delay(1000)完成后,由另一个线程执行6

    [UI线程]去处理别的事情

    [线程C]在UI线程的同步上下文中执行7(类似于在窗体类上执行Invoke()方法)

    [UI线程]执行Action2()

    [UI线程]await Method()返回

    [UI线程]执行Action3()

    å¯è§ï¼Œå½“使用await方法的线程为UI线程时,程序默认会通过第6步,保证await前后是同一个线程ID。这个当然是有一定性能牺牲的(甚至会造成死锁,在D里会讨论),如果你不想在await完成后回到UI线程,见C。


C. 你可以在UI线程上使用await XXX().ConfigureAwait(false)去替代await XXX(),来禁止当await XXX()结束时恢复线程。举个例子,执行下列代码是没问题的(如B里描述的):

private async void button1_Click(object sender, EventArgs e)
{
    this.Text = "123";
    await Task.Delay(1000);
    this.Text = "321";
}

但是,执行下列代码就会发生“线程间操作无效”的错误:

private async void button1_Click(object sender, EventArgs e)
{
    this.Text = "123";
    await Task.Delay(1000).ConfitureAwait(false);
    this.Text = "321";    //线程间操作无效
}

因为执行

this.Text = "321";

的线程已经不再是UI线程。


D. 顺便一提,Task.Wait()方法,相比于await Task,会同步地执行Task。但是,如果你在UI线程上Wait的Task里本身又有await,那么将会产生死锁:

private void Foo(object sender, EventArgs e)
{
    this.Text = "123";
    Method().Wait();        //此处发生死锁
    this.Text = "321";    //这行永远也不会执行
}
private async Task Method()
{
    await Task.Delay(1000);
}

    ä¸ºä»€ä¹ˆå‘¢ï¼ŸMethod().Wait()会阻塞UI线程等待Method()完成,但是参照B过程,在await完成后,Method()完成前,是需要恢复到UI线程的,但是此时UI线程已经被阻塞了,因此死锁就发生了。

    è¦é¿å…è¿™ä¸ªæ­»é”ï¼Œå¯ä»¥å‚ç…§C。


E. 说出来你可能不信,上面的都是我手打的。在内容上虽然不一定严谨,但希望对楼主和其它新接触TAP的朋友有一定启发。

温馨提示:答案为网友推荐,仅供参考