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();
}
}
}
创建一个与当前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任务调度器的实现的文章就介绍至此,更多相关c# taskscheduler任务调度器内容请搜索硕编程以前的文章,希望以后支持硕编程!


