面试八股文:你写过自定义任务调度器吗?

企业动态
我们常使用Task.Run和Task.Factory.StartNew创建并启动任务,但是他们的区别在哪里?在哪种场景下使用前后者?

[[397412]]

本文转载自微信公众号「全栈码农画像」,作者小码甲。转载本文请联系全栈码农画像公众号。

最近入职了新公司,尝试阅读祖传代码,记录并更新最近的编程认知。

思绪由Q1引发,后续Q2、Q3基于Q1的发散探究

Q1. Task.Run、Task.Factory.StartNew 的区别?

我们常使用Task.Run和Task.Factory.StartNew创建并启动任务,但是他们的区别在哪里?在哪种场景下使用前后者?

官方推荐使用Task.Run方法启动基于计算的任务, 当需要对长时间运行、基于计算的任务做精细化控制时使用Task.Factory.StartNew。

Task.Factory提供了自定义选项、自定义调度器的能力,这也说明了Task.Run是Task.Factory.StartNew的一个特例,Task.Run 只是提供了一个无参、默认的任务创建和调度方式。

当你在Task.Run传递委托

  1. Task.Run(someAction); 

实际上等价于

  1. Task.Factory.StartNew(someAction,  
  2. CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default); 

一个长时间运行的任务,如果使用Task.Run铁定会使用线程池线程,可能构成滥用线程池线程,这个时候最好在独立线程中执行任务。

Q2. 既然说到Task.Run使用线程池线程,线程池线程有哪些特征?为什么有自定义调度器一说?

github: TaskScheduler[1] 251行显示TaskScheduler.Dafult确实是线程池任务调度器。

线程池[2]线程的特征:

① 池中线程都是后台线程

② 线程可重用,一旦线程池中的线程完成任务,将返回到等待线程队列中, 避免了创建线程的开销

③ 池中预热了工作者线程、IO线程

我启动一个脚手架项目:默认最大工作者线程32767,最大IO线程1000 ; 默认最小工作线程数、最小IO线程数均为8个

github: ThreadPoolTaskScheduler[3] 显示线程池任务调度器是这样调度任务的:

  1. /// <summary> 
  2. /// Schedules a task to the ThreadPool. 
  3. /// </summary> 
  4. /// <param name="task">The task to schedule.</param> 
  5. protected internal override void QueueTask(Task task) 
  6.      TaskCreationOptions options = task.Options; 
  7.      if ((options & TaskCreationOptions.LongRunning) != 0) 
  8.      { 
  9.           // Run LongRunning tasks on their own dedicated thread. 
  10.           Thread thread = new Thread(s_longRunningThreadWork); 
  11.           thread.IsBackground = true; // Keep this thread from blocking process shutdown 
  12.           thread.Start(task); 
  13.     } 
  14.     else 
  15.     { 
  16.          // Normal handling for non-LongRunning tasks. 
  17.         bool preferLocal = ((options & TaskCreationOptions.PreferFairness) == 0); 
  18.         ThreadPool.UnsafeQueueUserWorkItemInternal(task, preferLocal); 
  19.     } 

请注意8-14行:若上层使用者将LongRunning任务应用到默认的任务调度器(也即ThreadPoolTaskScheduler),ThreadPoolTaskScheduler会有一个兜底方案:会将任务放在独立线程上执行。

何时不使用线程池线程

有几种应用场景,其中适合创建并管理自己的线程,而非使用线程池线程:

  • 需要一个前台线程。
  • 需要具有特定优先级的线程。
  • 拥有会导致线程长时间阻塞的任务。线程池具有最大线程数,因此大量被阻塞的线程池线程可能会阻止任务启动。
  • 需将线程放入单线程单元。所有 ThreadPool 线程均位于多线程单元中。
  • 需具有与线程关联的稳定标识,或需将一个线程专用于一项任务。

Q3. 既然要自定义任务调度器,那我们就来倒腾?

实现TaskScheduler 抽象类,其中的抓手是“调度”,也就是 QueueTask、TryExecuteTask 方法,之后你可以自定义数据结构和算法, 从数据结构中调度出任务执行。

给个例子:

  1. public sealed class CustomTaskScheduler : TaskScheduler, IDisposable 
  2.     { 
  3.         private BlockingCollection<Task> tasksCollection = new BlockingCollection<Task>(); 
  4.         private readonly Thread mainThread = null
  5.         public CustomTaskScheduler() 
  6.         { 
  7.             mainThread = new Thread(new ThreadStart(Execute)); 
  8.             if (!mainThread.IsAlive) 
  9.             { 
  10.                 mainThread.Start(); 
  11.             } 
  12.         } 
  13.         private void Execute() 
  14.         { 
  15.             foreach (var task in tasksCollection.GetConsumingEnumerable()) 
  16.             { 
  17.                 TryExecuteTask(task); 
  18.             } 
  19.         } 
  20.         protected override IEnumerable<Task> GetScheduledTasks() 
  21.         { 
  22.             return tasksCollection.ToArray(); 
  23.         } 
  24.         protected override void QueueTask(Task task) 
  25.         { 
  26.             if (task != null
  27.                 tasksCollection.Add(task);            
  28.         } 
  29.         protected override bool TryExecuteTaskInline(Task task, bool taskWasPreviouslyQueued) 
  30.         { 
  31.             return false
  32.         } 
  33.         private void Dispose(bool disposing) 
  34.         { 
  35.             if (!disposing) return
  36.             tasksCollection.CompleteAdding(); 
  37.             tasksCollection.Dispose(); 
  38.         } 
  39.         public void Dispose() 
  40.         { 
  41.             Dispose(true); 
  42.             GC.SuppressFinalize(this); 
  43.         } 
  44.     } 

引用链接

[1] github: TaskScheduler: https://github.com/dotnet/coreclr/blob/master/src/System.Private.CoreLib/shared/System/Threading/Tasks/TaskScheduler.cs

[2] 线程池: https://docs.microsoft.com/en-us/dotnet/standard/threading/the-managed-thread-pool

 

[3] github: ThreadPoolTaskScheduler: https://github.com/dotnet/coreclr/blob/master/src/System.Private.CoreLib/shared/System/Threading/Tasks/ThreadPoolTaskScheduler.cs

 

责任编辑:武晓燕 来源: 全栈码农画像
相关推荐

2021-10-26 14:40:03

MySQL SQL 语句数据库

2021-11-04 14:32:17

Spring 面试作用域

2021-10-21 14:43:23

Java 语言 Java 基础

2021-09-07 14:46:42

面试网络HTTP 协议

2021-07-26 14:59:23

面试Redis内存数据库

2024-02-23 19:17:12

构造函数C++开发

2021-10-26 17:05:55

Redis字符串复杂度

2023-11-28 18:09:49

Java多态

2022-09-03 11:36:11

Python文件网络

2021-08-01 22:59:43

Object八股文quals

2021-04-14 10:02:59

网络八股文协议

2021-08-12 09:28:24

Java多线程变量

2021-05-20 11:43:57

操作系统硬件软件

2023-11-29 17:28:07

2024-10-12 09:26:32

线程池系统核心线程

2023-01-13 18:04:03

面试题消息中间件

2023-12-12 13:38:00

Java异步编程

2024-06-05 10:59:51

2021-07-05 07:55:11

String[]byte转换

2022-05-27 14:43:45

JVM字节码指令
点赞
收藏

51CTO技术栈公众号