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

简介: 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月前
      |
      Java 调度 Windows
      JAVA面试八股文之多线程基础知识
      JAVA面试八股文之多线程基础知识
      |
      6月前
      |
      消息中间件 人工智能 Java
      面试了一个前阿里P7,Java八股文与架构核心知识简直背得炉火纯青
      前几天,跟个老朋友吃饭,他最近想跳槽去大厂,觉得压力很大,问我能不能分享些所谓的经验套路。 每次有这类请求,都觉得有些有趣,不知道你发现没有大家身边真的有很多人不知道怎么面试,也不知道怎么准备面试,哪怕是一些工龄比较长的“老开发”: 有的人明知道有些问题肯定会被问,面试前还不好好准备,结果要么回答得模棱两可,要么答非所问; 有的人则是不知道怎么包装自己的项目经历,结果明明还不错的项目却看上去平平无奇,过后就被面试官忘了; 更有甚者,简历写得花里胡哨,结果一问三不知,简历和经历完全对不上。
      159 0
      |
      21天前
      |
      缓存 NoSQL Java
      面试官:Redis如何实现延迟任务?
      延迟任务是计划任务,用于在未来特定时间执行。常见应用场景包括定时通知、异步处理、缓存管理、计划任务、订单处理、重试机制、提醒和数据采集。Redis虽无内置延迟任务功能,但可通过过期键通知、ZSet或Redisson实现。然而,这种方法精度有限,稳定性较差,适合轻量级需求。Redisson的RDelayedQueue提供更简单的延迟队列实现。
      340 9
      |
      1月前
      |
      消息中间件 Dubbo Java
      24年国内头条最牛的Java面试八股文1000集,不接受反驳!
      年后这个时间段, 找工作面试不要停!! 很多朋友据我了解,技术水平和工作经验都很不错,但是面试频频败北。 大家复盘下来发现问题不严重,但是很普遍,10个人里面8个都存在,那就是面试前不做准备。 技巧和避坑先不论,面试题型就不熟悉,没有系统过下大厂真题和必问项目,真正对线上面试官时被打的措手不及。 想要从容应对,就要提前建立把握和自信,这不但来自自身的技术能力水平,更来源于对面试时将要发生的各种情况有预判,做到心中有数。 这里整理了一套跳槽涨薪大厂Java知识点解析及面试题解析,涵盖20个技术栈的大厂面试题及详解文档,各大厂技术重点、面试难点、进阶要点,帮助大家“临阵磨枪”,如有需要的
      |
      3月前
      |
      Java 数据库连接 数据库
      【万字长文】Java面试八股文:深入剖析常见问题与解答
      【万字长文】Java面试八股文:深入剖析常见问题与解答
      335 0
      |
      3月前
      |
      设计模式 算法 Java
      2023最新发布!三天吃透Java面试八股文,面试通过率高达95%
      什么样的求职者能够获得面试官的青睐?求职者需要准备哪些内容来面对形形色色的面试官?(文末附免费领取方式!)
      |
      3月前
      |
      消息中间件 人工智能 Java
      面试了一个前阿里P7,Java八股文与架构核心知识简直背得炉火纯青
      前几天,跟个老朋友吃饭,他最近想跳槽去大厂,觉得压力很大,问我能不能分享些所谓的经验套路。 每次有这类请求,都觉得有些有趣,不知道你发现没有大家身边真的有很多人不知道怎么面试,也不知道怎么准备面试,哪怕是一些工龄比较长的“老开发”: 有的人明知道有些问题肯定会被问,面试前还不好好准备,结果要么回答得模棱两可,要么答非所问; 有的人则是不知道怎么包装自己的项目经历,结果明明还不错的项目却看上去平平无奇,过后就被面试官忘了; 更有甚者,简历写得花里胡哨,结果一问三不知,简历和经历完全对不上。
      |
      3月前
      |
      C语言 C++
      面试题 10.02:变位词组(自定义哈希函数)
      面试题 10.02:变位词组(自定义哈希函数)
      30 0
      |
      3月前
      |
      监控 NoSQL Java
      面试官:实际工作中哪里用到了自定义注解?
      面试官:实际工作中哪里用到了自定义注解?
      52 2
      |
      3月前
      |
      缓存 前端开发 JavaScript
      前端铜九铁十面试必备八股文——性能优化
      前端铜九铁十面试必备八股文——性能优化