面試八股文:你寫過(guò)自定義任務(wù)調(diào)度器嗎?
本文轉(zhuǎn)載自微信公眾號(hào)「全棧碼農(nóng)畫像」,作者小碼甲。轉(zhuǎn)載本文請(qǐng)聯(lián)系全棧碼農(nóng)畫像公眾號(hào)。
最近入職了新公司,嘗試閱讀祖?zhèn)鞔a,記錄并更新最近的編程認(rèn)知。
思緒由Q1引發(fā),后續(xù)Q2、Q3基于Q1的發(fā)散探究
Q1. Task.Run、Task.Factory.StartNew 的區(qū)別?
我們常使用Task.Run和Task.Factory.StartNew創(chuàng)建并啟動(dòng)任務(wù),但是他們的區(qū)別在哪里?在哪種場(chǎng)景下使用前后者?
官方推薦使用Task.Run方法啟動(dòng)基于計(jì)算的任務(wù), 當(dāng)需要對(duì)長(zhǎng)時(shí)間運(yùn)行、基于計(jì)算的任務(wù)做精細(xì)化控制時(shí)使用Task.Factory.StartNew。
Task.Factory提供了自定義選項(xiàng)、自定義調(diào)度器的能力,這也說(shuō)明了Task.Run是Task.Factory.StartNew的一個(gè)特例,Task.Run 只是提供了一個(gè)無(wú)參、默認(rèn)的任務(wù)創(chuàng)建和調(diào)度方式。
當(dāng)你在Task.Run傳遞委托
- Task.Run(someAction);
實(shí)際上等價(jià)于
- Task.Factory.StartNew(someAction,
- CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default);
一個(gè)長(zhǎng)時(shí)間運(yùn)行的任務(wù),如果使用Task.Run鐵定會(huì)使用線程池線程,可能構(gòu)成濫用線程池線程,這個(gè)時(shí)候最好在獨(dú)立線程中執(zhí)行任務(wù)。
Q2. 既然說(shuō)到Task.Run使用線程池線程,線程池線程有哪些特征?為什么有自定義調(diào)度器一說(shuō)?
github: TaskScheduler[1] 251行顯示TaskScheduler.Dafult確實(shí)是線程池任務(wù)調(diào)度器。
線程池[2]線程的特征:
① 池中線程都是后臺(tái)線程
② 線程可重用,一旦線程池中的線程完成任務(wù),將返回到等待線程隊(duì)列中, 避免了創(chuàng)建線程的開銷
③ 池中預(yù)熱了工作者線程、IO線程
我啟動(dòng)一個(gè)腳手架項(xiàng)目:默認(rèn)最大工作者線程32767,最大IO線程1000 ; 默認(rèn)最小工作線程數(shù)、最小IO線程數(shù)均為8個(gè)
github: ThreadPoolTaskScheduler[3] 顯示線程池任務(wù)調(diào)度器是這樣調(diào)度任務(wù)的:
- /// <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);
- }
- }
請(qǐng)注意8-14行:若上層使用者將LongRunning任務(wù)應(yīng)用到默認(rèn)的任務(wù)調(diào)度器(也即ThreadPoolTaskScheduler),ThreadPoolTaskScheduler會(huì)有一個(gè)兜底方案:會(huì)將任務(wù)放在獨(dú)立線程上執(zhí)行。
何時(shí)不使用線程池線程
有幾種應(yīng)用場(chǎng)景,其中適合創(chuàng)建并管理自己的線程,而非使用線程池線程:
- 需要一個(gè)前臺(tái)線程。
- 需要具有特定優(yōu)先級(jí)的線程。
- 擁有會(huì)導(dǎo)致線程長(zhǎng)時(shí)間阻塞的任務(wù)。線程池具有最大線程數(shù),因此大量被阻塞的線程池線程可能會(huì)阻止任務(wù)啟動(dòng)。
- 需將線程放入單線程單元。所有 ThreadPool 線程均位于多線程單元中。
- 需具有與線程關(guān)聯(lián)的穩(wěn)定標(biāo)識(shí),或需將一個(gè)線程專用于一項(xiàng)任務(wù)。
Q3. 既然要自定義任務(wù)調(diào)度器,那我們就來(lái)倒騰?
實(shí)現(xiàn)TaskScheduler 抽象類,其中的抓手是“調(diào)度”,也就是 QueueTask、TryExecuteTask 方法,之后你可以自定義數(shù)據(jù)結(jié)構(gòu)和算法, 從數(shù)據(jù)結(jié)構(gòu)中調(diào)度出任務(wù)執(zhí)行。
給個(gè)例子:
- 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);
- }
- }
引用鏈接
[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