• 用惯了Task,你应该也需要了解它的内部调度机制TaskScheduler


      平时我们在用多线程开发的时候少不了Task,确实task给我们带来了巨大的编程效率,在Task底层有一个TaskScheduler,它决定了task该如何被调度,而

    在.net framework中有两种系统定义Scheduler,第一个是Task默认的ThreadPoolTaskScheduler,还是一种就是SynchronizationContextTaskScheduler,

    以及这两种类型之外的如何自定义,这篇刚好和大家分享一下。

    一: ThreadPoolTaskScheduler

          这种scheduler机制是task的默认机制,而且从名字上也可以看到它是一种委托到ThreadPool的机制,刚好也从侧面说明task是基于ThreadPool基础上的

    封装,如果想具体查看代码逻辑,你可以通过ILSpy反编译一下代码看看:

     1   protected internal override void QueueTask(Task task)
     2          {
     3              if ((task.Options & TaskCreationOptions.LongRunning) != TaskCreationOptions.None)
     4              {
     5                  new Thread(ThreadPoolTaskScheduler.s_longRunningThreadWork)
     6                  {
     7                      IsBackground = true
     8                  }.Start(task);
     9                  return;
    10              }
    11              bool forceGlobal = (task.Options & TaskCreationOptions.PreferFairness) > TaskCreationOptions.None;
    12              ThreadPool.UnsafeQueueCustomWorkItem(task, forceGlobal);
    13          }

           从上面的代码中可以看到如下逻辑,如果当前Task上的TaskCreationOptions设置为LongRunning的话,这个task就会委托到Thread中去执行,这样的

    好处显而易见,如果长时间运行的task占用着ThreadPool的线程,这时候ThreadPool为了保证线程充足,会再次开辟一些Thread,如果耗时任务此时释放了,

    会导致ThreadPool线程过多,上下文切换频繁,所以这种情况下让Task在Thread中执行还是非常不错的选择,当然如果你不指定这个LongRunning的话,那就

    是在ThreadPool上执行,不信的话,还可以用windbg去验证一下。。。

    1     static void Main(string[] args)
    2          {
    3              var task = Task.Factory.StartNew(() =>
    4              {
    5                  Console.WriteLine("hello world!!!");
    6              }, TaskCreationOptions.LongRunning);
    7  
    8              Console.Read();
    9          }

    如果大家对windbg不熟悉的话,也没关系,先暂且不讨论,我们只要把TaskCreationOptions枚举去掉,然后用这种形式的!threads给大家展示下不同

    应该就非常明朗了。

    1        static void Main(string[] args)
    2          {
    3              var task = Task.Factory.StartNew(() =>
    4              {
    5                  Console.WriteLine("hello world!!!");
    6              });
    7  
    8              Console.Read();
    9          }

    好了,当你看到这两张图,你应该明白带LongRunning的话,thread中没有带(ThreadPool Worker)标记,也就表明当前是单独开辟的线程,而下面

    这张图很明显带有这种标识,表示当前是委托在ThreadPool中执行的。

    二:SynchronizationContextTaskScheduler

         从这个名字中就可以看到,这是一个同步上下文的taskscheduler,原理就是把繁重的耗时工作丢给ThreadPool,然后将更新UI的操作丢给 UI线程的

    队列中,由UIThread来执行,具体的也可以在这种scheduler中窥得一二。

    1        protected internal override void QueueTask(Task task)
    2          {
    3              this.m_synchronizationContext.Post(SynchronizationContextTaskScheduler.s_postCallback, task);
    4          }

         然后可以从s_postCallback上看到里面有一个Invoke函数,如下图:

    1  public virtual void Post(SendOrPostCallback d, object state)
    2  {
    3      ThreadPool.QueueUserWorkItem(new WaitCallback(d.Invoke), state);
    4  }

          有了这个基础我们再来看一下代码怎么写,可以看到,下面这段代码是不阻塞UIThread的,完美~~~

     1          private void button1_Click(object sender, EventArgs e)
     2          {
     3              Task task = Task.Factory.StartNew(() =>
     4              {
     5                  //复杂操作,等待10s
     6                  Thread.Sleep(10000);
     7  
     8              }).ContinueWith((t) =>
     9              {
    10                  button1.Text = "hello world";
    11              }, TaskScheduler.FromCurrentSynchronizationContext());
    12          }

    三:自定义TaskScheduler 

      我们知道在现有的.net framework中只有这么两种TaskScheduler,有些同学可能想问,这些Scheduler我用起来不爽,我想自定义一下,这个可

    以吗?当然!!!如果你想自定义,只要自定义一个类实现一下TaskScheduler就可以了,然后你可以将ThreadPoolTaskScheduler简化一下,即我要

    求所有的Task都需要走Thread,杜绝使用TheadPool,这样可以吗,当然了,不信你看。

     1 namespace ConsoleApplication1
     2 {
     3     class Program
     4     {
     5         static void Main(string[] args)
     6         {
     7             var task = Task.Factory.StartNew(() =>
     8             {
     9                 Console.WriteLine("hello world!!!");
    10             }, new CancellationToken(), TaskCreationOptions.None, new PerThreadTaskScheduler());
    11 
    12             Console.Read();
    13         }
    14     }
    15 
    16     /// <summary>
    17     /// 每个Task一个Thread
    18     /// </summary>
    19     public class PerThreadTaskScheduler : TaskScheduler
    20     {
    21         protected override IEnumerable<Task> GetScheduledTasks()
    22         {
    23             return null;
    24         }
    25 
    26         protected override void QueueTask(Task task)
    27         {
    28             var thread = new Thread(() =>
    29             {
    30                 TryExecuteTask(task);
    31             });
    32 
    33             thread.Start();
    34         }
    35 
    36         protected override bool TryExecuteTaskInline(Task task, bool taskWasPreviouslyQueued)
    37         {
    38             throw new NotImplementedException();
    39         }
    40     }
    41 }

      看到没有,自定义Task就是这么简单,其实自定义操作中最重要的就是其中的QueueTask方法,接下来我可以用windbg观察一下,确实是工作线程,而不是

    线程池,没骗你~~~

    好了,本篇就说到这里,希望对你有帮助。

  • 相关阅读:
    单例模式
    C++继承-重载-多态-虚函数
    c++仿函数 functor
    常用排序算法实现与效率比较
    树的中序非递归遍历
    二叉树递归遍历
    队列的顺序存储框架
    栈的链式存储框架
    栈的顺序存储构架
    函数指针和回调函数
  • 原文地址:https://www.cnblogs.com/huangxincheng/p/6781581.html
Copyright © 2020-2023  润新知