C# Task TaskContinuationOption 位枚举

原文链接:点这里

TaskContinuationOptions

根据 TaskContinuationOptions 的不同,出现了三个分支

  • LongRunning:独立线程,和线程池无关
  • 包含 PreferFairness 时:preferLocal = false,进入全局队列
  • 不包含 PreferFairness 时:preferLocal = true ,进入本地 队列

进入全局队列的任务能够公平地被各个线程池中的线程领取执行,也就是 prefer fairness这个词组的字面意思了。

下图中 Task666先进入全局队列,随后被Thread1 领走。Thread3 通过 WorkSteling 机制窃取了 Thread2 中的Task2。

[Flags,serializable]
public enum TaskContinuationOptions(
    
None=0x0000,
    
//将当前任务生成的子任务,安排到全局任务,不直接安排到本地任务队列。
//提示 System.Threading.Tasks.TaskScheduler 以一种尽可能公平的方式安排任务,这意味着较早安排的任务将更可能较早运行,而较晚安排运行的任务将更可能较晚运行。
PreferFairness=0x0001,

//指定任务将是长时间运行的、粗粒度的操作。 它会向 System.Threading.Tasks.TaskScheduler
//提示,过度订阅可能是合理的。 
LongRunning=0x0002,

//任务是之间是没有父子关系的,但是该枚举项可以实现任务之间的
//父子关系:将一个任务内部创建的子任务添加AttachedToParent枚举建立父子关系。 
//将一个Task和它的父Task关联(稍后讨论)。子任务和父任务并不一定运行在同一线程上。
//父子关系是为了解释任务状态和捕获子任务异常
AttachedToParent=0x0004,

//指定任何使用 System.Threading.Tasks.TaskCreationOptions.AttachedToParent 选项创建,并尝试作为附加的子任务
//执行的子任务(即,由此延续创建的任何嵌套内部任务)都无法附加到父任务,会改成作为分离的子任务执行。
DenyChildAttach = 0x0008,

//任务试图和这个父任务连接将抛出一个 InvalidOperationExceptionDenychildAttach=oxo008,
//强迫子任务使用默认调度器而不是父任务的调度器
Hidescheduler=Ox0010,

// 直到完成先前的任务,在执行取消后续任务
Lazycancellation=0xO020,

//默认 指定应异步运行延续任务。 此选项优先于 ExecuteSynchronously。
//RunContinuationsAsynchronously 成员在 TaskCreationOptions 从 .NET Framework 4.6 开始的枚举中可用。
RunContinuationsAsynchronously = 0x0040,

//这些标志指出在什么情况下运行Continuewith任务
NotOnRanToCompletion =0x10000,
NotOnFaulted=Ox20000,
NotOnCanceled=0x40000,

//这些标志是以上三个标志的便利组合
OnlyOnCanceled=NotOnRanToCompletion | NotOnFaulted,    //0x30000
OnlyOnFaulted= NotOnRanToCompletion | NotOnCanceled,    //0x50000
OnlyOnRanToCompletion = NotOnFaulted | NotonCanceled,    //0x60000
    
//这个标志指出你希望由执行第一个任务的线程执行
//Continuewith任务。第一个任务完成后,调用
//Continuewith的线程接着执行ContinueWith任务
ExecuteSynchronously=0x80000,

 

TaskContinuationOptions.AttachedToParent 

一个任务创建的一个或多个Task对象默认是顶级任务,它们与创建它们的任务无关。但TaskCreationOptions.AttachedToParent标志将一个Task和创建它的Task关联,结果是除非所有子任务(以及子任务的子任务)结束运行,否则创建任务(父任务)不认为已经结束。调用ContinueWith方法创建Task时,可指定TaskContinuationOptions.AttachedToParent标志将延续任务指定成子任务。

static void Main(string[] args)
        {
            /*只要在任务A中创建的任务(a、b、c、包括b的ContinueWith创建的任务) 都是顶级任务,只有当a、b、c等设定了AttachedToParent才是A的子任务。
             * 1、具有 TaskCreationOptions.AttachedToParent或TaskContinuationOptions.AttachedToParent特性的任务都依附与它的父任务。
             * 2、如果父任务的TaskCreationOptions不为DenyChildAttach,子任务的AttachedToParent就起作用。
             * 3、以下实列父亲任务默认的TaskCreationOptions为None,因此子任务的AttachedToParent就起作用。
             * 4、如果父任务已经完成,但是在依附与父任务的子任务还未完成 ,那么此时父任务就处于WaitingForChildrenToComplete。
             * 5、只有当依附与父任务的子任务和父任务都完成时,父任务的状态才会变成RanToCompletion。
             * 6、RanToCompletion=【依附于父任务的子任务RanToCompletion】+【父任务RanToCompletion】
             * 7、子任务和父任务并不一定运行在同一线程上。父子关系是为了解释任务状态和捕获子任务异常
             * */
            Task taskparent = new Task(() => {
                Task subtask1 = new Task(() => {
                    Console.WriteLine("subtask1 不和taskparent关联,等subtask1结束后执行");
                    Console.WriteLine("subtask1 开始执行,睡觉0.01s ");
                    Thread.Sleep(10);
                    Console.WriteLine("subtask1 睡醒了 ");
                });
                //ContinueWith方式创建的子任务,它不依附于父任务taskparent,是个独立的任务,因此父任务不需要等待它完成。
                //Task subtask2 = subtask1.ContinueWith(task => {
                //    Console.WriteLine("subtask2 不和subtask1关联,等subtask1结束后执行");
                //    Console.WriteLine("subtask2 开始执行,睡觉2s ");

                //    Thread.Sleep(2000);
                //    Console.WriteLine("subtask2  睡醒了 ");
                //});

                //ContinueWith方式创建子任务 
                //它设置了依附于父任务taskparent(不是subtask1),如果父任务的TaskCreationOptions
                //不为DenyChildAttach。 那么他就起作用,父任务要等待它一起完成后,才会把状态修改为RanToCompletion,
                //如果父任务提取完成,在等候的子任务期间父任务状态是WaitingForChildrenToComplete
                Task subtask3 = subtask1.ContinueWith(task =>
                {
                    Console.WriteLine("subtask3  和subtask1关联,等subtask1结束后执行");
                    Console.WriteLine("subtask3 开始执行,睡觉0.5s ");
                    Thread.Sleep(500);
                    Console.WriteLine("subtask3  睡醒了 ");
                }, TaskContinuationOptions.AttachedToParent);
                subtask1.Start();
                Console.WriteLine(subtask1.CreationOptions);

            });

            taskparent.Start();
            while (!taskparent.IsCompleted)
            {

                Console.WriteLine(taskparent.Status);

            }
            Console.WriteLine(taskparent.Status);
            Console.WriteLine("主线程完成");
            Console.Read();
        }

 总的来说,设置了AttachedToParent,只要起作用,父任务就要等子任务完成才能完成

 

TaskCreationOptions.DenyChildAttach

(不收儿子)拒绝任何子任务依附于它。所有的子任务中设置的TaskCreationOptions.AttachedToParent或TaskContinuationOptions.AttachedToParent属性都对他无效。它不会等待任何子任务,只要它自己的任务完成,它的状态就会变成RanToCompletion。

 

        static void Main(string[] args)
        {
            Task taskparent = new Task(() => {
                Task subtask1 = new Task(() => {
                    Console.WriteLine("subtask1 不和taskparent关联,等subtask1结束后执行");
                    Console.WriteLine("subtask1 开始执行,睡觉0.1s ");
                    Thread.Sleep(100);
                    Console.WriteLine("subtask1 睡醒了 ");
                });

                //ContinueWith方式创建的子任务,它依附于父任务taskparent(不是subtask1),如果父任务的TaskCreationOptions不为DenyChildAttach。那么他就起作用。
                Task subtask3 = subtask1.ContinueWith(task => {
                    Console.WriteLine("subtask3  和subtask1关联,等subtask1结束后执行");
                    Console.WriteLine("subtask3 开始执行,睡觉0.5s ");
                    Thread.Sleep(500);
                    Console.WriteLine("subtask3  睡醒了 ");
                }, TaskContinuationOptions.AttachedToParent);
                subtask1.Start();
                Console.WriteLine(subtask1.CreationOptions);
                //不会等到任何子任务,即使子任务设置了TaskCreationOptions.AttachedToParent或TaskContinuationOptions.AttachedToParent属性
            }, TaskCreationOptions.DenyChildAttach);

            taskparent.Start();
            while (!taskparent.IsCompleted)
            {

                Console.WriteLine(taskparent.Status);

            }
            Console.WriteLine(taskparent.Status);
            Console.Read();
        }

DenyChildAttach就是为了让让子任务的AttachedToParent失效

 

 TaskContinuationOptions.Hidescheduler

强迫子任务使用默认调度器而不是父任务或第一个任务的调度器,除了Task.Run()模式使用的默认的线程池调度器,task.start()和TaskFactory.StartNew() 都使用 Current Taskcheduler。所以在任务内创建子任务会继承上一级任务的任务调度器。

        static void Main(string[] args)
        {
            Task taskparent = new Task(() => {
                //第一次嵌套
                Task Employer1 = new Task(() => {
                    Console.WriteLine($"使用父任务的调度器 ");
                    Console.WriteLine($"Employer1 Current TaskScheduler:{TaskScheduler.Current}");


                });
                // 拒绝使用父类的Scheduler调度器,使用默认的线程池任务调度器ThreadPoolTaskScheduler
                Task Employer3 = Employer1.ContinueWith(task => {
                    Console.WriteLine($"拒绝使用父任务的调度器,使用默认的线程池任务调度器");
                    Console.WriteLine($"Employer3 Current TaskScheduler:{TaskScheduler.Current}");

                    //第二次嵌套

                    Task Employer2 = new Task(() => {
                        Console.WriteLine($"使用父任务的调度器2 ");
                        Console.WriteLine($"Employer2 Current TaskScheduler:{TaskScheduler.Current}");
                    }); Employer2.Start();

                }, TaskContinuationOptions.HideScheduler);
                Employer1.Start();



            });

            //使用自己预定义的调度器
            taskparent.Start(new PerThreadTaskScheduler());


            Console.WriteLine(taskparent.Status);
            Console.Read();
            /*输出
             *  
            WaitingToRun
            使用父任务的调度器
            Employer1 Current TaskScheduler:PerThreadTaskScheduler
            拒绝使用父任务的调度器,使用默认的线程池任务调度器
            Employer3 Current TaskScheduler:System.Threading.Tasks.ThreadPoolTaskScheduler
            使用父任务的调度器
            Employer2 Current TaskScheduler:System.Threading.Tasks.ThreadPoolTaskScheduler
             */
        }
自定义调度器
     public class PerThreadTaskScheduler : TaskScheduler
    {
        protected override IEnumerable<Task> GetScheduledTasks()
        {
            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();
        }
    }

 TaskContinuationOptions.HideScheduler这一个参数就是放弃父任务的调度器,使用默认的。

 

TaskContinuationOptions.ExecuteSynchronously 

ContinueWith和第一个任务线程同一个线程

        static void Main(string[] args)
        {

            Task taskParent = new Task(() =>
            {
                //Environment当前环境和平台的信息以及操作它们的方法
                Console.WriteLine($"taskParent CurrentId   is  {Task.CurrentId} And Thread{Environment.CurrentManagedThreadId}");
                //Console.WriteLine($"taskParent CurrentId   is  {Task.CurrentId} And Thread{Thread.CurrentThread.ManagedThreadId}");
            });

            Task tasktest = taskParent.ContinueWith(tas =>
            {
                Console.WriteLine($"taskParent Status is : {taskParent.Status} and TaskScheduler is {TaskScheduler.Current} ");
                Console.WriteLine($"Continue task CurrentId   is  {Task.CurrentId} And Thread{Environment.CurrentManagedThreadId}");
            },
            //使用相同线程执行
            TaskContinuationOptions.ExecuteSynchronously | TaskContinuationOptions.OnlyOnRanToCompletion
            );


            taskParent.Start();
            Console.Read();

            //输出
            //taskParent CurrentId   is  2 And Thread3
            //taskParent Status is : RanToCompletion and TaskScheduler is System.Threading.Tasks.ThreadPoolTaskScheduler
            //Continue task CurrentId   is  1 And Thread3

        }

 

OnlyOnxxx、NotOnxxx

在特定条件、不在特定条件下会继续执行的,思路用法都是一样的

        static void Main(string[] args)
        {
            CancellationTokenSource cts = new CancellationTokenSource();
            //任务取消时要调用的委托
            cts.Token.Register(() => Console.WriteLine($"临时请假"));

            Task taskparent = new Task(() => {
                Task Employer1 = new Task(() => {

                    while (!cts.Token.IsCancellationRequested)
                    {
                        Thread.Sleep(2000);
                        throw new Exception();
                        //try
                        //{
                        //    cts.Token.ThrowIfCancellationRequested();
                        //}
                        //finally { }
                    }

                    //传入取消令牌,执行continue任务 时候要调用该令牌

                }, cts.Token);

                Task Employer2 = Employer1.ContinueWith(task => {
                    //nameof(Employer1) 防止变量名修改时候 忘记修改字符总的字符串了,nameof()返回名称的字符串,编译的时候完成
                    Console.WriteLine($"{nameof(Employer1)} 请假了,{nameof(Employer2)}代替{nameof(Employer1)}工作");
                    Console.WriteLine("Employer2 开始 工作   ");

                    Thread.Sleep(3000);
                    Console.WriteLine($"{nameof(Employer2)}完成了剩下的工作 ");
                    // 只有当Employer1任务 取消时候 ,Employer2任务才开始运行
                }, TaskContinuationOptions.OnlyOnCanceled);

                Task Employer3 = Employer1.ContinueWith(task => {

                    Thread.Sleep(2000);
                    Console.WriteLine("Employer3  睡醒了 ");
                    // 只有当Employer1完成时, 该任务才开始运行
                }, TaskContinuationOptions.AttachedToParent | TaskContinuationOptions.OnlyOnRanToCompletion);
                Employer1.Start();

                Task Employer4 = Employer1.ContinueWith(task => {
                    Console.WriteLine($"{nameof(Employer1)} 请假了,{nameof(Employer4)}代替{nameof(Employer1)}工作");
                    Console.WriteLine("Employer4 开始 工作   ");

                    Thread.Sleep(3000);
                    Console.WriteLine($"{nameof(Employer4)}完成了剩下的工作 ");
                    // 只有当Employer1任务 出异常时候 ,Employer4任务才开始运行
                }, TaskContinuationOptions.OnlyOnFaulted/*NotOnRanToCompletion */);

                 cts.Cancel();

            });

            taskparent.Start();
            Console.WriteLine(taskparent.Status);
            Console.Read();

            //输出
            //WaitingToRun
            //临时请假
            //Employer1 请假了,Employer2代替Employer1工作
            //Employer2 开始 工作
            //Employer2完成了剩下的工作
        }

 

TaskContinuationOptions.LongRunning使用案例

启用一个后台线程,不属于线程池的线程

        static void Main(string[] args)
        {
            TaskFactory LongTask = new TaskFactory(TaskCreationOptions.LongRunning, TaskContinuationOptions.AttachedToParent);
            //PreferFairness 尽可能公平,早创建早开始,但不能确保
            TaskFactory preferTask = new TaskFactory(TaskCreationOptions.PreferFairness, TaskContinuationOptions.AttachedToParent);
            LongTask.StartNew(() => {
                Console.WriteLine("Thread.CurrentThread.IsThreadPoolThread:" + Thread.CurrentThread.IsThreadPoolThread);
                Console.WriteLine("Thread.CurrentThread.IsBackground:" + Thread.CurrentThread.IsBackground);

            });

            Console.ReadKey();
            /*
            Thread.CurrentThread.IsThreadPoolThread:False
            Thread.CurrentThread.IsBackground:True
            */
        }

 

TaskContinuationOptions.PreferFairness

目前只能理解是作用是尽量让线程公平,先创建先开始,但是不能确保

 

TaskContinuationOptions.Lazycancellation

等待先前任务完成后,再取消ContinueWith。如果后续任务未添加TaskContinuationOptions.LazyCancellation,那么还未等先前任务完成,后续就取消了。

        static void Main(string[] args)
        {
            var tokenSource = new CancellationTokenSource();
            tokenSource.Cancel();
            Task t1 = new Task(() =>
            {
                Thread.Sleep(1000);
                Console.WriteLine("t1 end");
            });
            var t2 = t1.ContinueWith((t) =>
            {
                Thread.Sleep(1000);
                Console.WriteLine("t2 end");
            }, tokenSource.Token, TaskContinuationOptions.LazyCancellation, TaskScheduler.Current);
            var t3 = t2.ContinueWith((t) =>
            {
                Console.WriteLine("t3 end");
            });

            t1.Start();

            //执行结果:t1 end、t3 end
            //如果不加TaskContinuationOptions.LazyCancellation,执行结果是:t3 end、t1 end
        }

 

TaskContinuationOptions.RunContinuationsAsynchronously

指定应异步运行延续任务。 此选项优先于 System.Threading.Tasks.TaskContinuationOptions.ExecuteSynchronously。

posted @   xunzf  阅读(123)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 单元测试从入门到精通
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律
点击右上角即可分享
微信分享提示