找回密码
 立即注册
首页 业界区 业界 LongRunningTask-正确用法

LongRunningTask-正确用法

裴竹悦 2025-8-4 20:28:31
在上一篇文章《如何正确实现一个 BackgroundService》中有提到 LongRunning 来优化后台任务始终保持在同一个线程上。
  1.         protected override Task ExecuteAsync(CancellationToken stoppingToken)
  2.         {
  3.             return Task.Factory.StartNew(async () =>
  4.             {
  5.                 while (!stoppingToken.IsCancellationRequested)
  6.                 {
  7.                     // Simulate some work
  8.                     Console.WriteLine("HostServiceTest_A is doing work.");
  9.                     LongTermTask();
  10.                     await Task.Delay(1000, stoppingToken); // Delay for 1 second
  11.                 }
  12.                 Console.WriteLine("HostServiceTest_A task done.");
  13.             }, TaskCreationOptions.LongRunning);
  14.         }
  15.         private void LongTermTask()
  16.         {
  17.             // Simulate some work
  18.             Console.WriteLine("LongTermTaskA is doing work.");
  19.             Thread.Sleep(30000);
  20.         }
复制代码
但是被黑洞视界 大佬指出这个用法是错误的:以上用法并不能保证任务始终在同一个 Task(线程) 上执行。原因是当碰到第一个 await 之后运行时会从 ThreadPool 中调度一个新的线程来执行后面的代码,而当前线程被释放。这个时候就不符合我们使用 LongRunning 的期望了。
在 .NET 中,Task.Factory.StartNew 提供了 TaskCreationOptions.LongRunning 选项,很多开发者会用它来启动长时间运行的任务,并且想当然的认为它会永远执行在同一个线程上。但是事实上当遇到 async await 的时候并想象的那么简单。
下面我们还是通过一个错误的示例开始讲解如何正确的使用它。
错误用法

很多人会直接在 Task.Factory.StartNew 里传入一个 async 方法:
  1. // See https://aka.ms/new-console-template for more information
  2. Console.WriteLine("Hello, World!");
  3. var task = Task.Factory.StartNew(async () =>
  4. {
  5.     Console.WriteLine($"long running task starting. Thread id: {Thread.CurrentThread.ManagedThreadId}");
  6.     var loopCount = 1;
  7.     while (true)
  8.     {
  9.         Console.WriteLine($"\r\nStart: loop count: {loopCount}, Thread id: {Thread.CurrentThread.ManagedThreadId}");
  10.         await LongRunningJob();
  11.         Console.WriteLine($"End: loop count: {loopCount}, Thread id: {Thread.CurrentThread.ManagedThreadId} \r\n ");
  12.         loopCount++;
  13.     }
  14. }, TaskCreationOptions.LongRunning);
  15. static async Task LongRunningJob()
  16. {
  17.     Console.WriteLine($"task doing. Thread id: {Thread.CurrentThread.ManagedThreadId}");
  18.     await Task.Delay(1000);
  19. }
  20. Console.ReadLine();
复制代码
输出:
  1. Hello, World!
  2. long running task starting. Thread id: 12
  3. Start: loop count: 1, Thread id: 12
  4. task doing. Thread id: 12
  5. End: loop count: 1, Thread id: 11
  6. Start: loop count: 2, Thread id: 11
  7. task doing. Thread id: 11
  8. End: loop count: 2, Thread id: 11
复制代码
可以看到,第一次循环后,线程 id 发生了变化。很明显 LongRunning 失效了。原因开篇已经讲了,不在赘述。
正确用法 1:同步方法

将 LongRunningJob 改为同步方法,避免异步切换线程:
  1. var task = Task.Factory.StartNew(() =>
  2. {
  3.     Console.WriteLine($"long running task starting. Thread id: {Thread.CurrentThread.ManagedThreadId}");
  4.     var loopCount = 1;
  5.     while (true)
  6.     {
  7.         Console.WriteLine($"\r\nStart: loop count: {loopCount}, Thread id: {Thread.CurrentThread.ManagedThreadId}");
  8.         LongRunningJob();
  9.         Console.WriteLine($"End: loop count: {loopCount}, Thread id: {Thread.CurrentThread.ManagedThreadId} \r\n ");
  10.         loopCount++;
  11.     }
  12. }, TaskCreationOptions.LongRunning);
  13. static void LongRunningJob()
  14. {
  15.     Console.WriteLine($"task doing. Thread id: {Thread.CurrentThread.ManagedThreadId}");
  16.     Thread.Sleep(1000);
  17. }
复制代码
输出:
  1. Hello, World!
  2. long running task starting. Thread id: 12
  3. Start: loop count: 1, Thread id: 12
  4. task doing. Thread id: 12
  5. End: loop count: 1, Thread id: 12
复制代码
线程 id 始终不变,说明始终运行在专用线程上。
正确用法 2:异步方法同步等待

如果必须用异步方法,可以用 .Wait() 让调用变为同步:
  1. var task = Task.Factory.StartNew(() =>
  2. {
  3.     Console.WriteLine($"long running task starting. Thread id: {Thread.CurrentThread.ManagedThreadId}");
  4.     var loopCount = 1;
  5.     while (true)
  6.     {
  7.         Console.WriteLine($"\r\nStart: loop count: {loopCount}, Thread id: {Thread.CurrentThread.ManagedThreadId}");
  8.         LongRunningJob().Wait();
  9.         Console.WriteLine($"End: loop count: {loopCount}, Thread id: {Thread.CurrentThread.ManagedThreadId} \r\n ");
  10.         loopCount++;
  11.     }
  12. }, TaskCreationOptions.LongRunning);
  13. static async Task LongRunningJob()
  14. {
  15.     Console.WriteLine($"task doing. Thread id: {Thread.CurrentThread.ManagedThreadId}");
  16.     await Task.Delay(1000);
  17. }
复制代码
输出:
  1. Hello, World!
  2. long running task starting. Thread id: 12
  3. Start: loop count: 1, Thread id: 12
  4. task doing. Thread id: 12
  5. End: loop count: 1, Thread id: 12
复制代码
总结


  • TaskCreationOptions.LongRunning 适用于同步、阻塞型任务。
  • 不要在 StartNew 里直接用 async 方法。
  • 如果必须用异步方法,需同步等待(如 .Wait())。
希望本文能帮你正确理解和使用 LongRunning 任务!
最后,再次感谢黑洞视界指出问题。如果对于这个问题大家希望了解更多,可以拜读大佬的这篇文章:
https://www.cnblogs.com/eventhorizon/p/17497359.html

来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
您需要登录后才可以回帖 登录 | 立即注册