【C# task】TaskContinuationOptions 位枚举
TaskContinuationOptions
根据 TaskContinuationOptions 的不同,出现了三个分支
- LongRunning:独立线程,和线程池无关
- 包含 PreferFairness时:preferLocal=false,进入全局队列
- 不包含 PreferFairness时:preferLocal=ture,进入本地队列
进入全局队列的任务能够公平地被各个线程池中的线程领取执行,也是就是 prefer fairness
这个词组的字面意思了。
下图中 Task666 先进入全局队列,随后被 Thread1 领走。Thread3 通过 WorkStealing 机制窃取了 Thread2 中的 Task2。
- [Flags,serializable]
- public enum TaskContinuationOptions(
- None=ooooo, //默认
- //将当前任务生成的子任务,安排到全局任务,不直接安排到本地任务队列。
- PreferFairness=ox0001,
- //提议TaskScheduler应尽可能地创建线程池线程
- LongRunning=ox0002,
- //任务是之间是没有父子关系的,但是该枚举项可以实现任务之间的父子关系:将一个任务内部创建的子任务添加AttachedToParent枚举建立父子关系。 将一个Task和它的父Task关联(稍后讨论)。子任务和父任务并不一定运行在同一线程上。父子关系是为了解释任务状态和捕获子任务异常
- AttachedToParent=ox0004,
- //任务试图和这个父任务连接将抛出一个 InvalidOperationException
DenychildAttach=oxo008,- //强迫子任务使用默认调度器而不是父任务的调度器
- Hidescheduler=Ox0010
- // 直到完成先前的任务,在执行取消后续任务
- Lazycancellation=oxO020
- //这个标志指出你希望由执行第一个任务的线程执行
- //Continuewith任务。第一个任务完成后,调用
- //Continuewith的线程接着执行ContinueWith任务
- ExecuteSynchronously=0x80000,
//默认
指定应异步运行延续任务。 此选项优先于 ExecuteSynchronously。RunContinuationsAsynchronously 成员在 TaskCreationOptions 从 .NET Framework 4.6 开始的枚举中可用。- RunContinuationsAsynchronously ,64
- //这些标志指出在什么情况下运行Continuewith任务
- NotOnRanToCompletion =0x10000,
- NotOnFaulted=Ox20000,
- NotOnCanceled=0x40000,
- //这些标志是以上三个标志的便利组合
- OnlyonCanceled=NotOnRanToCompletion | NotOnFaulted,
- OnlyonFaulted= NotOnRanToCompletion | NotOnCanceled,
- OnlyonRanToCompletion = NotOnFaulted | NotonCanceled,
TaskContinuationOptions.AttachedToParent使用案例
一个任务创建的一个或多个 Task对象默认是顶级任务,它们与创建它们的任务无关。但 TaskCreationOptions.AttachedToParent
标志将一个Task和创建它的Task关联,结果是除非所有子任务(以及子任务的子任务)结束运行,否则创建任务(父任务)不认为已经结束。调用ContinueWith方法创建Task时,
可指定TaskContinuationOptions.AttachedToParent标志将延续任务指定成子任务(这句话看下面的例子就明白了)。----ckr via C# 第四版P625
- /*只要在任务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 subtask1 = new(() => {
- Console.WriteLine("subtask1 不和taskparent关联,等subtask1结束后执行");
- Console.WriteLine("subtask1 开始执行,睡觉0.1s ");
- Thread.Sleep(100);
- Console.WriteLine("subtask1 睡醒了 ");
- } );
- //ContinueWith方式创建的子任务,它不依附于父任务taskparent,是个独立的任务,因此父任务不需要等待它完成。
- Task subtask2 = subtask1.ContinueWith(task => {
- Console.WriteLine("subtask2 不和subtask1关联,等subtask1结束后执行");
- Console.WriteLine("subtask2 开始执行,睡觉3s ");
- Thread.Sleep(3000);
- Console.WriteLine("subtask2 睡醒了 ");
- });
- //ContinueWith方式创建子任务
- Task subtask3 = subtask1.ContinueWith(task => {
- Console.WriteLine("subtask3 和subtask1关联,等subtask1结束后执行");
- Console.WriteLine("subtask3 开始执行,睡觉3s ");
- Thread.Sleep(1000);
- Console.WriteLine("subtask3 睡醒了 ");
//它设置了依附于父任务taskparent(不是subtask1),如果父任务的TaskCreationOptions不为DenyChildAttach。
//那么他就起作用,父任务要等待它一起完成后,才会把状态修改为RanToCompletion,如果父任务提取完成,在等候的子任务期间父任务状态是WaitingForChildrenToComplete- },TaskContinuationOptions.AttachedToParent);
- subtask1.Start();
- Console.WriteLine(subtask1.CreationOptions);
- });
- taskparent.Start();
- while (!taskparent.IsCompleted)
- {
- Console.WriteLine(taskparent.Status);
- }
- Console.WriteLine(taskparent.Status);
- Console.Read();
TaskCreationOptions.DenyChildAttach
(拒收义子)拒绝任何子任务依附与它。所有它的子任务中设置的TaskCreationOptions.AttachedToParent或TaskContinuationOptions.AttachedToParent属性都对他无效。
它不会等待任何子任务,只要它自己的任务完成,它的状态就会变成RanToCompletion。
- Task taskparent = new(() => {
- Task subtask1 = new(() => {
- Console.WriteLine("subtask1 不和taskparent关联,等subtask1结束后执行");
- Console.WriteLine("subtask1 开始执行,睡觉0.1s ");
- Thread.Sleep(100);
- Console.WriteLine("subtask1 睡醒了 ");
- } );
- Task subtask2 = subtask1.ContinueWith(task => {
- Console.WriteLine("subtask2 不和subtask1关联,等subtask1结束后执行");
- Console.WriteLine("subtask2 开始执行,睡觉3s ");
- Thread.Sleep(3000);
- Console.WriteLine("subtask2 睡醒了 ");
- });
- //ContinueWith方式创建的子任务,它依附于父任务taskparent(不是subtask1),如果父任务的TaskCreationOptions不为DenyChildAttach。那么他就起作用。
- Task subtask3 = subtask1.ContinueWith(task => {
- Console.WriteLine("subtask3 和subtask1关联,等subtask1结束后执行");
- Console.WriteLine("subtask3 开始执行,睡觉3s ");
- Thread.Sleep(1000);
- 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();
TaskContinuationOptions.Hidescheduler
强迫子任务使用默认调度器而不是父任务或第一个任务的调度器
除了Task.Run()模式使用的默认的线程池调度器,task.start()和TaskFactory.StartNew()都使用Current TaskScheduler。所以在任务内创建子任务会继承上一级任务的任务调度器。
- Task taskparent = new(() => {
- //第一次嵌套
- Task Employer1 = new(() => {
- 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(() => {
- Console.WriteLine($"使用父任务的调度器 ");
- 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
- */
- PerThreadTaskScheduler类


- 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.ExecuteSynchronously 使用案例
ContinueWith和第一个任务线程同一个线程。
- using System.Reflection;
- Task taskParent = new(() =>
- {
- Console.WriteLine($"taskParent CurrentId is {Task.CurrentId} And Thread{Environment.CurrentManagedThreadId}");
- });
- Task tasktest = taskParent.ContinueWith(tas =>
- {
- Console.WriteLine($"taskParent Status is : {Enum.GetName(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 4 And Thread6
- taskParent Status is : RanToCompletion and TaskScheduler is System.Threading.Tasks.ThreadPoolTaskScheduler
- Continue task CurrentId is 3 And Thread6
- */
其他枚举用法:
- //这些标志指出在什么情况下运行Continuewith任务
- NotOnRanToCompletion =0x10000,
- NotOnFaulted=Ox20000,
- NotOnCanceled=0x40000,
- //这些标志是以上三个标志的便利组合
- OnlyonCanceled=NotOnRanToCompletion l NotOnFaulted,
- OnlyonFaulted= NotOnRanToCompletion I NotOnCanceled,
- OnlyonRanToCompletion = NotOnFaulted | NotonCanceled,
- 这些枚举的用法都一样,该案例用了OnlyonCanceled和OnlyonRanToCompletion
- CancellationTokenSource cts = new CancellationTokenSource();
- cts.Token.Register(() =>Console.WriteLine($"临时请假"));
- Task taskparent = new(() => {
- Task Employer1 = new(() => {
- while (!cts.Token.IsCancellationRequested)
- {
- Thread.Sleep(2000);
- try
- {
- cts.Token.ThrowIfCancellationRequested();
- }
- finally { }
- }
- //传入取消令牌,执行continue任务 时候要调用该令牌
- }, cts.Token);
- Task Employer2 = Employer1.ContinueWith(task => {
- //nameof(Employer1) 防止变量名修改时候 忘记修改字符总的字符窜了
- 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();
- cts.Cancel();
- } );
- taskparent.Start();
- Console.WriteLine(taskparent.Status);
- Console.Read();
- /*输出
- * 临时请假
- WaitingToRun
- 临时请假
- Employer1 请假了,Employer2代替Employer1工作
- Employer2 开始 工作
- Employer2完成了剩下的工作*/
TaskContinuationOptions.LongRunning使用案例
启用一个后台线程,不属于线程池线程。
- TaskFactory LongTask = new TaskFactory( TaskCreationOptions.LongRunning,TaskContinuationOptions.AttachedToParent);
- 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
任务并行库实现良好性能的方法之一是通过"工作窃取"。.NET 4 线程池支持工作窃取,以便通过任务并行库及其默认计划程序进行访问。这表现为线程池中的每个线程都有自己的工作队列;当该线程创建任务时,默认情况下,这些任务将排队到线程的本地队列中,而不是排队到对 ThreadPool.QueueUserWorkItem 的调用通常面向的全局队列中。当线程搜索要执行的工作时,它会从其本地队列开始,该操作由于改进了缓存局部性,最小化了争用等,从而实现了一些额外的效率。但是,这种逻辑也会影响公平性。
典型的线程池将具有单个队列,用于维护要执行的所有工作。当池中的线程准备好处理另一个工作项时,它们将从队列的头部取消排队工作,当新工作到达池中执行时,它将排队到队列的尾部。这为工作项之间提供了一定程度的公平性,因为首先到达的工作项更有可能被选中并首先开始执行。
偷工作扰乱了这种公平。池外部的线程可能正在排队工作,但如果池中的线程也在生成工作,则池生成的工作将优先于其他工作项,具体取决于池中线程(这些线程首先开始使用其本地队列搜索工作, 仅继续进入全局队列,然后继续到其他线程的队列(如果本地没有工作可用)。这种行为通常是预期的,甚至是期望的,因为如果正在执行的工作项正在生成更多工作,则生成的工作通常被视为正在处理的整体操作的一部分,因此它比其他不相关的工作更可取是有道理的。例如,想象一个快速排序操作,其中每个递归排序调用都可能导致几个进一步的递归调用;这些调用(在并行实现中可能是单个任务)是全系列排序操作的一部分。
不过,在某些情况下,这种默认行为是不合适的,其中应该在池中的线程生成的特定工作项和其他线程生成的工作项之间保持公平性。对于长链的延续,通常就是这种情况,其中生成的工作不被视为当前工作的一部分,而是当前工作的后续工作。在这些情况下,您可能希望以公平的方式将后续工作与系统中的其他工作放在一起。这就是TaskCreationOptions.PreferFairness可以证明有用的地方。
将 Task 调度到默认调度程序时,调度程序将查看任务从中排队的当前线程是否是具有自己的本地队列的 ThreadPool 线程。如果不是,则工作项将排队到全局队列。如果是,计划程序还将检查任务的 TaskCreationOptions 值是否包含"首选公平性"标志,默认情况下该标志未打开。如果设置了该标志,即使线程确实有自己的本地队列,调度程序仍将 Task 排队到全局队列,而不是本地队列。通过这种方式,该任务将与全局排队的所有其他工作项一起被公平地考虑。
刚才描述的是默认计划程序中优先公平标志的当前实现。实现当然可以更改,但不会更改的是标志的目的:通过指定 PreferFairness,您可以告诉系统不应仅仅因为此任务来自本地队列而对其进行优先级排序。您是在告诉系统,您希望系统尽最大努力确保此任务以先到先得的方式进行优先级排序。
另一件需要注意的事情是,Task本身对这面旗帜一无所知。它只是一个标志,设置为任务上的一个选项。调度程序决定了它想要如何处理这个特定的选项,就像TaskCreationOptions.LongRunning一样。默认调度程序按上述方式处理它,但另一个调度程序(例如您编写的调度程序)可以根据需要使用此标志,包括忽略它。因此,命名"首选"而不是像"保证"这样更严格的东西。
TaskContinuationOptions.Lazycancellation
等待先前任务完成后,在取消ContinueWith。如果后续任务未添加TaskContinuationOptions.LazyCancellation,那么还未等先前任务完成,后续就取消了。
- 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
【C# task】TaskContinuationOptions 位枚举的更多相关文章
- 你必须知道的.Net 8.4.4 位枚举
位标记集合是一种由组合出现的元素形成的列表,通常设计为以“位或”运算组合新值:枚举 类型则通常表达一种语义相对独立的数值集合.而以枚举类型来实现位标记集合是最为完美的组 合,简称为位枚举.在.NET ...
- 【C#Task】TaskCreationOptions 枚举
根据 TaskCreationOptions 的不同,出现了三个分支 LongRunning:独立线程,和线程池无关 包含 PreferFairness时:preferLocal=false,进入全局 ...
- 数位DP按位枚举模板
借鉴:http://www.cnblogs.com/xz816111/p/4809913.html // pos = 当前处理的位置(一般从高位到低位) // pre = 上一个位的数字(更高的那一位 ...
- uva1354 天平难题 【位枚举子集】||【huffman树】
题目链接:https://vjudge.net/contest/210334#problem/G 转载于:https://blog.csdn.net/todobe/article/details/54 ...
- Optimal Marks SPOJ - OPTM (按位枚举-最小割)
题意:给一张无向图,每个点有其点权,边(i,j)的cost是\(val_i\ XOR \ val_j\).现在只给出K个点的权值,求如何安排其余的点,使总花费最小. 分析:题目保证权值不超过32位整型 ...
- hdu4421 2-sat(枚举二进制每一位)
题意: 给你一个数组b[][],在给你一些关系,问是否可以找到一个满足限制的a[], 关系如下(图片): 思路: 说到限制,而且还是两个两个之间的限制,那么很容易想到2-sat ...
- C# 线程知识--使用Task执行异步操作
在C#4.0之前需要执行一个复杂的异步操作时,只能使用CLR线程池技术来执行一个任务.线程池执行异步任务时,不知道任务何时完成,以及任务的在任务完成后不能获取到返回值.但是在C#4.0中引人了一个的任 ...
- CodeForces 165E Compatible Numbers(位运算 + 好题)
wo integers x and y are compatible, if the result of their bitwise "AND" equals zero, that ...
- 线程(Thread,ThreadPool)、Task、Parallel
线程(Thread.ThreadPool) 线程的定义我想大家都有所了解,这里我就不再复述了.我这里主要介绍.NET Framework中的线程(Thread.ThreadPool). .NET Fr ...
随机推荐
- WebGPU相关的资源学习和社区
我在网上搜寻了很多关于WebGPU相关的资料: #我觉得首先在B站上看到的徐博士免费教程非常好,讲解详细,并且评论回复比较快,都会有回应,徐博士B站网址:https://space.bilibili. ...
- Spring Boot Starter 和 ABP Module
Spring Boot 和 ABP 都是模块化的系统,分别是Java 和.NET 可以对比的框架.模块系统是就像乐高玩具一样,一块一块零散积木堆积起一个精彩的世界.每种积木的形状各不相同,功能各不相同 ...
- gin中multipart/urlencoded绑定
package main import ( "fmt" "github.com/gin-gonic/gin" "net/http" ) ty ...
- IoC容器-Bean管理注解方式(注入属性@Autowired和Qualifier)
基于注解方式实现属性注入 (1)@Autowired:根据属性类型进行自动装配 第一步 把 service 和 dao 对象创建,在service 和 dao 类添加创建对象注解 第二步 在servi ...
- java-异常-自定义异常异常类的抛出throws
1 package p1.exception; 2 /* 3 * 对于角标是整数不存在,可以用角标越界表示, 4 * 对于负数为角标的情况,准备用负数角标异常来表示. 5 * 6 * 负数角标这种异常 ...
- windows下使用LTP分词,安装pyltp
1.LTP介绍 ltp是哈工大出品的自然语言处理工具箱, 提供包括中文分词.词性标注.命名实体识别.依存句法分析.语义角色标注等丰富. 高效.精准的自然语言处理技术.pyltp是python下对ltp ...
- Ubuntu18.04 显卡驱动安装(解决各种疑难杂症)
步骤 下载驱动 准备工作 进行安装 检查安装 下载驱动 首先我们需要去官网下载显卡驱动 打开浏览器,在百度搜索框中搜索:显卡驱动 下载 在手动搜索驱动程序一栏,根据自己的显卡进行选择 产品系列中,No ...
- ApacheCN 大数据译文集 20211206 更新
PySpark 大数据分析实用指南 零.前言 一.安装 Pyspark 并设置您的开发环境 二.使用 RDD 将您的大数据带入 Spark 环境 三.Spark 笔记本的大数据清理和整理 四.将数据汇 ...
- Github新安全措施:停止Git客户端账号密码登录的解决方案
今年 8 月 13 日之后,如果你还用账户密码来操作 Github 上的仓库,就会收到如下警告: remote: Support for password authentication was rem ...
- kicad6 封装库的管理
kicad6 封装库的管理 kicad6 的封装编辑器有很多莫名其妙的地方, 让人在第一次用的时候摸不着头脑. 在下面稍微总结一下封装库的操作 1. 封装库的创建 选择 文件 -> 新建库 有两 ...