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

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

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

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

在使用Task.Factory.StartNew或者Task.Factory.FromAsync方法创建任务时,一些重载方法允许提供TaskCreationOptions来向调度器提示任务的调度方案。这里简要介绍了AttachedToParent、DenyChildAttach、HideScheduler、LongRunning、PreferFairness五种选项的具体行为。


AttachedToParent

在一个Task中创建另一个Task时,爸爸Task通常不会等待儿子Task结束。

例如:

using System;
using System.Threading;
using System.Threading.Tasks; public class Example
{
public static void Main()
{
var parent = Task.Factory.StartNew(() => {
Console.WriteLine("Outer task executing."); var child = Task.Factory.StartNew(() => {
Console.WriteLine("Nested task starting.");
Thread.SpinWait(500000);
Console.WriteLine("Nested task completing.");
});
}); parent.Wait();
Console.WriteLine("Outer has completed.");
}
}
// The example produces output like the following:
// Outer task executing.
// Nested task starting.
// Outer has completed.
// Nested task completing.

相应地,使用TaskCreationOptions.AttachedToParent创建的儿子Task则带有以下3个特点(这3个特点也是默认情况下创建的儿子Task不具备的):

  1. 爸爸Task会等待儿子Task结束。
  2. 爸爸Task会捕获儿子Task的Exception。
  3. 爸爸Task的执行状态取决于儿子Task的执行状态。
  4. 子任务和父任务并不一定运行在同一线程上。

例如,上面的代码使用TaskCreationOptions.AttachedToParent,则会得到以下的输出:

using System;
using System.Threading;
using System.Threading.Tasks; public class Example
{
public static void Main()
{
var parent = Task.Factory.StartNew(() => {
Console.WriteLine("Parent task executing.");
var child = Task.Factory.StartNew(() => {
Console.WriteLine("Attached child starting.");
Thread.SpinWait(5000000);
Console.WriteLine("Attached child completing.");
}, TaskCreationOptions.AttachedToParent);
});
parent.Wait();
Console.WriteLine("Parent has completed.");
}
}
// The example displays the following output:
// Parent task executing.
// Attached child starting.
// Attached child completing.
// Parent has completed.

 

DenyChildAttach

如果你不希望一个Task的启动的儿子们Attach到它自己身上,则可以在启动爸爸Task时为它指定TaskCreationOptions.DenyChildAttach。当通过DenyChildAttach启动的爸爸Task试图指定AttachedToParent来启动儿子Task时,AttachedToParent将会失效。


HideScheduler

当指定TaskCreationOptions.HideScheduler时,创建Task里再创建的儿子Task将使用默认的TaskScheduler,而不是当前的TaskScheduler。这相当于在创建Task时隐藏了自己当前的TaskScheduler。对于本身就是在默认的TaskScheduler里创建的Task,这个选项似乎没什么用。

using System.Reflection;
Task tasktest = new(() => { Console.WriteLine($"test Task TaskScheduler is {TaskScheduler.Current}"); });
Task taskParent = new(() => {
//这边不能使用Task.Run 因为它已经配置配置好了,用的是线程池任务调度器。
Task subtask=new(() =>
{
//这边直接使用了 父任务的任务调度器
Console.WriteLine($"sub Task TaskScheduler is {TaskScheduler.Current}");
}); subtask.Start();//这边使用的是当前线程,所以继承了父任务的 任务调度器
Console.WriteLine($"main Task TaskScheduler is {TaskScheduler.Current}"); Task subtask2 = new(() =>
{
//因为隐藏父任务的任务调度器,所以采用了默认的线程池调度器
Console.WriteLine($"sub Task2 TaskScheduler is {TaskScheduler.Current}");
},TaskCreationOptions.HideScheduler);// 隐藏父类的任务调度器 subtask2.Start();
Console.WriteLine($"main Task TaskScheduler is {TaskScheduler.Current}");
}); tasktest.Start();
taskParent.Start(new PerThreadTaskScheduler());//自定义的任务调度器
taskParent.Wait();
Console.WriteLine("all complete");
Console.Read(); /* 输出:
test Task TaskScheduler is System.Threading.Tasks.ThreadPoolTaskScheduler
main Task TaskScheduler is PerThreadTaskScheduler
sub Task TaskScheduler is PerThreadTaskScheduler
main Task TaskScheduler is PerThreadTaskScheduler
sub Task2 TaskScheduler is System.Threading.Tasks.ThreadPoolTaskScheduler
all complete */

自定义的任务调度器

public class PerThreadTaskScheduler : TaskScheduler
{
public string Name => "PerThreadTaskScheduler ";
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();
}
}

LongRunning

C#启动的Task都会通过TaskScheduler来安排执行。根据官方文档的描述:

The default scheduler for the Task Parallel Library and PLINQ uses the .NET Framework thread pool, which is represented by the ThreadPool class, to queue and execute work. The thread pool uses the information that is provided by the Task type to efficiently support the fine-grained parallelism (short-lived units of work) that parallel tasks and queries often represent.

