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. 看看CSDN的吃相

    大伙快来看看CSDN的吃相.

  2. Python webargs 模块

    一.安装 python3 -m pip install webargs 文档 二.基础特性 # encoding=utf-8 from flask import Flask from webargs ...

  3. pytest文档1-环境搭建

    1.安装方法 pip install -U pytest 2.查看安装版本 pip show pytest pytest -version 3.快速开始 新建test开头py文件 打开test_sam ...

  4. Autofac实现拦截器和切面编程

    Autofac.Annotation框架是我用.netcore写的一个注解式DI框架,基于Autofac参考 Spring注解方式所有容器的注册和装配,切面,拦截器等都是依赖标签来完成. 开源地址:h ...

  5. docker内存限制

    默认docker容器可以使用宿主机所有的内存和CPU,我们可以通过 docker run 来限制内存和CPU的使用. 有的系统内核不支持 Linux capabilities. 可以通过 docker ...

  6. Java线上问题排查神器Arthas实战分析

    概述 背景 是不是在实际开发工作当中经常碰到自己写的代码在开发.测试环境行云流水稳得一笔,可一到线上就经常不是缺这个就是少那个反正就是一顿报错抽风似的,线上调试代码又很麻烦,让人头疼得抓狂:而且deb ...

  7. Codeforces Round #740 Div. 2

    题目跳转链接 A. Simply Strange Sort 题意 定义一个函数\(f_{i}\) : 如果\(a_i \ge a_{i+1}\) swap(\(a_i\) \(a_{i+1}\)) 定 ...

  8. 理解java线程的中断(interrupt)

    一个线程在未正常结束之前, 被强制终止是很危险的事情. 因为它可能带来完全预料不到的严重后果比如会带着自己所持有的锁而永远的休眠,迟迟不归还锁等. 所以你看到Thread.suspend, Threa ...

  9. JS字符串去替换元素再转换成数组

    原创:转载需注明原创地址 https://www.cnblogs.com/fanerwei222/p/11648074.html JS字符串替换不必要的元素, 然后去除多余的空格, 转换成数组: va ...

  10. cloudstack-4.1.5版本最全入门笔记【2022】

    cloudstack简介 CloudStack是一个开源的具有高可用性及扩展性的云计算平台.目前Cloudstack支持管理大部分主流的hypervisors,如KVM,XenServer,VMwar ...