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

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

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


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


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


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


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


当你在Task.Run传递委托


Task.Run(someAction);


实际上等价于


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


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


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


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

3d1dbec2dc06c499f41595345c4eae2b.png


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


① 池中线程都是后台线程


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


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


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


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


    /// <summary>/// Schedules a task to the ThreadPool./// </summary>/// <param name="task">The task to schedule.</param>protected internal override void QueueTask(Task task){     TaskCreationOptions options = task.Options;     if ((options & TaskCreationOptions.LongRunning) != 0)     {          // Run LongRunning tasks on their own dedicated thread.          Thread thread = new Thread(s_longRunningThreadWork);          thread.IsBackground = true; // Keep this thread from blocking process shutdown          thread.Start(task);    }    else    {         // Normal handling for non-LongRunning tasks.        bool preferLocal = ((options & TaskCreationOptions.PreferFairness) == 0);        ThreadPool.UnsafeQueueUserWorkItemInternal(task, preferLocal);    }}


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


    何时不使用线程池线程


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


    需要一个前台线程。


    需要具有特定优先级的线程。


    拥有会导致线程长时间阻塞的任务。线程池具有最大线程数,因此大量被阻塞的线程池线程可能会阻止任务启动。


    需将线程放入单线程单元。所有 ThreadPool 线程均位于多线程单元中。


    需具有与线程关联的稳定标识,或需将一个线程专用于一项任务。


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


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


    给个例子:


      public sealed class CustomTaskScheduler : TaskScheduler, IDisposable    {        private BlockingCollection<Task> tasksCollection = new BlockingCollection<Task>();        private readonly Thread mainThread = null;        public CustomTaskScheduler()        {            mainThread = new Thread(new ThreadStart(Execute));            if (!mainThread.IsAlive)            {                mainThread.Start();            }        }        private void Execute()        {            foreach (var task in tasksCollection.GetConsumingEnumerable())            {                TryExecuteTask(task);            }        }        protected override IEnumerable<Task> GetScheduledTasks()        {            return tasksCollection.ToArray();        }        protected override void QueueTask(Task task)        {            if (task != null)                tasksCollection.Add(task);                   }        protected override bool TryExecuteTaskInline(Task task, bool taskWasPreviouslyQueued)        {            return false;        }        private void Dispose(bool disposing)        {            if (!disposing) return;            tasksCollection.CompleteAdding();            tasksCollection.Dispose();        }        public void Dispose()        {            Dispose(true);            GC.SuppressFinalize(this);        }    }
      相关文章
      |
      3月前
      |
      人工智能 自然语言处理 机器人
      大厂RAG面试题:24个RAG八股文。偷偷背下来,毒打面试官 !
      大厂RAG面试题:24个RAG八股文。偷偷背下来,毒打面试官 !
      大厂RAG面试题:24个RAG八股文。偷偷背下来,毒打面试官 !
      |
      4月前
      |
      存储 安全 Java
      2025 年一线互联网大厂最新高质量 Java 面试八股文整理及答案汇总
      本文整理了一线互联网大厂最新的高质量Java面试八股文及其答案,涵盖Java基础、集合框架与多线程三大核心模块。内容包括面向对象与面向过程的区别、`equals`与`==`的差异、`final`和`static`的用法、集合类如`ArrayList`与`LinkedList`的对比、`HashMap`的工作原理及其与`Hashtable`的区别,以及多线程中的线程创建方式、生命周期、上下文切换和死锁等知识点。通过系统化的梳理与解析,帮助读者高效备考Java面试,掌握核心技术要点。资源可从文末链接下载。
      486 40
      |
      4月前
      |
      存储 安全 Java
      2025 年一线互联网大厂最新高质量 Java 面试八股文整理带答案及实操要点
      本文整理了一线互联网大厂最新的高质量Java面试八股文及答案,涵盖Java基础、集合、多线程等多个核心方面,帮助你高效备考。内容包括面向对象与面向过程的区别、`equals`与`==`的对比、`final`和`static`的用法,以及ArrayList与LinkedList的区别、HashMap的工作原理等。同时,深入探讨了多线程创建方式、生命周期、上下文切换及死锁问题,并附有实操代码示例。资源链接:[点此下载](https://pan.quark.cn/s/14fcf913bae6)。
      954 1
      |
      10月前
      |
      Java 数据库连接 Maven
      最新版 | SpringBoot3如何自定义starter(面试常考)
      在Spring Boot中,starter是一种特殊的依赖,帮助开发人员快速引入和配置特定功能模块。自定义starter可以封装一组特定功能的依赖和配置,简化项目中的功能引入。其主要优点包括模块化、简化配置、提高代码复用性和实现特定功能。常见的应用场景有短信发送模块、AOP日志切面、分布式ID生成等。通过创建autoconfigure和starter两个Maven工程,并编写自动配置类及必要的配置文件,可以实现一个自定义starter。最后在测试项目中验证其有效性。这种方式使开发者能够更便捷地管理和维护代码,提升开发效率。
      1143 1
      最新版 | SpringBoot3如何自定义starter(面试常考)
      |
      12月前
      |
      NoSQL Java API
      美团面试:Redis锁如何续期?Redis锁超时,任务没完怎么办?
      在40岁老架构师尼恩的读者交流群中,近期有小伙伴在面试一线互联网企业时遇到了关于Redis分布式锁过期及自动续期的问题。尼恩对此进行了系统化的梳理,介绍了两种核心解决方案:一是通过增加版本号实现乐观锁,二是利用watch dog自动续期机制。后者通过后台线程定期检查锁的状态并在必要时延长锁的过期时间,确保锁不会因超时而意外释放。尼恩还分享了详细的代码实现和原理分析,帮助读者深入理解并掌握这些技术点,以便在面试中自信应对相关问题。更多技术细节和面试准备资料可在尼恩的技术文章和《尼恩Java面试宝典》中获取。
      美团面试:Redis锁如何续期?Redis锁超时,任务没完怎么办?
      |
      12月前
      |
      Android开发 Kotlin
      Android面试题之Kotlin中如何实现串行和并行任务?
      本文介绍了 Kotlin 中 `async` 和 `await` 在并发编程中的应用,包括并行与串行任务的处理方法。并通过示例代码展示了如何启动并收集异步任务的结果。
      170 0
      |
      存储 缓存 前端开发
      Java八股文面试之多线程篇
      Java八股文面试之多线程篇
      471 0
      Java八股文面试之多线程篇
      |
      NoSQL Java 数据库
      2022年整理最详细的java面试题、掌握这一套八股文、面试基础不成问题[吐血整理、纯手撸]
      这篇文章是一份详尽的Java面试题总结,涵盖了从面向对象基础到分布式系统设计的多个知识点,适合用来准备Java技术面试。
      2022年整理最详细的java面试题、掌握这一套八股文、面试基础不成问题[吐血整理、纯手撸]
      |
      存储 缓存 NoSQL
      Redis八股文(大厂面试真题)
      Redis八股文(大厂面试真题)
      447 1
      Redis八股文(大厂面试真题)
      |
      消息中间件 算法 Java
      抖音面试:说说延迟任务的调度算法?
      Netty 框架是以性能著称的框架,因此在它的框架中使用了大量提升性能的机制,例如 Netty 用于实现延迟队列的时间轮调度算法就是一个典型的例子。使用时间轮调度算法可以实现海量任务新增和取消任务的时间度为 O(1),那么什么是时间轮调度算法呢?接下来我们一起来看。 ## 1.延迟任务实现 在 Netty 中,我们需要使用 HashedWheelTimer 类来实现延迟任务,例如以下代码: ```java public class DelayTaskExample { public static void main(String[] args) { System.ou
      141 5
      抖音面试:说说延迟任务的调度算法?