第六节:深入研究Task实例方法ContinueWith的参数TaskContinuationOptions
一. 整体说明
揭秘:
该章节的性质和上一个章节类似,也是一个扩展的章节,主要来研究Task类下的实例方法ContinueWith中的参数TaskContinuationOptions。
通过F12查看TaskContinuationOptions的源码,知道主要有这么几个参数:
①. LazyCancellation:在延续取消的情况下,防止延续的完成直到完成先前的任务。
(下面的例子task2取消,原先的延续关系不复存在,task1和task3可以并行执行)
②. ExecuteSynchronously:希望执行前面那个task的thread也在执行本延续任务
(下面的例子执行task2的Thread和执行task1的Thread是同一个,所有二者的线程id相同)
③. NotOnRanToCompletion和OnlyOnRanToCompletion
NotOnRanToCompletion:延续任务必须在前面task非完成状态才能执行
OnlyOnRanToCompletion:延续任务必须在前面task完成状态才能执行
(下面例子:注释掉异常的这句代码task2不能执行,task3能执行;不注释,task2能执行,task3不能执行)
源码如下:
1 [Serializable] 2 [Flags] 3 public enum TaskContinuationOptions 4 { 5 // 摘要: 6 // Default = "Continue on any, no task options, run asynchronously" 指定应使用默认行为。 7 // 默认情况下,完成前面的任务之后将安排运行延续任务,而不考虑前面任务的最终 System.Threading.Tasks.TaskStatus。 8 None = 0, 9 // 10 // 摘要: 11 // 提示 System.Threading.Tasks.TaskScheduler 以一种尽可能公平的方式安排任务,这意味着较早安排的任务将更可能较早运行,而较晚安排运行的任务将更可能较晚运行。 12 PreferFairness = 1, 13 // 14 // 摘要: 15 // 指定某个任务将是运行时间长、粗粒度的操作。 它会向 System.Threading.Tasks.TaskScheduler 提示,过度订阅可能是合理的。 16 LongRunning = 2, 17 // 18 // 摘要: 19 // 指定将任务附加到任务层次结构中的某个父级。 20 AttachedToParent = 4, 21 // 22 // 摘要: 23 // 如果尝试附有子任务到创建的任务,指定 System.InvalidOperationException 将被引发。 24 DenyChildAttach = 8, 25 // 26 // 摘要: 27 // 防止环境计划程序被视为已创建任务的当前计划程序。 这意味着像 StartNew 或 ContinueWith 创建任务的执行操作将被视为 System.Threading.Tasks.TaskScheduler.Default 28 // 当前计划程序。 29 HideScheduler = 16, 30 // 31 // 摘要: 32 // 在延续取消的情况下,防止延续的完成直到完成先前的任务。 33 LazyCancellation = 32, 34 // 35 // 摘要: 36 // 指定不应在延续任务前面的任务已完成运行的情况下安排延续任务。 此选项对多任务延续无效。 37 NotOnRanToCompletion = 65536, 38 // 39 // 摘要: 40 // 指定不应在延续任务前面的任务引发了未处理异常的情况下安排延续任务。 此选项对多任务延续无效。 41 NotOnFaulted = 131072, 42 // 43 // 摘要: 44 // 指定只应在延续任务前面的任务已取消的情况下才安排延续任务。 此选项对多任务延续无效。 45 OnlyOnCanceled = 196608, 46 // 47 // 摘要: 48 // 指定不应在延续任务前面的任务已取消的情况下安排延续任务。 此选项对多任务延续无效。 49 NotOnCanceled = 262144, 50 // 51 // 摘要: 52 // 指定只应在延续任务前面的任务引发了未处理异常的情况下才安排延续任务。 此选项对多任务延续无效。 53 OnlyOnFaulted = 327680, 54 // 55 // 摘要: 56 // 指定只应在延续任务前面的任务已完成运行的情况下才安排延续任务。 此选项对多任务延续无效。 57 OnlyOnRanToCompletion = 393216, 58 // 59 // 摘要: 60 // 指定应同步执行延续任务。 指定此选项后,延续任务将在导致前面的任务转换为其最终状态的相同线程上运行。 如果在创建延续任务时已经完成前面的任务,则延续任务将在创建此延续任务的线程上运行。 61 // 只应同步执行运行时间非常短的延续任务。 62 ExecuteSynchronously = 524288, 63 }
二. 实际测试
下面通过代码来说明默认情况下、LazyCancellation、ExecuteSynchronously、NotOnRanToCompletion和OnlyOnRanToCompletion的作用和效果。
1. 默认情况
默认情况下,task1执行完后→task2→task2执行完后→task3。
1 { 2 Task task1 = new Task(() => 3 { 4 Thread.Sleep(1000); 5 Console.WriteLine("task1 tid={0}, dt={1}", Thread.CurrentThread.ManagedThreadId, DateTime.Now); 6 }); 7 8 var task2 = task1.ContinueWith(t => 9 { 10 Console.WriteLine("task2 tid={0}, dt={1}", Thread.CurrentThread.ManagedThreadId, DateTime.Now); 11 }); 12 13 var task3 = task2.ContinueWith(t => 14 { 15 Console.WriteLine("task3 tid={0}, dt={1} {2}", Thread.CurrentThread.ManagedThreadId, DateTime.Now, task2.Status); 16 }); 17 18 task1.Start(); 19 20 }
运行结果: task1执行完后→ task2执行→task2执行完后→ task3执行。
2. LazyCancellation
作用:取消该线程,该线程的前一个线程和后一个线程并行执行。
1 { 2 CancellationTokenSource source = new CancellationTokenSource(); 3 source.Cancel(); 4 5 Task task1 = new Task(() => 6 { 7 Thread.Sleep(1000); 8 Console.WriteLine("task1 tid={0}, dt={1}", Thread.CurrentThread.ManagedThreadId, DateTime.Now); 9 }); 10 11 var task2 = task1.ContinueWith(t => 12 { 13 Console.WriteLine("task2 tid={0}, dt={1}", Thread.CurrentThread.ManagedThreadId, DateTime.Now); 14 }, source.Token, TaskContinuationOptions.LazyCancellation, TaskScheduler.Current); 15 16 var task3 = task2.ContinueWith(t => 17 { 18 Console.WriteLine("task3 tid={0}, dt={1} {2}", Thread.CurrentThread.ManagedThreadId, DateTime.Now, task2.Status); 19 }); 20 21 task1.Start(); 22 23 }
运行结果: task2线程已经被取消,task1线程和task2线程并行执行。
3. ExecuteSynchronously
作用:希望执行前面那个task的thread也在执行本延续任务。
1 { 2 Task task1 = new Task(() => 3 { 4 Thread.Sleep(1000); 5 Console.WriteLine("task1 tid={0}, dt={1}", Thread.CurrentThread.ManagedThreadId, DateTime.Now); 6 }); 7 8 var task2 = task1.ContinueWith(t => 9 { 10 Console.WriteLine("task2 tid={0}, dt={1}", Thread.CurrentThread.ManagedThreadId, DateTime.Now); 11 }, TaskContinuationOptions.ExecuteSynchronously); 12 13 task1.Start(); 14 }
结果:task1和task2线程的线程id相同。
4. NotOnRanToCompletion和OnlyOnRanToCompletion
NotOnRanToCompletion:延续任务必须在前面task非完成状态才能执行。
OnlyOnRanToCompletion:延续任务必须在前面task完成状态才能执行。
{ Task task1 = new Task(() => { Thread.Sleep(1000); Console.WriteLine("task1 tid={0}, dt={1}", Thread.CurrentThread.ManagedThreadId, DateTime.Now); //手动制造异常,表示不能执行完毕 //(注释掉这句话task2不能执行,task3能执行) //不注释,task2能执行,task3不能执行 //throw new Exception("hello world"); }); var task2 = task1.ContinueWith(t => { Console.WriteLine("task2 tid={0}, dt={1}", Thread.CurrentThread.ManagedThreadId, DateTime.Now); }, TaskContinuationOptions.NotOnRanToCompletion); var task3 = task1.ContinueWith(t => { Console.WriteLine("task3 tid={0}, dt={1}", Thread.CurrentThread.ManagedThreadId, DateTime.Now); }, TaskContinuationOptions.OnlyOnRanToCompletion); task1.Start(); }
分析:task2和task3均为task的延续线程,当task1报错时候,task2执行,task3不能执行;当task1正常时候,task2不能执行,task3能执行。
task1报错时的运行结果:
task2正常时的运行结果: