這樣在 C# 使用 LongRunnigTask 是錯(cuò)的

Task.Factory.StartNew 有一個(gè)重載,是支持 TaskCreationOptions.LongRunning 參數(shù)來指定 Task 的特征的。但是可能在沒有注意的情況下,你就使用了錯(cuò)誤的用法。那么本文我們來簡單闡述一下這個(gè)參數(shù)的作用,和使用的注意要點(diǎn)。
這樣其實(shí)是錯(cuò)誤的
有的時(shí)候,你可能會(huì)這么寫:
Bilibili 代碼塊無法正常渲染,因此無法正常顯示。請(qǐng)關(guān)注微信公眾號(hào)“newbe技術(shù)專欄”,搜索對(duì)應(yīng)文章代碼內(nèi)容。

但其實(shí),這是個(gè)錯(cuò)誤的寫法。
為什么需要 LongRunning
我們通常兩種情況下會(huì)想到使用 TaskCreationOptions.LongRunning 參數(shù):
你的任務(wù)需要長時(shí)間運(yùn)行,比如一個(gè)循環(huán),或者一個(gè)死循環(huán)。用來從隊(duì)列中取數(shù)據(jù),然后處理數(shù)據(jù),或者是一些定時(shí)任務(wù)。
你的任務(wù)需要占用大量的 CPU 資源,是一個(gè)很大的循環(huán),比如要遍歷一個(gè)很大的數(shù)組,并做一些處理。
那么這個(gè)時(shí)候,我們就需要使用 TaskCreationOptions.LongRunning 參數(shù)來指定 Task。
因?yàn)槲覀兛赡軐W(xué)習(xí)到了,Task 默認(rèn)的 Scheduler 是 ThreadPool,而 ThreadPool 的線程是有限的,如果你的任務(wù)需要長時(shí)間運(yùn)行,或者是需要占用大量的 CPU 資源,那么就會(huì)導(dǎo)致 ThreadPool 的線程不夠用。導(dǎo)致線程饑餓,或者是線程池的線程被占用,導(dǎo)致其他的任務(wù)無法執(zhí)行。
于是我們很聰明的就想到了,我們可以使用 TaskCreationOptions.LongRunning 參數(shù)來指定 Task,這樣就可以避免線程饑餓。
弄巧成拙
但是實(shí)際上,開篇的寫法并不能達(dá)到我們的目的。
我們可以通過以下代碼來驗(yàn)證一下:
Bilibili 代碼塊無法正常渲染,因此無法正常顯示。請(qǐng)關(guān)注微信公眾號(hào)“newbe技術(shù)專欄”,搜索對(duì)應(yīng)文章代碼內(nèi)容。

我們可以看到,Task 的狀態(tài)是并非是 Running,而是 RanToCompletion。
也就是說,我們的任務(wù)在 3 秒后就已經(jīng)執(zhí)行完了,而不是我們想要的長時(shí)間運(yùn)行。
究其原因,是因?yàn)槲覀儾捎昧水惒降姆绞絹韴?zhí)行任務(wù)。而異步任務(wù)的執(zhí)行,是通過 ThreadPool 來執(zhí)行的。也就是說,雖然我們使用了 TaskCreationOptions.LongRunning 參數(shù),來想辦法指定線程池單獨(dú)開一個(gè)線程,但是實(shí)際上在一個(gè) await 之后,我們的任務(wù)還是在 ThreadPool 中執(zhí)行的。
這會(huì)導(dǎo)致,我們的任務(wù)實(shí)際上后續(xù)又回到了 ThreadPool 中,而不是我們想要的單獨(dú)的線程。起不到單獨(dú)長期運(yùn)行的作用。
正確的寫法
因此,實(shí)際上如果想要保持單獨(dú)的線程持續(xù)的運(yùn)行,我們需要移除異步的方式,改為同步的方式。
Bilibili 代碼塊無法正常渲染,因此無法正常顯示。請(qǐng)關(guān)注微信公眾號(hào)“newbe技術(shù)專欄”,搜索對(duì)應(yīng)文章代碼內(nèi)容。

這樣我們就可以看到,Task 的狀態(tài)是 Running,而不是 RanToCompletion。我們通過 TaskCreationOptions.LongRunning 參數(shù),單獨(dú)開啟的線程就可以一直運(yùn)行下去。
實(shí)際上還有很多考量
要考量 TaskScheduler 的實(shí)現(xiàn)
本文采用的是 aspnetcore 的實(shí)現(xiàn),但是在其他的實(shí)現(xiàn)中,可能會(huì)有不同的實(shí)現(xiàn)。你也完全有可能實(shí)現(xiàn)一個(gè) await 之后,不回到 ThreadPool 的實(shí)現(xiàn)。
LongRunning 也不是就不能用異步
正如開篇提到的第二種場景,如果你的業(yè)務(wù)是在第一個(gè) await 之前有大量的同步代碼,那么此時(shí)單獨(dú)開啟一個(gè)線程,也是有意義的。
我就是一個(gè)死循環(huán),里面也是異步的怎么辦
那么你可以考慮讓這個(gè) LongRuning 的 Task,不要 await,而是通過 Wait() 來等待。這樣就可以避免 LongRunning 的 Task 直接結(jié)束。
總結(jié)
本文我們簡單闡述了 TaskCreationOptions.LongRunning 參數(shù)的作用,和使用的注意要點(diǎn)。
參考
.NET Task 揭秘(2):Task 的回調(diào)執(zhí)行與 await1
Task2
TaskCreationOptions3
感謝您的閱讀,如果您覺得本文有用,快長按右下角大拇指??為本文點(diǎn)贊~
歡迎關(guān)注作者的微信公眾號(hào)“newbe技術(shù)專欄”,獲取更多技術(shù)內(nèi)容。

本文作者: newbe36524
本文鏈接: https://www.newbe.pro/Others/0x026-This-is-the-wrong-way-to-use-LongRunnigTask-in-csharp/
版權(quán)聲明: 本博客所有文章除特別聲明外,均采用 BY-NC-SA 許可協(xié)議。轉(zhuǎn)載請(qǐng)注明出處!
https://www.cnblogs.com/eventhorizon/p/15912383.html?
https://threads.whuanle.cn/3.task/?
https://learn.microsoft.com/en-us/dotnet/api/system.threading.tasks.taskcreationoptions?view=net-7.0&WT.mc_id=DX-MVP-5003606?