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

Python023

如何正确理解.NET 4.5和C#5.0中的asyncawait异步编程模式,第1张

相对于之前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的朋友有一定启发。

同步和异步的区别:

1、同步就是说多个任务之间是有先后关系的,一个任务需要等待另一个任务执行完毕才能继续执行。

2、异步就是说多个任务之间没有先后关系,不需要相互等待各做各的事。

同步编程方法:

1、信号量

2、互斥量

异步无需考虑资源冲突,不需特别处理。

注:host是在之前定义过的一个ip地址

这个一个客户端的部分代码,源代码本来是:

//tcpClient = new TcpClient(AddressFamily.InterNetwork)//获得本机的ip地址。

运行时没有错误,但是我不想这里指定本机ip,我想指定一个固定的服务器ip,所以讲其注释掉改成:

tcpClient = new TcpClient(host,52888)host为我指定的ip

上面是你理解的有问题。

第一:tcpClient = new TcpClient(AddressFamily.InterNetwork)这句话不是获得本机的ip地址,而是要创建一个使用ip版本4的寻址方案的TcpClient对象。这时只是定义这个对象,并没有建立连接

第二:tcpClient = new TcpClient(host,52888)这句代码的意思是建立一个连接到host和其端口52888的连接,在创建时就已经连接上了。这里的host指的是你要连接的服务器IP地址。此时在创建时已经建立连接了,所以在使用BeginConnect异步调用时,抛出一个【在一个已经建立连接的套接字上做一个连接请求。

你理解上面的内容,应该就可以解决你的问题了。