而默认的TaskScheduler采用的是.NET线程池ThreadPool,它主要面向的是细粒度的小任务,其执行时间通常在毫秒级。线程池中的线程数与处理器的内核数有关,如果线程池中没有空闲的线程,那么后续的Task将会被阻塞。因此,如果事先知道一个Task的执行需要较长的时间,就需要使用TaskCreationOptions.LongRunning枚举指明。使用TaskCreationOptions.LongRunning创建的任务将会脱离线程池启动一个单独的线程来执行。


PreferFairness

任务并行库实现良好性能的方法之一是通过"工作窃取"。.NET 4 线程池支持工作窃取,以便通过任务并行库及其默认计划程序进行访问。这表现为线程池中的每个线程都有自己的工作队列;当该线程创建任务时,默认情况下,这些任务将排队到线程的本地队列中,而不是排队到对 ThreadPool.QueueUserWorkItem 的调用通常面向的全局队列中。当线程搜索要执行的工作时,它会从其本地队列开始,该操作由于改进了缓存局部性,最小化了争用等,从而实现了一些额外的效率。但是,这种逻辑也会影响公平性。

典型的线程池将具有单个队列,用于维护要执行的所有工作。当池中的线程准备好处理另一个工作项时,它们将从队列的头部取消排队工作,当新工作到达池中执行时,它将排队到队列的尾部。这为工作项之间提供了一定程度的公平性,因为首先到达的工作项更有可能被选中并首先开始执行。

