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 位枚举的更多相关文章

  1. 你必须知道的.Net 8.4.4 位枚举

    位标记集合是一种由组合出现的元素形成的列表,通常设计为以“位或”运算组合新值:枚举 类型则通常表达一种语义相对独立的数值集合.而以枚举类型来实现位标记集合是最为完美的组 合,简称为位枚举.在.NET ...

  2. 【C#Task】TaskCreationOptions 枚举

    根据 TaskCreationOptions 的不同,出现了三个分支 LongRunning:独立线程,和线程池无关 包含 PreferFairness时:preferLocal=false,进入全局 ...

  3. 数位DP按位枚举模板

    借鉴:http://www.cnblogs.com/xz816111/p/4809913.html // pos = 当前处理的位置(一般从高位到低位) // pre = 上一个位的数字(更高的那一位 ...

  4. uva1354 天平难题 【位枚举子集】||【huffman树】

    题目链接:https://vjudge.net/contest/210334#problem/G 转载于:https://blog.csdn.net/todobe/article/details/54 ...

  5. Optimal Marks SPOJ - OPTM (按位枚举-最小割)

    题意:给一张无向图,每个点有其点权,边(i,j)的cost是\(val_i\ XOR \ val_j\).现在只给出K个点的权值,求如何安排其余的点,使总花费最小. 分析:题目保证权值不超过32位整型 ...

  6. hdu4421 2-sat(枚举二进制每一位)

    题意:       给你一个数组b[][],在给你一些关系,问是否可以找到一个满足限制的a[], 关系如下(图片): 思路:       说到限制,而且还是两个两个之间的限制,那么很容易想到2-sat ...

  7. C# 线程知识--使用Task执行异步操作

    在C#4.0之前需要执行一个复杂的异步操作时,只能使用CLR线程池技术来执行一个任务.线程池执行异步任务时,不知道任务何时完成,以及任务的在任务完成后不能获取到返回值.但是在C#4.0中引人了一个的任 ...

  8. CodeForces 165E Compatible Numbers(位运算 + 好题)

    wo integers x and y are compatible, if the result of their bitwise "AND" equals zero, that ...

  9. 线程(Thread,ThreadPool)、Task、Parallel

    线程(Thread.ThreadPool) 线程的定义我想大家都有所了解,这里我就不再复述了.我这里主要介绍.NET Framework中的线程(Thread.ThreadPool). .NET Fr ...

随机推荐

  1. vs python2.7 bug

    微软vs里面小细节的bug真他妈的多

  2. gin框架中请求参数的绑定与多数据格式处理

    package main import ( "fmt" "github.com/gin-gonic/gin" ) // gin框架提供给开发者表单实体绑定的功能 ...

  3. Tomcat-给Tomcat添加第三方jar包

    给动态web工程添加额外jar包 1,打开项目结构菜单操作界面,添加一个自己的类库 2,添加你当前类库需要的jar包 3,选择你添加的类库,给哪个模块使用 4,选择Artifacts选项,将类库添加到 ...

  4. glibc-2.18升级

    1.下载文件下载地址:https://mirrors.tuna.tsinghua.edu.cn/gnu/glibc/glibc-2.18.tar.gz 2.安装部署解压tar -zxvf glibc- ...

  5. ApacheCN JavaScript 译文集 20211122 更新

    JavaScript 编程精解 中文第三版 零.前言 一.值,类型和运算符 二.程序结构 三.函数 四.数据结构:对象和数组 五.高阶函数 六.对象的秘密 七.项目:机器人 八.Bug 和错误 九.正 ...

  6. 布客&#183;ApacheCN 编程/后端/大数据/人工智能学习资源 2020.7

    公告 我们的群共享文件有备份到 IPFS 的计划,具体时间待定. 我们的机器学习群(915394271)正式改名为财务提升群,望悉知. 请关注我们的公众号"ApacheCN",回复 ...

  7. 布客&#183;ApacheCN 编程/大数据/数据科学/人工智能学习资源 2020.2

    特约赞助商 公告 我们愿意普及区块链技术,但前提是互利互惠.我们有大量技术类学习资源,也有大量的人需要这些资源.如果能借助区块链技术存储和分发,我们就能将它们普及给我们的受众. 我们正在招募项目负责人 ...

  8. 布客&#183;ApacheCN 编程/大数据/数据科学/人工智能学习资源 2020.1

    公告 我们正在招募项目负责人,完成三次贡献可以申请,请联系片刻(529815144).几十个项目等你来申请和参与,不装逼的朋友,我们都不想认识. 薅资本主义羊毛的 CDNDrive 计划正式启动! 我 ...

  9. Argo 安装和 workflow 实例配置文件解析

    一.Argo 安装配置 1.1 Argo 安装 $ kubectl create ns argo $ kubectl apply -n argo -f https://raw.githubuserco ...

  10. Struts2中的过滤器

    过滤器:            过滤器可以处理用户的请求和程序响应的内容,可用于权限控制.编码转换的场合.过滤器是servlet规范中的一部分,不是只有Struts2有. 使用过滤器之前你得定义一个过 ...