async/await成对匹配,不是一个死循环吗

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了async/await成对匹配,不是一个死循环吗相关的知识,希望对你有一定的参考价值。

相对于之前Begin/End模式和事件模式,async/await模式让程序员得以用同步的代码结构进行异步编程。async/await入门很方便,但是深入理解却涉及很多领域,如线程池、同步上下文等等。我断断续续接触了几个月,稍微有一些心得:
await的作用是等待异步Task完成,并不是阻塞的。举个例子,一个异步方法:<pre t="code" l="csharp">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()</ol> 其中,线程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()</ol> 可见,当使用await方法的线程为UI线程时,程序默认会通过第6步,保证await前后是同一个线程ID。这个当然是有一定性能牺牲的(甚至会造成死锁,在D里会讨论),如果你不想在await完成后回到UI线程,见C。
C. 你可以在UI线程上使用await XXX().ConfigureAwait(false)去替代await XXX(),来禁止当await XXX()结束时恢复线程。举个例子,执行下列代码是没问题的(如B里描述的):<pre t="code" l="csharp">private async void button1_Click(object sender, EventArgs e)

this.Text = "123";
await Task.Delay(1000);
this.Text = "321";
但是,执行下列代码就会发生“线程间操作无效”的错误:<pre t="code" l="csharp">private async void button1_Click(object sender, EventArgs e)

this.Text = "123";
await Task.Delay(1000).ConfitureAwait(false);
this.Text = "321"; //线程间操作无效
因为执行<pre t="code" l="csharp">this.Text = "321";的线程已经不再是UI线程。

D. 顺便一提,Task.Wait()方法,相比于await Task,会同步地执行Task。但是,如果你在UI线程上Wait的Task里本身又有await,那么将会产生死锁:<pre t="code" l="csharp">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的朋友有一定启发。
参考技术A 相对于之前Begin/End模式和事件模式,async/await模式让程序员得以用同步的代码结构进行异步编程。async/await入门很方便,但是深入理解却涉及很多领域,如线程池、同步上下文等等。我断断续续接触了几个月,稍微有一些心得:
await的作用是等待异步Task完成,并不是阻塞的。举个例子,一个异步方法:<pre t="code" l="csharp">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()</ol> 其中,线程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()</ol> 可见,当使用await方法的线程为UI线程时,程序默认会通过第6步,保证await前后是同一个线程ID。这个当然是有一定性能牺牲的(甚至会造成死锁,在D里会讨论),如果你不想在await完成后回到UI线程,见C。
C. 你可以在UI线程上使用await XXX().ConfigureAwait(false)去替代await XXX(),来禁止当await XXX()结束时恢复线程。举个例子,执行下列代码是没问题的(如B里描述的):<pre t="code" l="csharp">private async void button1_Click(object sender, EventArgs e)

this.Text = "123";
await Task.Delay(1000);
this.Text = "321";
但是,执行下列代码就会发生“线程间操作无效”的错误:<pre t="code" l="csharp">private async void button1_Click(object sender, EventArgs e)

this.Text = "123";
await Task.Delay(1000).ConfitureAwait(false);
this.Text = "321"; //线程间操作无效
因为执行<pre t="code" l="csharp">this.Text = "321";的线程已经不再是UI线程。

D. 顺便一提,Task.Wait()方法,相比于await Task,会同步地执行Task。但是,如果你在UI线程上Wait的Task里本身又有await,那么将会产生死锁:<pre t="code" l="csharp">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的朋友有一定启发。

以上是关于async/await成对匹配,不是一个死循环吗的主要内容,如果未能解决你的问题,请参考以下文章

async / await 的串行和并行

C# 异步操作 async await 的用法

promise async/await

async/await使用深入详解

.NET Web应用中为什么要使用async/await异步编程

async/await 原理及简单实现