根据 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. Windows系统安装和office版本兼容

    MSDN, I tell you 下载windows10系统各种纯净镜像.但是还是推荐使用官网修改参数显示出下载链接,下载windows10最新版. 在最近几次重做系统中,PE工具似乎无法识别从MSD ...

  2. Ajax的IE缓存问题

    Ajax之IE缓存问题 <!-- IE浏览器会对ajax的结果进行一个缓存,这样就会导致一个缓存问题 浏览器会读取缓存 而不会去使用一个新的数据 这样对一个时效性比较强的场景 ajax的缓存会影 ...

  3. Natasha 4.0 探索之路系列(二) "域"与插件

    域与ALC 在 Natasha 发布之后有不少小伙伴跑过来问域相关的问题, 能不能兼容 AppDomain, 如何使用 AppDomain, 为什么 CoreAPI 阉割了 AppDomain 等一系 ...

  4. 使用VS Code的MySQL扩展管理数据库

    我将在本文告诉你如何用VS Code的扩展程序管理MySQL数据库,包括连接到MySQL.新建数据库和表.修改字段定义.简单的查询方法以及导入导出. 在许多情况下,我们需要随时查看数据库的记录来确保程 ...

  5. gorm概述与快速入门

    特性 全功能 ORM 关联 (Has One,Has Many,Belongs To,Many To Many,多态,单表继承) Create,Save,Update,Delete,Find 中钩子方 ...

  6. Servlet-请求的分发处理

    1,HelloServlet类中 2,a.html中

  7. 初步学习TypeScript

    使用Visual Studio Code搭建TypeScript开发环境 1.TypeScript是干什么的 ? TypeScript是由微软Anders Hejlsberg(安德斯·海尔斯伯格,也是 ...

  8. 获取nginx日志状态码百分比脚本

    #!/bin/bash pwd=/app/nginx/logs/access.log for num1 in `cat $pwd | awk '{print $9}' | grep -Ei " ...

  9. asp.net core监控—引入Prometheus(三)

    上一篇博文中说到Prometheus有四种指标类型:Counter(计数器).Gauge(仪表盘).Histogram(直方图).Summary(摘要),并且我们做了一个Counter的Demo,接下 ...

  10. docker 安装遇到404 not find

    https://mirrors.ustc.edu.cn/docker-ce/linux/centos/docker-ce/repodata/repomd.xml: [Errno 14] HTTPS E ...