C#TaskScheduler 任务调度器的原理
什么是TaskScheduler?
SynchronizationContext是对“调度程序(scheduler)”的通用抽象。个别框架会有自己的抽象调度程序,比如System.Threading.Tasks。当Tasks通过委托的形式进行排队和执行时,会用到System.Threading.Tasks.TaskScheduler。和SynchronizationContext提供了一个virtual Post方法用于将委托排队调用一样(稍后,我们会通过典型的委托调用机制来调用委托),TaskScheduler也提供了一个abstract QueueTask方法(稍后,我们会通过ExecuteTask方法来调用该Task)。
通过TaskScheduler.Default我们可以获取到Task默认的调度程序ThreadPoolTaskScheduler——线程池(译注:这下知道为什么Task默认使用的是线程池线程了吧)。并且可以通过继承TaskScheduler来重写相关方法来实现在任意时间任意地点进行Task调用。例如,核心库中有个类,名为System.Threading.Tasks.ConcurrentExclusiveSchedulerPair,其实例公开了两个TaskScheduler属性,一个叫ExclusiveScheduler,另一个叫ConcurrentScheduler。调度给ConcurrentScheduler的任务可以并发,但是要在构造ConcurrentExclusiveSchedulerPair时就要指定最大并发数(类似于前面演示的MaxConcurrencySynchronizationContext);相反,在ExclusiveScheduler执行任务时,那么将只允许运行一个排他任务,这个行为很像读写锁。
和SynchronizationContext一样,TaskScheduler也有一个Current属性,会返回当前调度程序。不过,和SynchronizationContext不同的是,它没有设置当前调度程序的方法,而是在启动Task时就要提供,因为当前调度程序是与当前运行的Task相关联的。所以,下方的示例程序会输出“True”,这是因为和StartNew一起使用的lambda表达式是在ConcurrentExclusiveSchedulerPair的ExclusiveScheduler上执行的(我们手动指定cesp.ExclusiveScheduler),并且TaskScheduler.Current也
using System;
using System.Threading.Tasks; class Program
{
static void Main()
{
var cesp = new ConcurrentExclusiveSchedulerPair();
Task.Factory.StartNew(() =>
{
Console.WriteLine(TaskScheduler.Current == cesp.ExclusiveScheduler);
}, default, TaskCreationOptions.None, cesp.ExclusiveScheduler)
.Wait();
}
}
TaskScheduler 任务调度器的原理

public abstract class TaskScheduler
{
// 任务入口,待调度执行的 Task 会通过该方法传入,调度器会将任务安排task到指定的队列(线程池任务队列(全局任务队列、本地队列)、独立线程、ui线程) 只能被.NET Framework调用,不能配派生类调用
//
protected internal abstract void QueueTask(Task task); // 这个是在执行 Task 回调的时候才会被执行到的方法,放到后面再讲
protected abstract bool TryExecuteTaskInline(Task task, bool taskWasPreviouslyQueued);
protected abstract bool TryExecuteTask(Task task, bool taskWasPreviouslyQueued);
// 获取所有调度到该 TaskScheduler 的 Task
protected abstract IEnumerable<Task>? GetScheduledTasks();
}
.net中的任务调度器有哪些
线程池任务调度器:ThreadPoolTaskScheduler、
核心库任务调度器:ConcurrentExclusiveSchedulerPair
UI任务调度器:SynchronizationContextTaskScheduler,并发度为1
平时我们在用多线程开发的时候少不了Task,确实task给我们带来了巨大的编程效率,在Task底层有一个TaskScheduler,它决定了task该如何被调度,而
在.net framework中有两种系统定义Scheduler,第一个是Task默认的ThreadPoolTaskScheduler,还是一种就是SynchronizationContextTaskScheduler(wpf),默认的调度器无法控制任务优先级,那么需要自定义调度器实现优先级控制。
以及这两种类型之外的如何自定义,这篇刚好和大家分享一下。
一: ThreadPoolTaskScheduler

