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。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 单元测试从入门到精通
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律