1、什么是异步编程?
异步编程就是把耗时的操作放进一个单独的线程中进行处理(该线程需要将执行进度反映到界面上)。由于耗时操作是在另外一个线程中被执行的,所以它不会堵塞主线程。主线程开启这些单独的线程后,还可以继续执行其他操作(例如窗体绘制等)。
异步编程可以提高用户体验,避免在进行耗时操作时让用户看到程序“卡死”的现象。
2、异步编程模型(APM)
APM是Asynchronous Programming Mode的缩写,即异步编程模型的意思,它允许程序用更少的线程去执行更多的操作。在.NET Framework中,要分辨某个类是否实现了异步编程模型,主要就是看该类是否实现了返回类型为IAsyncResult接口的BeginXXX方法和EndXXX方法。
由于委托类型定义了BeginInvoke和EndInvoke方法,所以委托类型都实现了异步编程模型。
2.1 Beginxxx方法--开始执行异步操作
在需要获取文件中的内容时,我们通常会使用FileStream的同步方法Read进行读取,该同步方法的定义为:
public override int Read(byte[] array,int offset,int count)
当使用上面的方法读取大文件的内容时,会出现堵塞UI线程,导致在文件内容没有读取完成之前,用户不能对窗体进行任何操作(包括关闭应用程序),这时窗体就会出现无法响应的情况。
为了解决这个问题,微软早在.NET 1.0的时候就提出了异步编程模型,并为FileStream类提供了异步模式的方法实现,即BeginRead方法。该方法会异步地执行读取操作,并返回实现了IAsyncResult接口的对象(该对象存储这异步操作的信息)。
下面给出了BeginRead方法的定义,我们可以从中找出它与同步方法Read的区别:
public override IAsyncResult BeginRead(byte[] array,int offset,int numBytes,AsyncCallback userCallback,Object stateObject)
从以上的异步方法的定义可以看出,该异步方法的前面3个参数与同步方法Read一致,后两个参数userCallback和StateObject则是同步方法所不具备的。userCallback表示异步操作完成后需要回调的方法,该方法必须匹配AsyncCallback委托类型;stateObject则代表传递给回调方法的对象,在回调方法中,可以通过查询IAsyncResult接口的AsyncState属性来读取该对象。该异步方法之所以不会堵塞UI线程,是因为它在被调用后,会立即把控制权交还给调用线程(如果是UI线程调用了该方法,则就将控制权返回给UI线程),然后由另一个线程去执行文件读取操作。
2.2 Endxxx方法--结束异步操作
每次调用Beginxxx方法后,应用程序还需调用Endxxx方法来获取操作返回的结果。Beginxxx方法所返回的,是实现了IAsyncResult接口的对象,该对象并非相应的同步方法返回的结果。此时还需要调用Endxxx方法来结束异步操作,并向该方法传递Beginxxx所返回的对象。Endxxx方法返回的类型与同步方法相同,如FileStream的EndRead方法会返回一个Int32类型,代表从文件流中实际读取的字节数。
Endxxx方法有许多中方式调用,但有一种是最常用的,即使用AsyncCallback委托来指定操作完成时要调用的方法,在回调方法中调用Endxxx方法来获得异步操作返回的结果。
1 static void Main() 2 { 3 SynchronizationContext sc=SynchronizationContext.Current; 4 AsyncMethodCaller methodCaller=new AsyncMethodCaller(DownLoadFileAsync); 5 method.BeginInvoke(txtUrl.Text.Trim(),GetResult,null); 6 } 7 private static void GetRsult(IAsyncResult result) 8 { 9 AsyncMethodCaller caller=(AsyncMethodCaller)((AsyncResult)result).AsyncDelegate; 10 string returnstring=call.EndInvoke(result); 11 }
3、异步编程模型(EAP)
虽然前面的异步编程可以解决执行耗时操作时界面无法响应的问题,但APM也同样存在这一些明显的问题,如不支持对异步操作的取消以及不能提供下载进度报告等。然而对于桌面应用程序而言,进度报告和取消操作的功能是必不可少的,所以微软在.NET 2.0 发布时又提出了一个新的异步编程模型--基于事件的异步模型,即EAP(Event-based Asynchronous Pattern)。
实现了EAP的类具有一个或多个以Async为后缀的方法,以及对应的Completed事件,并且这些类支持异步方法的取消和进度报告。在.NET类库中,只有部分类实现了EAP,共17个。在这17个类中,开发过程中使用最多的莫过于BackgroundWorker类了。
经常使用的属性为:
CancellationPending:用来指示应用程序是否已请求取消后台操作;
IsBusy:指示异步操作是否正在运行;
WorkReportProgress:只是BackgrounWorker能否报告进度;
WorkerSupportsCancellation:指示BackgroundWoker是否支持异步取消操作;
经常使用的方法为:
CancelAsync:请求取消异步操作;
ReportProgress:用于引发ProgressChanged事件;
RunWorkAsync:调用后开始执行异步操作;
经常使用到的3个事件为:
DoWork:调用RunWokerAsync时触发的事件;
ProgressChanged:调用ReportProgress时触发的事件,程序会在该事件中进行进度报告的更新;
RunWorkerCompleted:当异步操作已完成、被取消或引发异常时被触发。
这种方法已经很少用到了,所以这里就不详细介绍了。
4、TAP又是什么?
前面介绍了.NET提供的两种异步编程模式,分别为.NET 1.0中的APM和.NET 2.0中的EAP。虽然这两种异步编程模式可以实现多数情况下的异步编程,但是它们在MSDN文档上都被标注为了不推荐使用的实现方式,因为在.NET 4.0中,微软又提供了更简单的异步编程实现方式--TAP,基于任务的异步模式。
该模式主要使用System.Threading.Tasks命名空间中的Task<T>类来实现异步编程,所以在采用TAP之前,首先要引入System.Threading.Tasks命名空间。
基于任务的异步模式(TAP,Task-based Asynchronous Pattern)只使用一个方法就能表示异步操作的开始和完成,而APM却需要Beginxxx和Endxxx两个方法分别表示开始和结束,EAP则要求具有以Async为后缀的方法和一个或多个事件。在基于任务的异步模式中,只需要一个以TaskAsync为后缀的方法,通过向该方法传入CancellationToken参数,我们就可以很好地完成异步编程了。而且,还可以通过IProgress<T>接口来实现进度报告的功能。总体来说,使用TAP会减少我们的工作量,是代码更加简洁。
1 Task task=new Task(()=>{.......}); 2 task.Start();
5、让异步编程So easy——C# 5.0中的async和await
虽然.NET 1.0和.NET 2.0和.NET 4.0都对异步编程做了很好的支持,微软也逐渐地使用异步编程变得简单,但微软觉得现有的工作还不够,它希望使异步编程的开发过程更为简化,所以在.NET 4.5中,微软又提出了async和await两个关键字来支持异步编程。
这也是目前.NET Framework中最简单的异步编程实现方式,因为使用这个两个关键字进行异步编程,思考方式和实现同步编程时的完全一样。
async和await关键字不会让调用方法运行在新线程中,而是将方法分割成多个片段(片段的界限出现在方法内部使用await关键字的位置处),并使其中一些片段可以异步运行。await关键字处的代码片段是在线程池线程上运行的,而整个方法的调用确实同步的。所以,使用此方式编程不用考虑跨线程访问UI控件的问题,从而大大降低了异步编程的出错率。