在上一篇文章《如何正確實(shí)現(xiàn)一個(gè) BackgroundService》中有提到 LongRunning 來優(yōu)化后臺(tái)任務(wù)始終保持在同一個(gè)線程上。
protected override Task ExecuteAsync(CancellationToken stoppingToken)
{
return Task.Factory.StartNew(async () =>
{
while (!stoppingToken.IsCancellationRequested)
{
Console.WriteLine("HostServiceTest_A is doing work.");
LongTermTask();
await Task.Delay(1000, stoppingToken);
}
Console.WriteLine("HostServiceTest_A task done.");
}, TaskCreationOptions.LongRunning);
}
private void LongTermTask()
{
Console.WriteLine("LongTermTaskA is doing work.");
Thread.Sleep(30000);
}
但是被 大佬指出這個(gè)用法是錯(cuò)誤的:以上用法并不能保證任務(wù)始終在同一個(gè) Task(線程) 上執(zhí)行。原因是當(dāng)碰到第一個(gè) await 之后運(yùn)行時(shí)會(huì)從 ThreadPool 中調(diào)度一個(gè)新的線程來執(zhí)行后面的代碼,而當(dāng)前線程被釋放。這個(gè)時(shí)候就不符合我們使用 LongRunning 的期望了。
在 .NET 中,Task.Factory.StartNew 提供了 TaskCreationOptions.LongRunning 選項(xiàng),很多開發(fā)者會(huì)用它來啟動(dòng)長(zhǎng)時(shí)間運(yùn)行的任務(wù),并且想當(dāng)然的認(rèn)為它會(huì)永遠(yuǎn)執(zhí)行在同一個(gè)線程上。但是事實(shí)上當(dāng)遇到 async await 的時(shí)候并想象的那么簡(jiǎn)單。
下面我們還是通過一個(gè)錯(cuò)誤的示例開始講解如何正確的使用它。
錯(cuò)誤用法
很多人會(huì)直接在 Task.Factory.StartNew 里傳入一個(gè) async 方法:
Console.WriteLine("Hello, World!");
var task = Task.Factory.StartNew(async () =>
{
Console.WriteLine($"long running task starting. Thread id: {Thread.CurrentThread.ManagedThreadId}");
var loopCount = 1;
while (true)
{
Console.WriteLine($"\r\nStart: loop count: {loopCount}, Thread id: {Thread.CurrentThread.ManagedThreadId}");
await LongRunningJob();
Console.WriteLine($"End: loop count: {loopCount}, Thread id: {Thread.CurrentThread.ManagedThreadId} \r\n ");
loopCount++;
}
}, TaskCreationOptions.LongRunning);
static async Task LongRunningJob()
{
Console.WriteLine($"task doing. Thread id: {Thread.CurrentThread.ManagedThreadId}");
await Task.Delay(1000);
}
Console.ReadLine();
輸出:
Hello, World!
long running task starting. Thread id: 12
Start: loop count: 1, Thread id: 12
task doing. Thread id: 12
End: loop count: 1, Thread id: 11
Start: loop count: 2, Thread id: 11
task doing. Thread id: 11
End: loop count: 2, Thread id: 11
可以看到,第一次循環(huán)后,線程 id 發(fā)生了變化。很明顯 LongRunning 失效了。原因開篇已經(jīng)講了,不在贅述。
正確用法 1:同步方法
將 LongRunningJob 改為同步方法,避免異步切換線程:
var task = Task.Factory.StartNew(() =>
{
Console.WriteLine($"long running task starting. Thread id: {Thread.CurrentThread.ManagedThreadId}");
var loopCount = 1;
while (true)
{
Console.WriteLine($"\r\nStart: loop count: {loopCount}, Thread id: {Thread.CurrentThread.ManagedThreadId}");
LongRunningJob();
Console.WriteLine($"End: loop count: {loopCount}, Thread id: {Thread.CurrentThread.ManagedThreadId} \r\n ");
loopCount++;
}
}, TaskCreationOptions.LongRunning);
static void LongRunningJob()
{
Console.WriteLine($"task doing. Thread id: {Thread.CurrentThread.ManagedThreadId}");
Thread.Sleep(1000);
}
輸出:
Hello, World!
long running task starting. Thread id: 12
Start: loop count: 1, Thread id: 12
task doing. Thread id: 12
End: loop count: 1, Thread id: 12
線程 id 始終不變,說明始終運(yùn)行在專用線程上。
正確用法 2:異步方法同步等待
如果必須用異步方法,可以用 .Wait() 讓調(diào)用變?yōu)橥剑?/p>
var task = Task.Factory.StartNew(() =>
{
Console.WriteLine($"long running task starting. Thread id: {Thread.CurrentThread.ManagedThreadId}");
var loopCount = 1;
while (true)
{
Console.WriteLine($"\r\nStart: loop count: {loopCount}, Thread id: {Thread.CurrentThread.ManagedThreadId}");
LongRunningJob().Wait();
Console.WriteLine($"End: loop count: {loopCount}, Thread id: {Thread.CurrentThread.ManagedThreadId} \r\n ");
loopCount++;
}
}, TaskCreationOptions.LongRunning);
static async Task LongRunningJob()
{
Console.WriteLine($"task doing. Thread id: {Thread.CurrentThread.ManagedThreadId}");
await Task.Delay(1000);
}
輸出:
Hello, World!
long running task starting. Thread id: 12
Start: loop count: 1, Thread id: 12
task doing. Thread id: 12
End: loop count: 1, Thread id: 12
總結(jié)
TaskCreationOptions.LongRunning 適用于同步、阻塞型任務(wù)。
- 不要在
StartNew 里直接用 async 方法。
- 如果必須用異步方法,需同步等待(如
.Wait())。
希望本文能幫你正確理解和使用 LongRunning 任務(wù)!
轉(zhuǎn)自https://www.cnblogs.com/kklldog/p/19022317