这种scheduler机制是task的默认机制,而且从名字上也可以看到它是一种委托到ThreadPool的机制,刚好也从侧面说明task是基于ThreadPool基础上的
封装,源代码
ThreadPoolTaskScheduler的原理:将指定的长任务开辟一个独立的线程去执行,未指定的长时间运行的任务就用线程池的线程执行
internal sealed class ThreadPoolTaskScheduler : TaskScheduler
{
//其他代码
protected internal override void QueueTask(Task task)
{
TaskCreationOptions options = task.Options;
if (Thread.IsThreadStartSupported && (options & TaskCreationOptions.LongRunning) != 0)
{
// Run LongRunning tasks on their own dedicated thread.
new Thread(s_longRunningThreadWork)
{
IsBackground = true,
Name = ".NET Long Running Task"
}.UnsafeStart(task);
}
else
{
// Normal handling for non-LongRunning tasks.
ThreadPool.UnsafeQueueUserWorkItemInternal(task, (options & TaskCreationOptions.PreferFairness) == 0);
}
}
//其他代码 }
二:SynchronizationContextTaskScheduler
使用条件:只有当前程的同步上下文不为null时,该方法才能正常使用。例如在UI线程(wpf、 winform、 asp.net)中,UI线程的同步上下文不为Null。控制台默认的当前线程同步上下文为null,如果给当前线程设置默认的同步上下文SynchronizationContext.SetSynchronizationContext(new SynchronizationContext());就可以正常使用该方法。如果控制台程序的线程未设置同步上下将引发【当前的 SynchronizationContext 不能用作 TaskScheduler】异常。
默认的同步上下文将方法委托给线程池执行。
使用方式:通过TaskScheduler.FromCurrentSynchronizationContext() 调用SynchronizationContextTaskScheduler。
原理:初始化时候捕获当前的线程的同步上下文。 将同步上下文封装入任务调度器形成新的任务调度器SynchronizationContextTaskScheduler。重写该任务调度器中的QueueTask方法,利用同步上下文的post方法将任务送到不同的处理程序,如果是
winform的UI线程同步上下文 的post方法(已重写post方法),就将任务送到UI线程。如果是控制台线程(默认为null 设置默认同步上下文后可以正常使用。默认同步上下文采用线程池线程)就将任务送入线程池处理。
在winform中的同步上下文:WindowsFormsSynchronizationContext
在wpf中的同步上下文:DispatcherSynchronizationContext
在控制台\线程池\new thread 同步上下文:都默认为Null。可以给他们设置默认的同步上下文SynchronizationContext。SynchronizationContext.SetSynchronizationContext(new SynchronizationContext());
SynchronizationContext 综述 | Microsoft Docs
以下是SynchronizationContextTaskScheduler部分源代码
internal sealed class SynchronizationContextTaskScheduler : TaskScheduler
{ //初始化时候 ,捕获当前线程的同步上下文
internal SynchronizationContextTaskScheduler()
{
m_synchronizationContext = SynchronizationContext.Current ??
// make sure we have a synccontext to work with
throw new InvalidOperationException(SR.TaskScheduler_FromCurrentSynchronizationContext_NoCurrent);
} //其他代码
private readonly SynchronizationContext m_synchronizationContext;
protected internal override void QueueTask(Task task)
{
m_synchronizationContext.Post(s_postCallback, (object)task);
}
//其他代码
///改变post的调度方法、 调用者线程执行各方面的任务操作 private static readonly SendOrPostCallback s_postCallback = static s =>
{
Debug.Assert(s is Task);
((Task)s).ExecuteEntry(); //调用者线程执行各方面的任务操作
};
}
以下是SynchronizationContext部分源代码
public partial class SynchronizationContext
{
//其他代码
public virtual void Post(SendOrPostCallback d, object? state) => ThreadPool.QueueUserWorkItem(static s => s.d(s.state), (d, state), preferLocal: false);
//其他代码
}
有了这个基础我们再来看一下代码怎么写,可以看到,下面这段代码是不阻塞UIThread的,完美~~~
private void button1_Click(object sender, EventArgs e)
{
Task task = Task.Factory.StartNew(() =>
{
//复杂操作,等待10s
Thread.Sleep(10000); }).ContinueWith((t) =>
{
button1.Text = "hello world";
}, TaskScheduler.FromCurrentSynchronizationContext());
}
三:自定义TaskScheduler
我们知道在现有的.net framework中只有这么两种TaskScheduler,有些同学可能想问,这些Scheduler我用起来不爽,我想自定义一下,这个可
以吗?当然!!!如果你想自定义,只要自定义一个类实现一下TaskScheduler就可以了,然后你可以将ThreadPoolTaskScheduler简化一下,即我要
求所有的Task都需要走Thread,杜绝使用TheadPool,这样可以吗,当然了,不信你看。
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
var task = Task.Factory.StartNew(() =>
{
Console.WriteLine("hello world!!!");
}, new CancellationToken(), TaskCreationOptions.None, new PerThreadTaskScheduler()); Console.Read();
}
} /// <summary>
/// 每个Task一个Thread
/// </summary>
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();
}
}
}
方法的使用
TaskScheduler.FromCurrentSynchronizationContext() 方法
创建一个与当前SynchronizationContext关联的TaskScheduler。源代码如下:
假设有一个UI App,它有一个按钮。当点击按钮后,会从网上下载一些文本并将其设置为按钮的内容。我们应当只在UI线程中访问该按钮,因此当我们成功下载新的文本后,我们需要从拥有按钮控制权的的线程中将其设置为按钮的内容。如果不这样做的话,会得到一个这样的异常:
System.InvalidOperationException: 'The calling thread cannot access this object because a different thread owns it.'
如果我们自己手动实现,那么可以使用前面所述的SynchronizationContext将按钮内容的设置传回原始上下文,例如借助TaskScheduler:
用法如下
private static readonly HttpClient s_httpClient = new HttpClient(); private void downloadBtn_Click(object sender, RoutedEventArgs e)
{
s_httpClient.GetStringAsync("http://example.com/currenttime").ContinueWith(downloadTask =>
{
downloadBtn.Content = downloadTask.Result;
}, TaskScheduler.FromCurrentSynchronizationContext());//捕获当前UI线程的同步上下文
}
C#TaskScheduler 任务调度器的原理的更多相关文章
- 18 TaskScheduler任务调度器抽象基类——Live555源码阅读(一)任务调度相关类
这是Live555源码阅读的第二部分,包括了任务调度相关的三个类.任务调度是Live555源码中很重要的部分. 本文由乌合之众 lym瞎编,欢迎转载 http://www.cnblogs.com/ol ...
- TaskScheduler一个.NET版任务调度器
TaskScheduler是一个.net版的任务调度器.概念少,简单易用. 支持SimpleTrigger触发器,指定固定时间间隔和执行次数: 支持CronTrigger触发器,用强大的Cron表达式 ...
- MapReduce多用户任务调度器——容量调度器(Capacity Scheduler)原理和源码研究
前言:为了研究需要,将Capacity Scheduler和Fair Scheduler的原理和代码进行学习,用两篇文章作为记录.如有理解错误之处,欢迎批评指正. 容量调度器(Capacity Sch ...
- Spark源码剖析 - SparkContext的初始化(五)_创建任务调度器TaskScheduler
5. 创建任务调度器TaskScheduler TaskScheduler也是SparkContext的重要组成部分,负责任务的提交,并且请求集群管理器对任务调度.TaskScheduler也可以看作 ...
- spring任务执行器与任务调度器(TaskExecutor And TaskScheduler)
对于多线程及周期性调度相关的操作,spring框架提供了TaskExecutor和TaskScheduler接口为异步执行和任务调度.并提供了相关实现类给开发者使用.(只记录采用注解的使用形式,对于X ...
- 21 BasicTaskScheduler基本任务调度器(一)——Live555源码阅读(一)任务调度相关类
21_BasicTaskScheduler基本任务调度器(一)——Live555源码阅读(一)任务调度相关类 BasicTaskScheduler基本任务调度器 BasicTaskScheduler基 ...
- C# 可指定并行度任务调度器
可指定并行度的任务调度器 https://social.msdn.microsoft.com/Forums/zh-CN/b02ba3b4-539b-46b7-af6b-a5ca3a61a309/tas ...
- 使用TaskScheduler 调度器 实现跨线程的控件访问
//任务调度器 TaskScheduler UIscheduler = null; public Form1() { //获取任务调度器 UIscheduler = TaskScheduler.Fro ...
- 基于Spring Task的定时任务调度器实现
在很多时候,我们会需要执行一些定时任务 ,Spring团队提供了Spring Task模块对定时任务的调度提供了支持,基于注解式的任务使用也非常方便. 只要跟需要定时执行的方法加上类似 @Schedu ...
随机推荐
- 【记录一个问题】用毫无用处的方法解决了libtask的asm.S在ndk下编译的问题
昨天提到,libtask中的asm.S使用的是ARM 32位的语法,因此在ARM 64下无法编译通过. 于是查了一下资料,改写了一下汇编代码,使得可以在64位下编译通过.源码如下 #if define ...
- JUC之阻塞队列(BlockingQueue)基础
阻塞队列 阻塞队列(BlockingQueue)是一个支持两个附加操作的队列.这两个附加的操作支持阻塞的插入和移除方法. 阻塞队列常用于生产者和消费者的场景: 当队列为空的,从队列中获取元素的操作将会 ...
- windows系统配置Nginx使用SSL证书实现Https反向代理
Nginx反向代理服务,可以代理接收请求,并把请求转发到设置好的其他服务器上. 例如,Nginx服务器为 100.101.102.103,A服务为 100.101.102.104 ,通过Nginx配置 ...
- java中的继承 和多态。
package com.aaa.zxf.ajax.test; /** *一. java 中的继承和多态. * * 继承的特性? * 1.实现继承的方式 * A 如何建立继承关系(一个类继承于 另一个类 ...
- C编译器中“不是所有的控件路径都返回值”报错
编译器的判断逻辑是是否在所有的分支中都返回了值,即if不成立时也必须返回值.编译器认为如果三个if都不成立则此函数可能没有返回值,故报错.需要将第三个if改为else或者去掉if体直接return.
- ApacheCN Asp.NET 译文集 20211126 更新
ASP.NET Core2 基础知识 零.前言 一.搭建舞台 二.控制器 三.视图 四.模型 五.验证 六.路由 七.RestBuy 八.添加功能.测试和部署 ASP.NET Core3 和 Angu ...
- Swift 介绍
简介 Swift 语言由苹果公司在 2014 年推出,用来撰写 OS X 和 iOS 应用程序 2014 年,在 Apple WWDC 发布 几家欢喜,几家愁 愁者:只学Object-C的人 欢喜者: ...
- 如何使PreparedStatement支持命名参数
http://m.blog.csdn.net/wallimn/article/details/3734242
- 你还不懂java的日志系统吗
一.背景 在java的开发中,使用最多也绕不过去的一个话题就是日志,在程序中除了业务代码外,使用最多的就是打印日志.经常听到的这样一句话就是"打个日志调试下",没错在日常的开发.调 ...
- JS快速入门(一)
目录 Javascript快速入门(一) 变量的命名规则 变量与常量 变量 常量 数据类型 数值型:Number 字符串:String 常用方法: 布尔类型:Boolean 未定义类型 :Undefi ...