什么是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 任务调度器的原理的更多相关文章

  1. 18 TaskScheduler任务调度器抽象基类——Live555源码阅读(一)任务调度相关类

    这是Live555源码阅读的第二部分,包括了任务调度相关的三个类.任务调度是Live555源码中很重要的部分. 本文由乌合之众 lym瞎编,欢迎转载 http://www.cnblogs.com/ol ...

  2. TaskScheduler一个.NET版任务调度器

    TaskScheduler是一个.net版的任务调度器.概念少,简单易用. 支持SimpleTrigger触发器,指定固定时间间隔和执行次数: 支持CronTrigger触发器,用强大的Cron表达式 ...

  3. MapReduce多用户任务调度器——容量调度器(Capacity Scheduler)原理和源码研究

    前言:为了研究需要,将Capacity Scheduler和Fair Scheduler的原理和代码进行学习,用两篇文章作为记录.如有理解错误之处,欢迎批评指正. 容量调度器(Capacity Sch ...

  4. Spark源码剖析 - SparkContext的初始化(五)_创建任务调度器TaskScheduler

    5. 创建任务调度器TaskScheduler TaskScheduler也是SparkContext的重要组成部分,负责任务的提交,并且请求集群管理器对任务调度.TaskScheduler也可以看作 ...

  5. spring任务执行器与任务调度器(TaskExecutor And TaskScheduler)

    对于多线程及周期性调度相关的操作,spring框架提供了TaskExecutor和TaskScheduler接口为异步执行和任务调度.并提供了相关实现类给开发者使用.(只记录采用注解的使用形式,对于X ...

  6. 21 BasicTaskScheduler基本任务调度器(一)——Live555源码阅读(一)任务调度相关类

    21_BasicTaskScheduler基本任务调度器(一)——Live555源码阅读(一)任务调度相关类 BasicTaskScheduler基本任务调度器 BasicTaskScheduler基 ...

  7. C# 可指定并行度任务调度器

    可指定并行度的任务调度器 https://social.msdn.microsoft.com/Forums/zh-CN/b02ba3b4-539b-46b7-af6b-a5ca3a61a309/tas ...

  8. 使用TaskScheduler 调度器 实现跨线程的控件访问

    //任务调度器 TaskScheduler UIscheduler = null; public Form1() { //获取任务调度器 UIscheduler = TaskScheduler.Fro ...

  9. 基于Spring Task的定时任务调度器实现

    在很多时候,我们会需要执行一些定时任务 ,Spring团队提供了Spring Task模块对定时任务的调度提供了支持,基于注解式的任务使用也非常方便. 只要跟需要定时执行的方法加上类似 @Schedu ...

随机推荐

  1. 字符串工具类ToStringBuilder常用方法介绍

    一.简介与引入   1.ToStringBuilder.HashCodeBuilder.EqualsBuilder.ToStringStyle.ReflectionToStringBuilder.Co ...

  2. Go 变量及基本数据类型3

    #### Go 变量及基本数据类型(三)今天学习一下剩下的两个基本数据类型(布尔类型,字符串类型)以及基本数据类型的相互转换##### 布尔类型布尔类型也称为bool 类型, bool 类型只允许取值 ...

  3. Android开发----WebView&Activity生命周期

    WebView webview是一个再应用中设置好位置和大小的浏览器,而且不会放置任何花哨的UI. 在大多数情况下,除非你调用了原生API,否则不必在webview中专门测试web应用. 首先为Web ...

  4. Avoiding the Backup of Online Redo Logs

    Although it may seem that you should back up online redo logs along with the datafiles and control f ...

  5. 势能分析(splay分析)

    定义 第\(x\)次操作后,势能为\(\phi(x)\),该操作实际复杂度\(c(x)\),均摊复杂度\(a(x)\). 定义\(a(x)=c(x)+\phi(x)-\phi(x-1)\). 那么总复 ...

  6. 「CTSC2010」产品销售

    「CTSC2010」产品销售 30pts的费用流都会吧... 100pts只要模拟费用流就行了,是不是很简单呀( 咕咕咕 令\(M_i\)表示\(i-1\to i\)的正向边,\(M_i^{'}\)表 ...

  7. 湖人季后赛淘汰出局 - For James 2021.6.4

    今天有NBA季后赛湖人主场对太阳的G6比赛,之前湖人2-3落后,这场比赛输了就被淘汰了.上午特意看了比赛的直播,期望着湖人能赢下这场,这样还有打G7的机会,也就还有进入下一轮的机会.最后湖人还是输了这 ...

  8. 实现Nginx代理WSS协议

    因为线上H5游戏需要加上SSL,不想在原来的Web 服务器和游戏服务器支持SSL,只希望 在Nginx代理集群支持SSL.整体架构如下: 从上图可以看出需要总共涉及到https/http 和wss/w ...

  9. JVM学习二:JVM之GC算法和种类

    我们前面说到了JVM的常用的配置参数,其中就涉及了GC相关的知识,趁热打铁,我们今天就学习下GC的算法有哪些,种类又有哪些,让我们进一步的认识GC这个神奇的东西,帮助我们解决了C 一直挺头疼的内存回收 ...

  10. 对于Web性能优化, 了解和经验

    我们在发布项目之前压缩CSS和JavaScript源代码,这样文件体积就变小了,用户加载必要资源所花的时间也就更短了. 压缩源码和图片 JavaScript文件源代码可以采用混淆压缩的方式,CSS文件 ...