實(shí)現(xiàn)常駐任務(wù)除了避免曇花線(xiàn)程,還需要避免重返線(xiàn)程池

前面我們使用簡(jiǎn)單的例子演示了 Task 和 Thread 的兩種制造曇花線(xiàn)程的方式。那么除了避免曇花線(xiàn)程,在實(shí)現(xiàn)常駐任務(wù)的時(shí)候,還需要避免重返線(xiàn)程池。本文將介紹如何避免重返線(xiàn)程池。
常駐任務(wù)
常駐任務(wù)非常常見(jiàn),比如:
我們正在編寫(xiě)一個(gè)日志文件庫(kù),我們希望在后臺(tái)不斷的將日志寫(xiě)入文件,盡可能不影響業(yè)務(wù)線(xiàn)程的執(zhí)行。因此,需要一個(gè)寫(xiě)文件的常駐任務(wù)。
我們對(duì)接了一個(gè)遠(yuǎn)程 TCP 服務(wù),對(duì)方要求我們每隔一段時(shí)間發(fā)送一個(gè)心跳包,以保持連接。因此,需要一個(gè)發(fā)送心跳包的常駐任務(wù)。
我們編寫(xiě)了一個(gè)簡(jiǎn)單的內(nèi)存緩存,通過(guò)一個(gè)后臺(tái)任務(wù)來(lái)定期清理過(guò)期的緩存。因此,需要一個(gè)清理緩存的常駐任務(wù)。
類(lèi)似的場(chǎng)景還有很多。因此,我們需要一個(gè)能夠?qū)崿F(xiàn)常駐任務(wù)的方法。
而實(shí)現(xiàn)常駐任務(wù)的主要要點(diǎn)是:
常駐任務(wù)必須避免影響業(yè)務(wù)線(xiàn)程的執(zhí)行,因此需要在后臺(tái)執(zhí)行。
常駐任務(wù)不能被業(yè)務(wù)線(xiàn)程影響,無(wú)論當(dāng)前業(yè)務(wù)多么繁忙,常駐任務(wù)都必須能夠正常執(zhí)行。否則會(huì)出現(xiàn)日志不落盤(pán),心跳包不發(fā)送,緩存不清理等問(wèn)題。
實(shí)現(xiàn)常駐任務(wù)的手段有很多。本文將圍繞如何使用常駐單一線(xiàn)程來(lái)實(shí)現(xiàn)常駐任務(wù)。
所謂常駐單一線(xiàn)程,就是指始終使用一個(gè)線(xiàn)程來(lái)執(zhí)行常駐任務(wù)。從而達(dá)到:
避免頻繁的創(chuàng)建和銷(xiāo)毀線(xiàn)程,從而避免頻繁的線(xiàn)程切換。
更容易的處理背壓?jiǎn)栴}。
更容易的處理線(xiàn)程安全問(wèn)題。
評(píng)測(cè)主體
我們將采用如下情況來(lái)評(píng)測(cè)如何編寫(xiě)常駐任務(wù)的正確性。
Bilibili 代碼塊無(wú)法正常渲染,因此無(wú)法正常顯示。請(qǐng)關(guān)注微信公眾號(hào)“newbe技術(shù)專(zhuān)欄”,搜索對(duì)應(yīng)文章代碼內(nèi)容。

這里我們定義了一個(gè) ProcessTest 方法,用于評(píng)測(cè)常駐任務(wù)的正確性。我們將在這個(gè)方法中啟動(dòng)常駐任務(wù),然后執(zhí)行一個(gè)嚴(yán)架給壓力的方法,來(lái)模擬非常繁忙的業(yè)務(wù)操作。最后我們將輸出常駐任務(wù)中的計(jì)數(shù)器的值。
可以初步看一下嚴(yán)架帶來(lái)的壓力有多大:

然后我們不妨假設(shè),我們的常駐任務(wù)是希望每秒進(jìn)行一次計(jì)數(shù)。那么最終在控制臺(tái)輸出的結(jié)果應(yīng)該是 5 或者 6。但如果小于 5,那么就說(shuō)明我們的常駐任務(wù)有問(wèn)題。
比如下面這樣:
Bilibili 代碼塊無(wú)法正常渲染,因此無(wú)法正常顯示。請(qǐng)關(guān)注微信公眾號(hào)“newbe技術(shù)專(zhuān)欄”,搜索對(duì)應(yīng)文章代碼內(nèi)容。

