Task.ContinueWith
这货,和 await 一样有“陷阱”。^^,因为写 ContinueWith 不能直观如人的“过程性”思维,写在 ContinueWith 括号里的部分不一定只在发起 ContinueWith 的任务完成后完成,比如这样:
1 namespace TaskConsole 2 { 3 class Program 4 { 5 static void Main(string[] args) 6 { 7 while (true) 8 { 9 var enter = Console.ReadLine(); 10 11 if (enter.ToLower() == "continue") { Entry(); } 12 else { break; } 13 } 14 } 15 16 17 async static void DoWork(string workerName) 18 { 19 await Task.Delay(1000); 20 Console.WriteLine("{0} is working @ Thread Id {1}", workerName, System.Threading.Thread.CurrentThread.ManagedThreadId); 21 } 22 23 async static void DoClean(string cleaner) 24 { 25 await Task.Delay(1000); 26 Console.WriteLine("{0} is cleaning @ Task Id {1}", cleaner, Task.CurrentId); 27 } 28 29 async static Task Entry() 30 { 31 var task = Task.Factory.StartNew(() => DoWork("工作者1")). 32 ContinueWith(backer => DoClean("清理者1")). 33 ContinueWith(backer => DoClean("清理者2")). 34 ContinueWith(backer => DoClean("清理者3")); 35 } 36 } 37 }
运行一下试试?如果过程中没有 await Task.Delay 参与,看起来基本是按顺序的,但是这句一加上就立马露陷了,看
那如何让 ContinueWith 按我们的心思走呢?对了,还有其它参数可以用,让我们随便试试
1 var task = Task.Factory.StartNew(() => DoWork("工作者1")). 2 ContinueWith(backer => DoClean("清理者1"), TaskContinuationOptions.NotOnRanToCompletion). 3 ContinueWith(backer => DoClean("清理者2"), TaskContinuationOptions.OnlyOnRanToCompletion). 4 ContinueWith(backer => DoClean("清理者3"), TaskContinuationOptions.OnlyOnRanToCompletion);
运行一下,咦,发生了什么?
为什么清理者都不工作了?问题出在参数 【TaskContinuationOptions.NotOnRanToCompletion】,它起了作用,它告诉第一个清理者“当工作者任务不是正常完成时你才能启动”,所以清理者1就没有机会启动,它没有机会启动,后面的清理者2、清理者3自然都没有办法启动。到这里只知道 ContinueWith 还有参数可以利用,但是没有达到我们的目的,继续!先把两个 void 方法改造一下
1 async static Task DoWork(string workerName) 2 { 3 await Task.Delay(1000); 4 Console.WriteLine("{0} is working @ Thread Id {1}", workerName, System.Threading.Thread.CurrentThread.ManagedThreadId); 5 } 6 7 async static Task DoClean(string cleaner) 8 { 9 await Task.Delay(1000); 10 Console.WriteLine("{0} is cleaning @ Task Id {1}", cleaner, Task.CurrentId); 11 }
变成了 Task 的方法,就有了控制的前提条件,接下来改造一下 ContinueWith 的流程
1 { 2 var taskWorker = Task.Factory.StartNew(() => DoWork("工作者1")); 3 await taskWorker.Result; 4 var c1 = await taskWorker.ContinueWith(backer => DoClean("清理者1"), TaskContinuationOptions.OnlyOnRanToCompletion); 5 var c2 = await c1.ContinueWith(backer => DoClean("清理者2"), TaskContinuationOptions.OnlyOnRanToCompletion); 6 var c3 = await c2.ContinueWith(backer => DoClean("清理者3"), TaskContinuationOptions.OnlyOnRanToCompletion); 7 }
把原来写在一起的一堆 ContinueWith 分开,让它们各自“拥有身份”,并且按顺序去继承上一个 Task,只有当指定的 Task 执行完成到 RanToCompletion 的状态时自身才被启用,并且 await 的使用保证了 taskWorker 运行完成 C1 才会执行,C1 运行完 C2 才会执行,C2 运行完 C3 才会执行。到此,似乎实现了我们的要求。