北京白癜风的最好医院 https://wapjbk.39.net/yiyuanzaixian/bjzkbdfyy/在本系列上一篇文章[15:异步编程基础]中,我们讲到,现代应用程序广泛使用的是基于任务的异步编程模式(TAP),历史的EAP和AMP模式已经过时不推荐使用。今天继续总结一下TAP的异步操作,比如取消任务、报告进度、Task.Yield()、ConfigureAwait()和并行操作等。
虽然实际TAP编程中很少使用到任务的状态,但它是很多TAP操作机理的基础,所以下面先从任务状态讲起。
1任务状态
Task类为异步操作提供了一个生命周期,这个周期由TaskStatus枚举表示,它有如下值:
publicenumTaskStatus{Created=0,WaitingForActivation=1,WaitingToRun=2,Running=3,WaitingForChildrenToComplete=4,RanToCompletion=5,Canceled=6,Faulted=7}
其中Canceled、Faulted和RanToCompletion状态一起被认为是任务的最终状态。因此,如果任务处于最终状态,则其IsCompleted属性为true值。
手动控制任务启动
为了支持手动控制任务启动,并支持构造与调用的分离,Task类提供了一个Start方法。由Task构造函数创建的任务被称为冷任务,因为它们的生命周期处于Created状态,只有该实例的Start方法被调用才会启动。
任务状态平时用的情况不多,一般我们在封装一个任务相关的方法时,可能会用到。比如下面这个例子,需要判断某任务满足一定条件才启动:
staticvoidMain(string[]args){MyTaskt=new(()={//dosomething.});StartMyTask(t);Console.ReadKey();}publicstaticvoidStartMyTask(MyTaskt){if(t.Status==TaskStatus.Createdt.Counter10){t.Start();}else{//这里模拟计数,直到Counter10再执行Startwhile(t.Counter=10){//Dosomethingt.Counter++;}t.Start();}}publicclassMyTask
ask{publicMyTask(Actionaction):base(action){}publicintCounter{get;set;}}
同样,TaskStatus.Created状态以外的状态,我们叫它热任务,热任务一定是被调用了Start方法激活过的。
确保任务已激活
注意,所有从TAP方法返回的任务都必须被激活,比如下面这样的代码:
MyTasktask=new(()={Console.WriteLine("Dosomething.");});//在其它地方调用awaittask;
在await之前,任务没有执行Task.Start激活,await时程序就会一直等待下去。所以如果一个TAP方法内部使用Task构造函数来实例化要返回的Task,那么TAP方法必须在返回Task对象之前对其调用Start。
2任务取消
在TAP中,取消对于异步方法实现者和消费者来说都是可选的。如果一个操作允许取消,它就会暴露一个异步方法的重载,该方法接受一个取消令牌(CancellationToken实例)。按照惯例,参数被命名为cancellationToken。例如:
publicTaskReadAsync(byte[]buffer,intoffset,intcount,CancellationTokencancellationToken)
异步操作会监控这个令牌是否有取消请求。如果收到取消请求,它可以选择取消操作,如下面的示例通过while来监控令牌的取消请求:
staticvoidMain(string[]args){CancellationTokenSourcesource=new();CancellationTokentoken=source.Token;vartask=DoWork(token);//实际情况可能是在稍后的其它线程请求取消Thread.Sleep();source.Cancel();Console.WriteLine($"取消后任务返回的状态:{task.Status}");Console.ReadKey();}publicstaticTaskDoWork(CancellationTokencancellationToken){while(!cancellationToken.IsCancellationRequested){//Dosomething.Thread.Sleep(0);returnTask.CompletedTask;}returnTask.FromCanceled(cancellationToken);}
如果取消请求导致工作提前结束,甚至还没有开始就收到请求取消,则TAP方法返回一个以Canceled状态结束的任务,它的IsCompleted属性为true,且不会抛出异常。当任务在Canceled状态下完成时,任何在该任务注册的延续任务仍都会被调用和执行,除非指定了诸如NotOnCanceled这样的选项来选择不延续。
但是,如果在异步任务在工作时收到取消请求,异步操作也可以选择不立刻结束,而是等当前正在执行的工作完成后再结束,并返回RanToCompletion状态的任务;也可以终止当前工作并强制结束,根据实际业务情况和是否生产异常结果返回Canceled或Faulted状态。
对于不能被取消的业务方法,不要提供接受取消令牌的重载,这有助于向调用者表明目标方法是否可以取消。
3进度报告
几乎所有异步操作都可以提供进度通知,这些通知通常用于用异步操作的进度信息更新用户界面。
在TAP中,进度是通过IProgressT接口来处理的,该接口作为一个参数传递给异步方法。下面是一个典型的的使用示例:
staticvoidMain(string[]args){varprogress=newProgressint(n={Console.WriteLine($"当前进度:
%");});vartask=DoWork(progress);Console.ReadKey();}publicstaticasyncTaskDoWork(IProgressintprogress){for(inti=1;i=;i++){awaitTask.Delay();if(i%10==0){progress?.Report(i);};}}
输出如下结果:
当前进度:10%当前进度:20%当前进度:30%当前进度:40%当前进度:50%当前进度:60%当前进度:70%当前进度:80%当前进度:90%当前进度:%
IProgressT接口支持不同的进度实现,这是由消费代码决定的。例如,消费代码可能只关心最新的进度更新,或者希望缓冲所有更新,或者希望为每个更新调用一个操作,等等。所有这些选项都可以通过使用该接口来实现,并根据特定消费者的需求进行定制。例如,如果本文前面的ReadAsync方法能够以当前读取的字节数的形式报告进度,那么进度回调可以是一个IProgresslong接口。
publicTaskReadAsync(byte[]buffer,intoffset,intcount,IProgresslongprogress)
再如FindFilesAsync方法返回符合特定搜索模式的所有文件列表,进度回调可以提供工作完成的百分比和当前部分结果集,它可以用一个元组来提供这个信息。
publicTaskReadOnlyCollectionFileInfoFindFilesAsync(stringpattern,IProgressTupledouble,ReadOnlyCollectionListFileInfoprogress)
或使用API特有的数据类型:
publicTaskReadOnlyCollectionFileInfoFindFilesAsync(stringpattern,IProgressFindFilesProgressInfoprogress)
如果TAP的实现提供了接受IProgressT参数的重载,它们必须允许参数为空,在这种情况下,不会报告进度。IProgressT实例可以作为独立的对象,允许调用者决定如何以及在哪里处理这些进度信息。
4Task.Yield让步
我们先来看一段Task.Yield()的代码:
Task.Run(async()={for(inti=0;i10;i++){awaitTask.Yield();...}});
这里的Task.Yield()其实什么也没干,它返回的是一个空任务。那await一个什么也没做的空任务有什么用呢?
我们知道,对计算机来说,任务调度是根据一定的优先策略来安排线程去执行的。如果任务太多,线程不够用,任务就会进入排队状态。而Yield的作用就是让出等待的位置,让后面排除的任务先行。它字面上的意思就是让步,当任务做出让步时,其它任务就可以尽快被分配线程去执行。举个现实生活中的例子,就像你在排队办理业务时,好不容易到你了,但你的事情并不急,自愿让出位置,让其他人先办理,自己假装临时有事到外面溜一圈什么事也没干又回来重新排队。默默地做了一次大善人。
Task.Yield()方法就是在异步方法中引入一个让步点。当代码执行到让步点时,就会让出控制权,去线程池外面兜一圈什么事也没干再回来重新排队。
5定制异步任务后续操作
我们可以对异步任务执行完成的后续操作进行定制。常见的两个方法是ConfigureAwait和ContinueWith。
ConfigureAwait
我们先来看一段WindowsForm中的代码:
privatevoidbutton1_Click(objectsender,EventArgse){varcontent=CurlAsync().Result;...}privateasyncTaskstringCurlAsync(){using(varclient=newHttpClient()){returnawaitclient.GetStringAsync("