在該測(cè)試中,我們希望使用 Task.Run 來(lái)執(zhí)行我們期待的循環(huán),進(jìn)行每秒加一的操作。但是,我們發(fā)現(xiàn),最終輸出的結(jié)果是 1。這是因?yàn)椋?/p>
Task.Run 會(huì)將我們的任務(wù)放入 Task Default Scheduler 線(xiàn)程池中執(zhí)行。
但是由于迫于嚴(yán)架給壓力,我們的業(yè)務(wù)線(xiàn)程會(huì)一直處于繁忙狀態(tài),因此線(xiàn)程池中的線(xiàn)程也會(huì)一直處于繁忙狀態(tài)。
從而日導(dǎo)致我們的常駐任務(wù)無(wú)法正常執(zhí)行。
這里我們可以看到,Task.Run 并不是一種正確的實(shí)現(xiàn)常駐任務(wù)的方法。當(dāng)然實(shí)際上這也不是常駐單一線(xiàn)程,因?yàn)檫@樣本質(zhì)是使用了線(xiàn)程池。
歡迎關(guān)注作者的微信公眾號(hào)“newbe技術(shù)專(zhuān)欄”,獲取更多技術(shù)內(nèi)容。

全同步過(guò)程
結(jié)合我們之前提到的 TaskCreationOptions.LongRunning 以及 Thread 很容易在全同步的情況下實(shí)現(xiàn)常駐單一線(xiàn)程。
Bilibili 代碼塊無(wú)法正常渲染,因此無(wú)法正常顯示。請(qǐng)關(guān)注微信公眾號(hào)“newbe技術(shù)專(zhuān)欄”,搜索對(duì)應(yīng)文章代碼內(nèi)容。

這兩種正確的寫(xiě)法都實(shí)現(xiàn)了常駐單一線(xiàn)程,因此我們可以看到,最終輸出的結(jié)果都是 6。
曇花線(xiàn)程
那么自然,我們也可以知道,如果混合了曇花線(xiàn)程,那么就會(huì)出現(xiàn)問(wèn)題。
Bilibili 代碼塊無(wú)法正常渲染,因此無(wú)法正常顯示。請(qǐng)關(guān)注微信公眾號(hào)“newbe技術(shù)專(zhuān)欄”,搜索對(duì)應(yīng)文章代碼內(nèi)容。

這兩種錯(cuò)誤的寫(xiě)法都無(wú)法實(shí)現(xiàn)常駐單一線(xiàn)程,因此我們可以看到,最終輸出的結(jié)果都是 1。
不是有 Task 就是異步的
雖然不是本篇的關(guān)鍵內(nèi)容,但是還是額外補(bǔ)充兩個(gè) case 作為對(duì)比:
Bilibili 代碼塊無(wú)法正常渲染,因此無(wú)法正常顯示。請(qǐng)關(guān)注微信公眾號(hào)“newbe技術(shù)專(zhuān)欄”,搜索對(duì)應(yīng)文章代碼內(nèi)容。

在這兩個(gè) case 但中,雖然在 while 中包含了 wait Task,但是由于 Task.CompletedTask 實(shí)際上是一種同步代碼,所以并不會(huì)進(jìn)入到線(xiàn)程池當(dāng)中。因此也就不會(huì)出現(xiàn)錯(cuò)誤的情況。
但是這種錯(cuò)誤的原因不是因?yàn)闀一ň€(xiàn)程,是由于我們?cè)?Thread 中進(jìn)行了 Wait,但是被調(diào)用的 Task 如果確實(shí)是一個(gè)異步的 Task,那么由于線(xiàn)程池繁忙,我們的 Task 就會(huì)被延遲執(zhí)行,因此就會(huì)出現(xiàn)錯(cuò)誤的情況。
總結(jié)
在全同步的情況下,我們可以使用 TaskCreationOptions.LongRunning 或者 Thread 來(lái)實(shí)現(xiàn)常駐單一線(xiàn)程。從而實(shí)現(xiàn)穩(wěn)定的常駐任務(wù)。
注意 async/await 可能會(huì)導(dǎo)致線(xiàn)程池的使用,從而避免常駐單一線(xiàn)程被破壞。
我們暫未給出帶有異步代碼的情況下如何實(shí)現(xiàn)穩(wěn)定的常駐任務(wù),我們將在后續(xù)討論。
測(cè)試代碼:https://github.com/newbe36524/Newbe.Demo/tree/main/src/BlogDemos/Newbe.LongRunningJob
參考
.NET Task 揭秘(2):Task 的回調(diào)執(zhí)行與 await1
Task2
TaskCreationOptions3
這樣在 C# 使用 LongRunningTask 是錯(cuò)的4
async 與 Thread 的錯(cuò)誤結(jié)合5
感謝您的閱讀,如果您覺(jué)得本文有用,快長(zhǎng)按右下角大拇指??為本文點(diǎn)贊~
歡迎關(guān)注作者的微信公眾號(hào)“newbe技術(shù)專(zhuān)欄”,獲取更多技術(shù)內(nèi)容。

本文作者: newbe36524
本文鏈接: https://www.newbe.pro/Others/0x028-avoid-return-to-threadpool-in-longrunning-task/
版權(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?
https://www.newbe.pro/Others/0x026-This-is-the-wrong-way-to-use-LongRunnigTask-in-csharp/?
https://www.newbe.pro/Others/0x027-error-when-using-async-with-thread/?