一个async和await 关键字的简单入门

c#4.5中出现了async 和 await关键字,对于简化异步的写法有很大帮助,说是编译器帮你做了很多自动修改代码的工作,比Java7异步框架好了不知道多少,吹牛吧, 这类语法糖带来的后果是写代码的人根本就不知道后面的原理,也很难入门,要看大量的资料。 首先看TAP,基于任务的异步模式,之前最好先了解APM,异步模型, 和EAP, 基于事件的异步模式,饶了一圈才知道这个东西怎么用。 但是博客园里的文章少之又少,也没有解释清楚。

今天看了TIM的一篇文章,觉得能解释清楚吧, 所以转到这里。

若果要举一个简单的例子,就用一个Windows Forms 应用加一个长时间运行的任务来做演示,目的是不想长时间运行的任务阻塞UI线程, 这里是一个长时间运行的方法。

 private int slowFunc(int a,int b)       
 {          
 System.Threading.Thread.Sleep(10000); 
 return a + b;
 }

这个方法需要10秒去运行, 在窗口里点击一下按钮,调用这个方法, 然后执行结果显示到标签控件上,  以下是一段使用c#4.0的代码想达到这个目的。

private void button1_Click(object sender, EventArgs e)
 {            
 this.button1.Enabled = false; //prevent re-entry 
 var someTask = Task<int>.Factory.StartNew(() => slowFunc(1, 2));
 this.label1.Text = "Result: " + someTask.Result.ToString(); //oops, blocks calling thread 
 this.button1.Enabled = true;       
 }

糟糕,好像不灵, 虽然已经很麻烦的创建了Task对象来将slowFunc方法运行为一个后台线程, 当调用Task对象的Result属性时还是不行, 因为它阻塞了线程直到任务(Task)完成。

下面是修改的结果, 还是C# 4.0

private void button1_Click(object sender, EventArgs e)        
 {
 this.button1.Enabled = false;          
 var uiScheduler = TaskScheduler.FromCurrentSynchronizationContext(); //get UI thread context 
 var someTask = Task<int>.Factory.StartNew(() => slowFunc(1, 2)); //create and start the Task 
 someTask.ContinueWith(x =>     
   {                                          
   this.label1.Text = "Result: " + someTask.Result.ToString();   
   this.button1.Enabled = true;   
   }, uiScheduler  
  );        
 }

这段代码可以达到目的, 点击Button, 界面并没有阻塞, 我可以自由最小化,移动窗口和其他一些操作。

但是这里要做一个额外工作, ContinueWith方法告诉Task当后台线程完成后运行一些其他的代码, 默认情况,这些代码不会运行在UI线程上,意味着你访问UI的话将引发异常,但是传入之前建立的TaskScheduler(得到当前UI线程),让他可以继续运行。

现在再来看看C#5.0 下同样的问题怎么解决。 只贴出响应按钮的代码。

private async void button1_Click(object sender, EventArgs e)
 {
 this.button1.Enabled = false; 
 var someTask = Task<int>.Factory.StartNew(() => slowFunc(1, 2)); 
 await someTask;  
 this.label1.Text = "Result: " + someTask.Result.ToString(); 
 this.button1.Enabled = true;
 }

少点代码,同样的效果,通常是一件好事。

但是这里发生了什么?

第一, async修饰符被加到Click的事件处理方法上, 这并不意味着方法异步运行, 这意味着它使用await来包含要异步运行的代码,就像Eric Lippert解释的那样, 它告诉编译器重写这个方法。

第二, await关键字, 并不意味”这个方法会阻塞现在的线程知道异步操作返回“, 这会把异步操作变回同步操作, 这正是我们要避免的, 恰恰相反, 它意味”如果我们要等待的任务Task还没有完成, 那么把这个方法之后的部分注册为Task任务的延续(Continuation), 然后马上返回到调用者,任务Task完成后会调用这个延续的部分。 很难理解? 可以这样想Task<TResult>.Run(Func<TResult>).ContinueWith(Delegate…..).

要是参考第一个不灵的例子,会发现代码很相似,换一个说法,就是使用await关键字使得代码更直观,不用专门调用ContinueWith方法,和线程上下文纠缠不清。

这还是并发程序,C# 5.0没法阻止的是不耐烦的用户在结果出现之前会不断点击按钮, 所以在后台线程运行完之前要禁用按钮。

最后,这个是C#编译器开发者的文章,还没时间读,找不到part one. http://blogs.msdn.com/b/ericlippert/archive/2010/10/29/asynchronous-programming-in-c-5-0-part-two-whence-await.aspx

原文地址:https://www.cnblogs.com/grkin/p/3014208.html