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

task默认是ThreadPoolTaskScheduler,无法更新UI线程。必需用App.Current.Dispatcher.Invoke来更新,或者设置SynchronizationContextTaskScheduler,如下!

用惯了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观察一下,确实是工作线程,而不是

线程池,没骗你~~~

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

时间: 2024-10-13 15:48:14

转发:用惯了Task,你应该也需要了解它的内部调度机制TaskScheduler的相关文章

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

平时我们在用多线程开发的时候少不了Task,确实task给我们带来了巨大的编程效率,在Task底层有一个TaskScheduler,它决定了task该如何被调度,而 在.net framework中有两种系统定义Scheduler,第一个是Task默认的ThreadPoolTaskScheduler,还是一种就是SynchronizationContextTaskScheduler, 以及这两种类型之外的如何自定义,这篇刚好和大家分享一下. 一: ThreadPoolTaskScheduler

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

平时我们在用多线程开发的时候少不了Task,确实task给我们带来了巨大的编程效率,在Task底层有一个TaskScheduler,它决定了task该如何被调度,而 在.net framework中有两种系统定义Scheduler,第一个是Task默认的ThreadPoolTaskScheduler,还是一种就是SynchronizationContextTaskScheduler, 以及这两种类型之外的如何自定义,这篇刚好和大家分享一下. 一: ThreadPoolTaskScheduler

IP网络QoS技术

1  概述 1.1  产生背景 在传统的IP网络中,所有的报文都被无区别的等同对待,每个转发设备对所有的报文均采用先入先出(FIFO)的策略进行处理,它尽最大的努力(Best-Effort)将报文送到目的地,但对报文传送的可靠性.传送延迟等性能不提供任何保证. 网络发展日新月异,随着IP网络上新应用的不断出现,对IP网络的服务质量也提出了新的要求,例如VoIP等实时业务就对报文的传输延迟提出了较高要求,如果报文传送延时太长,用户将不能接受(相对而言,E-Mail和FTP业务对时间延迟并不敏感).

大话Spark(9)-源码之TaskScheduler

上篇文章讲到DAGScheduler会把job划分为多个Stage,每个Stage中都会创建一批Task,然后把Task封装为TaskSet提交到TaskScheduler. 这里我们来一起看下TaskScheduler是如何把Task分配到应用程序的Executor上去执行. 重点是这里的task分配算法. 如下图是DagScheduler中把TaskSet提交到TaskScheduler: 这里我们以standalone模式为例,使用的是TaskSchedulerImpl,实现与TaskSc

Linux转发性能评估与优化(转发瓶颈分析与解决方案)

线速问题 很多人对这个线速概念存在误解.认为所谓线速能力就是路由器/交换机就像一根网线一样.而这,是不可能的.应该考虑到的一个概念就是延迟.数据包进入路由器或者交换机,存在一个核心延迟操作,这就是选路,对于路由器而言,就是路由查找,对于交换机而言,就是查询MAC/端口映射表,这个延迟是无法避开的,这个操作需要大量的计算机资源,所以不管是路由器还是交换机,数据包在内部是不可能像在线缆上那样近光速传输的.类比一下你经过十字街头的时候,是不是要左顾右盼呢? 那么,设备的线速能力怎么衡量呢?如果一个数据

Linux转发性能评估与优化-转发瓶颈分析与解决方式(补遗)

补遗 关于网络接收的软中断负载均衡,已经有了成熟的方案,可是该方案并不特别适合数据包转发,它对server的小包处理非常好.这就是RPS.我针对RPS做了一个patch.提升了其转发效率. 下面是我转载的我自己的原文. 线速问题 非常多人对这个线速概念存在误解.觉得所谓线速能力就是路由器/交换机就像一根网线一样.而这.是不可能的.应该考虑到的一个概念就是延迟. 数据包进入路由器或者交换机.存在一个核心延迟操作,这就是选路,对于路由器而言.就是路由查找,对于交换机而言,就是查询MAC/port映射

Linux转发性能评估与优化-转发瓶颈分析与解决方案(补遗)

补遗 关于网络接收的软中断负载均衡,已经有了成熟的方案,但是该方案并不特别适合数据包转发,它对服务器的小包处理非常好,这就是RPS.我针对RPS做了一个patch,提升了其转发效率. 下面是我转载的我自己的原文. 线速问题 很多人对这个线速概念存在误解.认为所谓线速能力就是路由器/交换机就像一根网线一样.而这,是不可能的.应该考虑到的一个概念就是延迟.数据包进入路由器或者交换机,存在一个核心延迟操作,这就是选路,对于路由器而言,就是路由查找,对于交换机而言,就是查询MAC/端口映射表,这个延迟是

实现基于Task的异步模式

返回该系列目录<基于Task的异步模式--全面介绍> 生成方法 编译器生成 在.NET Framework 4.5中,C#编译器实现了TAP.任何标有async关键字的方法都是异步方法,编译器会使用TAP执行必要的转换从而异步地实现方法.这样的方法应该返回Task或者Task<TResult>类型.在后者的案例中,方法体应该返回一个TResult,且编译器将确保通过返回的Task<TResult>是可利用的.相似地,方法体内未经处理的异常会被封送到输出的task,造成返

servlet中的转发和重定向问题

重定向和请求转发在学习servlet的时候很容易混淆,故在此特意记录. 1. 重定向---------sendRedirect()方法 Servlet响应请求有两种方式,一个是重定向,返回一个页面给客户端,另一种方式是直接向客户端输出内容. 重定向功能是让浏览器重新发生请求,但是将请求的是另外一个Servlet. A.使用方法: response.sendRedirect("/ServletProject/fristServlet"). forward(request, respons