Task.Factory.StartNew 有一個過載,是支援 TaskCreationOptions.LongRunning 引數來指定 Task 的特徵的。但是可能在沒有注意的情況下,你就使用了錯誤的用法。那麼本文我們來簡單闡述一下這個引數的作用,和使用的注意要點。
有的時候,你可能會這麼寫:
Task.Factory.StartNew(async () =>
{
while (true)
{
// do something
await Task.Delay(1000);
}
}, TaskCreationOptions.LongRunning);
但其實,這是個錯誤的寫法。
我們通常兩種情況下會想到使用 TaskCreationOptions.LongRunning 引數:
那麼這個時候,我們就需要使用 TaskCreationOptions.LongRunning 引數來指定 Task。
因為我們可能學習到了,Task 預設的 Scheduler 是 ThreadPool,而 ThreadPool 的執行緒是有限的,如果你的任務需要長時間執行,或者是需要佔用大量的 CPU 資源,那麼就會導致 ThreadPool 的執行緒不夠用。導致執行緒飢餓,或者是執行緒池的執行緒被佔用,導致其他的任務無法執行。
於是我們很聰明的就想到了,我們可以使用 TaskCreationOptions.LongRunning 引數來指定 Task,這樣就可以避免執行緒飢餓。
但是實際上,開篇的寫法並不能達到我們的目的。
我們可以通過以下程式碼來驗證一下:
var task = Task.Factory.StartNew(async () =>
{
while (true)
{
// do something
await Task.Delay(1000);
}
}, TaskCreationOptions.LongRunning);
Thread.Sleep(3000);
Console.WriteLine($"Task Status: {task.Status}");
// Task Status: RanToCompletion
我們可以看到,Task 的狀態是並非是 Running,而是 RanToCompletion。
也就是說,我們的任務在 3 秒後就已經執行完了,而不是我們想要的長時間執行。
究其原因,是因為我們採用了非同步的方式來執行任務。而非同步任務的執行,是通過 ThreadPool 來執行的。也就是說,雖然我們使用了 TaskCreationOptions.LongRunning 引數,來想辦法指定執行緒池單獨開一個執行緒,但是實際上在一個 await 之後,我們的任務還是在 ThreadPool 中執行的。
這會導致,我們的任務實際上後續又回到了 ThreadPool 中,而不是我們想要的單獨的執行緒。起不到單獨長期執行的作用。
因此,實際上如果想要保持單獨的執行緒持續的執行,我們需要移除非同步的方式,改為同步的方式。
var task = Task.Factory.StartNew(() =>
{
while (true)
{
// do something
Thread.Sleep(1000);
}
}, TaskCreationOptions.LongRunning);
Thread.Sleep(3000);
Console.WriteLine($"Task Status: {task.Status}");
// Task Status: Running
這樣我們就可以看到,Task 的狀態是 Running,而不是 RanToCompletion。我們通過 TaskCreationOptions.LongRunning 引數,單獨開啟的執行緒就可以一直執行下去。
本文采用的是 aspnetcore 的實現,但是在其他的實現中,可能會有不同的實現。你也完全有可能實現一個 await 之後,不回到 ThreadPool 的實現。
正如開篇提到的第二種場景,如果你的業務是在第一個 await 之前有大量的同步程式碼,那麼此時單獨開啟一個執行緒,也是有意義的。
那麼你可以考慮讓這個 LongRuning 的 Task,不要 await,而是通過 Wait() 來等待。這樣就可以避免 LongRunning 的 Task 直接結束。
本文我們簡單闡述了 TaskCreationOptions.LongRunning 引數的作用,和使用的注意要點。
感謝閱讀,如果覺得本文有用,不妨點選推薦