【C#Task】TaskCreationOptions 枚举
在使用Task.Factory.StartNew或者Task.Factory.FromAsync方法创建任务时,一些重载方法允许提供TaskCreationOptions来向调度器提示任务的调度方案。这里简要介绍了AttachedToParent、DenyChildAttach、HideScheduler、LongRunning、PreferFairness五种选项的具体行为。
AttachedToParent
在一个Task中创建另一个Task时,爸爸Task通常不会等待儿子Task结束。例如:
using System; using System.Threading; using System.Threading.Tasks; public class Example { public static void Main() { var parent = Task.Factory.StartNew(() => { Console.WriteLine("Outer task executing."); var child = Task.Factory.StartNew(() => { Console.WriteLine("Nested task starting."); Thread.SpinWait(500000); Console.WriteLine("Nested task completing."); }); }); parent.Wait(); Console.WriteLine("Outer has completed."); } } // The example produces output like the following: // Outer task executing. // Nested task starting. // Outer has completed. // Nested task completing.
相应地,使用TaskCreationOptions.AttachedToParent创建的儿子Task则带有以下3个特点(这3个特点也是默认情况下创建的儿子Task不具备的):
- 爸爸Task会等待儿子Task结束。
- 爸爸Task会捕获儿子Task的Exception。
- 爸爸Task的执行状态取决于儿子Task的执行状态。
例如,上面的代码使用TaskCreationOptions.AttachedToParent,则会得到以下的输出:
using System; using System.Threading; using System.Threading.Tasks; public class Example { public static void Main() { var parent = Task.Factory.StartNew(() => { Console.WriteLine("Parent task executing."); var child = Task.Factory.StartNew(() => { Console.WriteLine("Attached child starting."); Thread.SpinWait(5000000); Console.WriteLine("Attached child completing."); }, TaskCreationOptions.AttachedToParent); }); parent.Wait(); Console.WriteLine("Parent has completed."); } } // The example displays the following output: // Parent task executing. // Attached child starting. // Attached child completing. // Parent has completed.
DenyChildAttach
如果你不希望一个Task的启动的儿子们Attach到它自己身上,则可以在启动爸爸Task时为它指定TaskCreationOptions.DenyChildAttach。当通过DenyChildAttach启动的爸爸Task试图指定AttachedToParent来启动儿子Task时,AttachedToParent将会失效。
HideScheduler
当指定TaskCreationOptions.HideScheduler时,创建Task里再创建的儿子Task将使用默认的TaskScheduler,而不是当前的TaskScheduler。这相当于在创建Task时隐藏了自己当前的TaskScheduler。对于本身就是在默认的TaskScheduler里创建的Task,这个选项似乎没什么用。
using System.Reflection; Task tasktest = new(() => { Console.WriteLine($"test Task TaskScheduler is {TaskScheduler.Current}"); }); Task taskParent = new(() => { //这边不能使用Task.Run 因为它已经配置配置好了,用的是线程池任务调度器。 Task subtask=new(() => {
//这边直接使用了 父任务的任务调度器 Console.WriteLine($"sub Task TaskScheduler is {TaskScheduler.Current}"); }); subtask.Start();//这边使用的是当前线程,所以继承了父任务的 任务调度器 Console.WriteLine($"main Task TaskScheduler is {TaskScheduler.Current}"); Task subtask2 = new(() => { //因为隐藏父任务的任务调度器,所以采用了默认的线程池调度器 Console.WriteLine($"sub Task2 TaskScheduler is {TaskScheduler.Current}"); },TaskCreationOptions.HideScheduler);// 隐藏父类的任务调度器 subtask2.Start(); Console.WriteLine($"main Task TaskScheduler is {TaskScheduler.Current}"); }); tasktest.Start(); taskParent.Start(new PerThreadTaskScheduler());//自定义的任务调度器 taskParent.Wait(); Console.WriteLine("all complete"); Console.Read(); /* 输出: test Task TaskScheduler is System.Threading.Tasks.ThreadPoolTaskScheduler main Task TaskScheduler is PerThreadTaskScheduler sub Task TaskScheduler is PerThreadTaskScheduler main Task TaskScheduler is PerThreadTaskScheduler sub Task2 TaskScheduler is System.Threading.Tasks.ThreadPoolTaskScheduler all complete */
自定义的任务调度器
public class PerThreadTaskScheduler : TaskScheduler { public string Name => "PerThreadTaskScheduler "; protected override IEnumerableGetScheduledTasks() { return null; } protected override void QueueTask(Task task) { var thread = new Thread(() => { TryExecuteTask(task); }); thread.Start(); } protected override bool TryExecuteTaskInline(Task task, bool taskWasPreviouslyQueued) { throw new NotImplementedException(); } }
LongRunning
C#启动的Task都会通过TaskScheduler来安排执行。根据官方文档的描述:
The default scheduler for the Task Parallel Library and PLINQ uses the .NET Framework thread pool, which is represented by the ThreadPool class, to queue and execute work. The thread pool uses the information that is provided by the Task type to efficiently support the fine-grained parallelism (short-lived units of work) that parallel tasks and queries often represent.
而默认的TaskScheduler采用的是.NET线程池ThreadPool,它主要面向的是细粒度的小任务,其执行时间通常在毫秒级。线程池中的线程数与处理器的内核数有关,如果线程池中没有空闲的线程,那么后续的Task将会被阻塞。因此,如果事先知道一个Task的执行需要较长的时间,就需要使用TaskCreationOptions.LongRunning枚举指明。使用TaskCreationOptions.LongRunning创建的任务将会脱离线程池启动一个单独的线程来执行。
PreferFairness
提示TaskScheduler以一种尽可能公平的方式安排任务,这意味着较早安排的任务将更可能较早运行,而较晚安排运行的任务将更可能较晚运行。
用任务用的是线程的任务调度,线程工作者线程采用的LIFO。该枚举可以将工作者线程 用枚举PreferFairness标注的任务改成FIFO工作方式。案例:
转载自:https://www.licc.tech/article?id=53