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

简介: 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);        }    }
      相关文章
      |
      7月前
      |
      消息中间件 前端开发 Java
      美团面试:如何实现线程任务编排?
      线程任务编排指的是对多个线程任务按照一定的逻辑顺序或条件进行组织和安排,以实现协同工作、顺序执行或并行执行的一种机制。 ## 1.线程任务编排 VS 线程通讯 有同学可能会想:那线程的任务编排是不是问的就是线程间通讯啊? 线程间通讯我知道了,它的实现方式总共有以下几种方式: 1. Object 类下的 wait()、notify() 和 notifyAll() 方法; 2. Condition 类下的 await()、signal() 和 signalAll() 方法; 3. LockSupport 类下的 park() 和 unpark() 方法。 但是,**线程通讯和线程的任务编排是
      74 1
      |
      4天前
      |
      Java 数据库连接 Maven
      最新版 | SpringBoot3如何自定义starter(面试常考)
      在Spring Boot中,starter是一种特殊的依赖,帮助开发人员快速引入和配置特定功能模块。自定义starter可以封装一组特定功能的依赖和配置,简化项目中的功能引入。其主要优点包括模块化、简化配置、提高代码复用性和实现特定功能。常见的应用场景有短信发送模块、AOP日志切面、分布式ID生成等。通过创建autoconfigure和starter两个Maven工程,并编写自动配置类及必要的配置文件,可以实现一个自定义starter。最后在测试项目中验证其有效性。这种方式使开发者能够更便捷地管理和维护代码,提升开发效率。
      最新版 | SpringBoot3如何自定义starter(面试常考)
      |
      2月前
      |
      NoSQL Java API
      美团面试:Redis锁如何续期?Redis锁超时,任务没完怎么办?
      在40岁老架构师尼恩的读者交流群中,近期有小伙伴在面试一线互联网企业时遇到了关于Redis分布式锁过期及自动续期的问题。尼恩对此进行了系统化的梳理,介绍了两种核心解决方案:一是通过增加版本号实现乐观锁,二是利用watch dog自动续期机制。后者通过后台线程定期检查锁的状态并在必要时延长锁的过期时间,确保锁不会因超时而意外释放。尼恩还分享了详细的代码实现和原理分析,帮助读者深入理解并掌握这些技术点,以便在面试中自信应对相关问题。更多技术细节和面试准备资料可在尼恩的技术文章和《尼恩Java面试宝典》中获取。
      美团面试:Redis锁如何续期?Redis锁超时,任务没完怎么办?
      |
      2月前
      |
      Android开发 Kotlin
      Android面试题之Kotlin中如何实现串行和并行任务?
      本文介绍了 Kotlin 中 `async` 和 `await` 在并发编程中的应用,包括并行与串行任务的处理方法。并通过示例代码展示了如何启动并收集异步任务的结果。
      36 0
      |
      4月前
      |
      NoSQL Java 数据库
      2022年整理最详细的java面试题、掌握这一套八股文、面试基础不成问题[吐血整理、纯手撸]
      这篇文章是一份详尽的Java面试题总结,涵盖了从面向对象基础到分布式系统设计的多个知识点,适合用来准备Java技术面试。
      2022年整理最详细的java面试题、掌握这一套八股文、面试基础不成问题[吐血整理、纯手撸]
      |
      5月前
      |
      存储 缓存 前端开发
      Java八股文面试之多线程篇
      Java八股文面试之多线程篇
      139 0
      Java八股文面试之多线程篇
      |
      6月前
      |
      存储 缓存 NoSQL
      Redis八股文(大厂面试真题)
      Redis八股文(大厂面试真题)
      229 1
      Redis八股文(大厂面试真题)
      |
      5月前
      |
      Android开发
      Android面试题之自定义View注意事项
      在Android开发中,自定义View主要分为四类:直接继承View重写onDraw,继承ViewGroup创建布局,扩展特定View如TextView,以及继承特定ViewGroup。实现时需注意:支持wrap_content通过onMeasure处理,支持padding需在onDraw或onMeasure/onLayout中处理。避免在View中使用Handler,使用post系列方法代替。记得在onDetachedFromWindow时停止线程和动画以防止内存泄漏。处理滑动嵌套时解决滑动冲突,并避免在onDraw中大量创建临时对象。
      66 4
      |
      5月前
      |
      设计模式 安全 Java
      Java面试题:设计模式如单例模式、工厂模式、观察者模式等在多线程环境下线程安全问题,Java内存模型定义了线程如何与内存交互,包括原子性、可见性、有序性,并发框架提供了更高层次的并发任务处理能力
      Java面试题:设计模式如单例模式、工厂模式、观察者模式等在多线程环境下线程安全问题,Java内存模型定义了线程如何与内存交互,包括原子性、可见性、有序性,并发框架提供了更高层次的并发任务处理能力
      90 1
      |
      6月前
      |
      消息中间件 算法 Java
      抖音面试:说说延迟任务的调度算法?
      Netty 框架是以性能著称的框架,因此在它的框架中使用了大量提升性能的机制,例如 Netty 用于实现延迟队列的时间轮调度算法就是一个典型的例子。使用时间轮调度算法可以实现海量任务新增和取消任务的时间度为 O(1),那么什么是时间轮调度算法呢?接下来我们一起来看。 ## 1.延迟任务实现 在 Netty 中,我们需要使用 HashedWheelTimer 类来实现延迟任务,例如以下代码: ```java public class DelayTaskExample { public static void main(String[] args) { System.ou
      61 5
      抖音面试:说说延迟任务的调度算法?

      热门文章

      最新文章