浅议 Task 底层的调度机制 TaskScheduler
共 4352字,需浏览 9分钟
·
2020-11-06 08:54
相信大家对 Task 已经非常熟悉了,在 Task 底层有一个发动机,决定了它是涡轮增压还是自然吸气,它就是 TaskScheduler 抽象类,在框架下这个发动机有两个默认实现子类:ThreadPoolTaskScheduler 和 SynchronizationContextTaskScheduler,具体应用场景以及如何自定义子类,这篇刚好和大家分享一下。
一: ThreadPoolTaskScheduler
ThreadPoolTaskScheduler 是 Task 的默认机制,而且从名字上也可以看到它是一种基于 ThreadPool 的机制,从侧面也说明 Task 是基于 ThreadPool 的封装,如果想具体查看代码逻辑,可以通过 ILSpy 反编译一下代码:
protected internal override void QueueTask(Task task)
{
if ((task.Options & TaskCreationOptions.LongRunning) != TaskCreationOptions.None)
{
new Thread(ThreadPoolTaskScheduler.s_longRunningThreadWork)
{
IsBackground = true
}.Start(task);
return;
}
bool forceGlobal = (task.Options & TaskCreationOptions.PreferFairness) > TaskCreationOptions.None;
ThreadPool.UnsafeQueueCustomWorkItem(task, forceGlobal);
}
从上面代码中可以看到如下逻辑,如果当前 Task 的 TaskCreationOptions 设置为 LongRunning 的话,这个task就会委托到 Thread 中去执行,这样的好处显而易见,如果长时间运行的 task 占用着 ThreadPool 的线程,这时候ThreadPool为了保证线程充足,会再次开辟一些 Thread,如果耗时任务此时释放了,会导致ThreadPool线程过多,上下文切换频繁,不信的话,还可以用 windbg 验证一下。。。
static void Main(string[] args)
{
var task = Task.Factory.StartNew(() =>
{
Console.WriteLine("hello world!!!");
}, TaskCreationOptions.LongRunning);
Console.Read();
}
为了好做对比,我再把 TaskCreationOptions 枚举去掉,用 !threads 在看看。
static void Main(string[] args)
{
var task = Task.Factory.StartNew(() =>
{
Console.WriteLine("hello world!!!");
});
Console.Read();
}
好了,看完这两张图,你应该明白当是 LongRunning 的话,thread 中是没有(ThreadPool Worker)标记的,表明当前是单独开辟的线程,下面这张图很明显带有这种标识,表示当前是委托在 ThreadPool 中执行的。
二:SynchronizationContextTaskScheduler
从名字中可以看到,这是一个同步上下文的 TaskScheduler,原理就是把繁重的耗时工作丢给 ThreadPool,然后将更新UI的操作丢给UI任务队列,由 UIThread 来执行,具体也可以在源码中窥探一二。
protected internal override void QueueTask(Task task)
{
this.m_synchronizationContext.Post(SynchronizationContextTaskScheduler.s_postCallback, task);
}
继续追代码,可以看到 s_postCallback 里面会执行 Invoke 函数,如下代码:
public virtual void Post(SendOrPostCallback d, object state)
{
ThreadPool.QueueUserWorkItem(new WaitCallback(d.Invoke), state);
}
有了这个基础再来实现代码逻辑,注意下面这段代码是不阻塞 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
有些朋友可能要问,这些 Scheduler 我用起来不爽,我想自定义一下,这个可以吗?当然!!!实现一个抽象类 TaskScheduler 的子类,想怎么玩就怎么玩,比如说我想让所有的 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();
}
}
///
/// 每个Task一个Thread
///
public class PerThreadTaskScheduler : TaskScheduler
{
protected override IEnumerable 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();
}
}
}
看到没有,自定义Task就是这么简单,其实最重要的就是实现 QueueTask 方法,接下来我可以用windbg观察一下,确实是工作线程,而不是线程池,没骗你~~~
好了,本篇就说到这里,希望对你有帮助。
.NET Core实战项目之CMS 第一章 入门篇-开篇及总体规划
【.NET Core微服务实战-统一身份认证】开篇及目录索引
Redis基本使用及百亿数据量中的使用技巧分享(附视频地址及观看指南)
.NET Core中的一个接口多种实现的依赖注入与动态选择看这篇就够了
用abp vNext快速开发Quartz.NET定时任务管理界面