偷工作扰乱了这种公平。池外部的线程可能正在排队工作,但如果池中的线程也在生成工作,则池生成的工作将优先于其他工作项,具体取决于池中线程(这些线程首先开始使用其本地队列搜索工作, 仅继续进入全局队列,然后继续到其他线程的队列(如果本地没有工作可用)。这种行为通常是预期的,甚至是期望的,因为如果正在执行的工作项正在生成更多工作,则生成的工作通常被视为正在处理的整体操作的一部分,因此它比其他不相关的工作更可取是有道理的。例如,想象一个快速排序操作,其中每个递归排序调用都可能导致几个进一步的递归调用;这些调用(在并行实现中可能是单个任务)是全系列排序操作的一部分。

不过,在某些情况下,这种默认行为是不合适的,其中应该在池中的线程生成的特定工作项和其他线程生成的工作项之间保持公平性。对于长链的延续,通常就是这种情况,其中生成的工作不被视为当前工作的一部分,而是当前工作的后续工作。在这些情况下,您可能希望以公平的方式将后续工作与系统中的其他工作放在一起。这就是TaskCreationOptions.PreferFairness可以证明有用的地方。

将 Task 调度到默认调度程序时,调度程序将查看任务从中排队的当前线程是否是具有自己的本地队列的 ThreadPool 线程。如果不是,则工作项将排队到全局队列。如果是,计划程序还将检查任务的 TaskCreationOptions 值是否包含"首选公平性"标志,默认情况下该标志未打开。如果设置了该标志,即使线程确实有自己的本地队列,调度程序仍将 Task 排队到全局队列,而不是本地队列。通过这种方式,该任务将与全局排队的所有其他工作项一起被公平地考虑。

刚才描述的是默认计划程序中优先公平标志的当前实现。实现当然可以更改,但不会更改的是标志的目的:通过指定 PreferFairness,您可以告诉系统不应仅仅因为此任务来自本地队列而对其进行优先级排序。您是在告诉系统,您希望系统尽最大努力确保此任务以先到先得的方式进行优先级排序。

另一件需要注意的事情是,Task本身对这面旗帜一无所知。它只是一个标志,设置为任务上的一个选项。调度程序决定了它想要如何处理这个特定的选项,就像TaskCreationOptions.LongRunning一样。默认调度程序按上述方式处理它,但另一个调度程序(例如您编写的调度程序)可以根据需要使用此标志,包括忽略它。因此,命名"首选"而不是像"保证"这样更严格的东西。

转载自:TaskCreationOptions.PreferFairness - .NET Parallel Programming (microsoft.com)

【C#Task】TaskCreationOptions 枚举的更多相关文章

  1. 第五节:Task构造函数之TaskCreationOptions枚举处理父子线程之间的关系。

    一. 整体说明 揭秘: 通过F12查看Task类的源码(详见下面的截图),发现Task类的构造函数有有一个参数为:TaskCreationOptions类型,本章节可以算作是一个扩展章节,主要就来研究 ...

  2. Educational Codeforces Round 88 (Rated for Div. 2) D. Yet Another Yet Another Task(枚举/最大连续子序列)

    题目链接:https://codeforces.com/contest/1359/problem/D 题意 有一个大小为 $n$ 的数组,可以选取一段连续区间去掉其中的最大值求和,问求和的最大值为多少 ...

  3. .NET 4.0 任务(Task)

    随着 .NET 4.0的到来,她与以前各版本的一个明显差别就是并行功能的增强,以此来适应这个多核的世界.于是引入了一个新概念---任务,作为支持并行运算的重要组成部分,同时,也作为对线程池的一个补充和 ...

  4. C# Task的使用---Task的启动

    .NET 4.0包含的新名称空间System.Threading.Tasks,它包含的类抽象出了线程功能.任务表示应完成的某个单元的工作.这个单元的工作可以在单独的线程中运行,也可以以同步的方式启动一 ...

  5. .net task

    Task 是4.0里面带来的一个很好用的线程类,后台也是由线程池控制的 有时间是里面的方法得好好看看. 今天学到一个新的. 当需要两个操作并行执行,然后再线性执行时.可以先 Task1 Task2执行 ...

  6. 用惯了Task,你应该也需要了解它的内部调度机制TaskScheduler

    平时我们在用多线程开发的时候少不了Task,确实task给我们带来了巨大的编程效率,在Task底层有一个TaskScheduler,它决定了task该如何执行,而在 .net framework中有两 ...

  7. .Net4.0 任务(Task)

    任务(Task)是一个管理并行工作单元的轻量级对象.它通过使用CLR的线程池来避免启动专用线程,可以更有效率的利用线程池.System.Threading.Tasks 命名空间下任务相关类一览: 类 ...

  8. 那些年我们一起追逐的多线程(Thread、ThreadPool、委托异步调用、Task/TaskFactory、Parallerl、async和await)

    一. 背景 在刚接触开发的头几年里,说实话,根本不考虑多线程的这个问题,貌似那时候脑子里也有没有多线程的这个概念,所有的业务都是一个线程来处理,不考虑性能问题,当然也没有考虑多线程操作一条记录存在的并 ...

  9. 任务(task)

    任务概述 线程(Thread)是创建并发的底层工具,因此有一定的局限性(不易得到返回值(必须通过创建共享域):异常的捕获和处理也麻烦:同时线程执行完毕后无法再次开启该线程),这些局限性会降低性能同时影 ...

随机推荐

  1. Chrome本地跨域请求设置,实现HTML模板页

    按照需求,公司现在需要通过第三方的API反馈的数据,进行在本地就可以打开的静态页面程序(完全脱离IIS等服务器).为了更好的维护项目,需要实现静态HTML引入HTML模板,完成ASP.NET模板页的类 ...

  2. 第02讲:Flink 入门程序 WordCount 和 SQL 实现

    我们右键运行时相当于在本地启动了一个单机版本.生产中都是集群环境,并且是高可用的,生产上提交任务需要用到flink run 命令,指定必要的参数. 本课时我们主要介绍 Flink 的入门程序以及 SQ ...

  3. [JavaWeb]利用JSP的编码特性制作免杀后门

    利用JSP的编码特性制作免杀后门 这里是借鉴了Y4stacker师傅的thinkings 待解决的问题 JSP解析 JSP"乱码"为什么还能被识别 "乱码"的J ...

  4. gin中jsonp的用法

    package main import ( "github.com/gin-gonic/gin" "net/http" ) func main() { r := ...

  5. Python实现自动更改系统用户密码,生成随机密码

    算是一个实用的例子,定制系统任务,并将随机密码上传至日志服务器,实现定期修改密码: 部分代码: 1 #!/usr/bin/env python 2 #coding:utf-8 3 import ran ...

  6. 【Vulnhub靶场】JANGOW: 1.0.1

    时隔这么久,终于开始做题了 环境准备 下载靶机,导入到virtualBox里面,这应该不用教了吧 开机可以看到,他已经给出了靶机的IP地址,就不用我们自己去探测了 攻击机IP地址为:192.168.2 ...

  7. jsp加载css失效的解决方法

    问题: 有时候大家修改了css文件里的样式,但是刷新浏览器,利用工具看样式的时候,发现样式根本没加载或者说没更新, 其实这出现的问题就是缓存的问题 解决: 如何避免发生这种事,其实很简单,只需要每次请 ...

  8. docker和K8s对应参数

    创建 Pod 时设置命令及参数 创建 Pod 时,可以为其下的容器设置启动时要执行的命令及其参数.如果要设置命令,就填写在配置文件的 command 字段下,如果要设置命令的参数,就填写在配置文件的  ...

  9. ARC-124 部分题解

    E 直接统计原式不好做,注意到首先我们应该知道怎样的 \(x\) 序列是合法的,那么不妨首先来统计一下合法的 \(x\) 序列数量. 令 \(b_i\) 为 \(i\) 向右给的球数,那么有(\(i ...

  10. AT [ABC177F] I hate Shortest Path Problem

    因为每行只有一个区域不能往下走,因此我们可以来分析一下从起点到整个矩形每个位置的最短路.可以发现每一行的最短路只与上一行的最短路有关,假设我们知道上一行的最短路,上一行不能往下走的区间在 \([L, ...