浅议 Task 底层的调度机制 TaskScheduler

DotNetCore实战

共 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开发实战视频课程 ★★★

.NET Core实战项目之CMS 第一章 入门篇-开篇及总体规划

【.NET Core微服务实战-统一身份认证】开篇及目录索引

Redis基本使用及百亿数据量中的使用技巧分享(附视频地址及观看指南)

.NET Core中的一个接口多种实现的依赖注入与动态选择看这篇就够了

10个小技巧助您写出高性能的ASP.NET Core代码

用abp vNext快速开发Quartz.NET定时任务管理界面

在ASP.NET Core中创建基于Quartz.NET托管服务轻松实现作业调度

现身说法:实际业务出发分析百亿数据量下的多表查询优化

关于C#异步编程你应该了解的几点建议

C#异步编程看这篇就够了


浏览 25
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报