在上一篇文章《如何正确实现一个 BackgroundService》中有提到 LongRunning 来优化后台任务始终保持在同一个线程上。

        protected override Task ExecuteAsync(CancellationToken stoppingToken)
{
return Task.Factory.StartNew(async () =>
{
while (!stoppingToken.IsCancellationRequested)
{
// Simulate some work
Console.WriteLine("HostServiceTest_A is doing work."); LongTermTask(); await Task.Delay(1000, stoppingToken); // Delay for 1 second
} Console.WriteLine("HostServiceTest_A task done."); }, TaskCreationOptions.LongRunning);
} private void LongTermTask()
{
// Simulate some work
Console.WriteLine("LongTermTaskA is doing work.");
Thread.Sleep(30000);
}

但是被黑洞视界 大佬指出这个用法是错误的:以上用法并不能保证任务始终在同一个 Task(线程) 上执行。原因是当碰到第一个 await 之后运行时会从 ThreadPool 中调度一个新的线程来执行后面的代码,而当前线程被释放。这个时候就不符合我们使用 LongRunning 的期望了。

在 .NET 中,Task.Factory.StartNew 提供了 TaskCreationOptions.LongRunning 选项,很多开发者会用它来启动长时间运行的任务,并且想当然的认为它会永远执行在同一个线程上。但是事实上当遇到 async await 的时候并想象的那么简单。

下面我们还是通过一个错误的示例开始讲解如何正确的使用它。

错误用法

很多人会直接在 Task.Factory.StartNew 里传入一个 async 方法:

// See https://aka.ms/new-console-template for more information
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

可以看到,第一次循环后,线程 id 发生了变化。很明显 LongRunning 失效了。原因开篇已经讲了,不在赘述。

正确用法 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 始终不变,说明始终运行在专用线程上。

正确用法 2:异步方法同步等待

如果必须用异步方法,可以用 .Wait() 让调用变为同步:

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

总结

  • TaskCreationOptions.LongRunning 适用于同步、阻塞型任务。
  • 不要在 StartNew 里直接用 async 方法。
  • 如果必须用异步方法,需同步等待(如 .Wait())。

希望本文能帮你正确理解和使用 LongRunning 任务!

最后,再次感谢黑洞视界指出问题。如果对于这个问题大家希望了解更多,可以拜读大佬的这篇文章:

https://www.cnblogs.com/eventhorizon/p/17497359.html

LongRunningTask-正确用法的更多相关文章

  1. Spring MVC中Session的正确用法<转>

    Spring MVC是个非常优秀的框架,其优秀之处继承自Spring本身依赖注入(Dependency Injection)的强大的模块化和可配置性,其设计处处透露着易用性.可复用性与易集成性.优良的 ...

  2. C#中dynamic的正确用法

    C#中dynamic的正确用法  http://www.cnblogs.com/qiuweiguo/archive/2011/08/03/2125982.html dynamic是FrameWork4 ...

  3. C# string.Split对于换行符的分隔正确用法

    C# string.Split对于换行符的分隔正确用法 tmpCase "11117144-8c91-4817-9b92-99ec2f9d784a\r\n23D95A26-012C-4332 ...

  4. 【转】Spring MVC中Session的正确用法之我见

    Spring MVC是个非常优秀的框架,其优秀之处继承自Spring本身依赖注入(Dependency Injection)的强大的模块化和可配置性,其设计处处透露着易用性.可复用性与易集成性.优良的 ...

  5. 转载~kxcfzyk:Linux C语言多线程库Pthread中条件变量的的正确用法逐步详解

    Linux C语言多线程库Pthread中条件变量的的正确用法逐步详解   多线程c语言linuxsemaphore条件变量 (本文的读者定位是了解Pthread常用多线程API和Pthread互斥锁 ...

  6. C#中dynamic的正确用法 以及 typeof(DynamicSample).GetMethod("Add");

    dynamic是FrameWork4.0的新特性.dynamic的出现让C#具有了弱语言类型的特性.编译器在编译的时候不再对类型进行检查,编译期默认dynamic对象支持你想要的任何特性.比如,即使你 ...

  7. 【转】改善C#程序的建议2:C#中dynamic的正确用法 空间

    dynamic是FrameWork4.0的新特性.dynamic的出现让C#具有了弱语言类型的特性.编译器在编译的时候不再对类型进行检查,编译期默认dynamic对象支持你想要的任何特性.比如,即使你 ...

  8. C#中dynamic、ExpandoObject 的正确用法

    原文地址:http://www.cnblogs.com/qiuweiguo/archive/2011/08/03/2125982.html dynamic是FrameWork4.0的新特性.dynam ...

  9. C#中dynamic的正确用法【转】

    dynamic是FrameWork4.0的新特性.dynamic的出现让C#具有了弱语言类型的特性.编译器在编译的时候不再对类型进行检查,编译期默认dynamic对象支持你想要的任何特性.比如,即使你 ...

  10. Spring MVC中Session的正确用法之我见

    Spring MVC是个非常优秀的框架,其优秀之处继承自Spring本身依赖注入(Dependency Injection)的强大的模块化和可配置性,其设计处处透露着易用性.可复用性与易集成性.优良的 ...

随机推荐

  1. 解决Andaconda创建虚拟环境出现的“无法定位程序输入点”的问题

    解决Andaconda创建虚拟环境出现的"无法定位程序输入点"的问题 需要查看两个相同名称的文件:libssl-1_1x64.dll 第一个文件的路径:anaconda\DLLs\ ...

  2. 【实战】深入浅出 Rust 并发:RwLock 与 Mutex 在 Tauri 项目中的实践

    引言 你是否遇到过 Rust 并发场景下的资源竞争.性能瓶颈? 当多个线程同时抓取网页导致 IP 被封.多线程读写本地数据引发一致性问题时,如何优雅地实现线程安全? 本文结合开源项目 Saga Rea ...

  3. 2025第一届轩辕杯Misc详解

    Terminal Hacker 一步到位 flag{Cysay_terminal_game_hacked_successfully} 哇哇哇瓦 foremost分离 GekkoYoru 随波逐流检测, ...

  4. HDE演讲---RN应用的鸿蒙化适配经验分享

    .markdown-body { line-height: 1.75; font-weight: 400; font-size: 16px; overflow-x: hidden; color: rg ...

  5. 【拥抱鸿蒙】Flutter+Cursor轻松打造HarmonyOS应用(一)

    前言 在移动应用开发领域,Flutter以其出色的跨平台能力和高效的开发体验赢得了众多开发者的青睐,是许多移动开发者混合开发的首选. 随着HarmonyOS的崛起,许多开发者开始探索如何将Flutte ...

  6. Java 实现文件和base64字符串互转

    项目中遇到需要将图片转成base64编码的字符串的需求,但是,考虑到扩展性,写了一个可以转换任务类型文件的方法.需要引入的包: <dependency> <groupId>co ...

  7. Web前端入门第 64 问:JavaScript 几种函数定义方式有什么区别?

    函数 作为 JS 的一等公民,随处可见它的身影. 我理解的它最主要作用就是用来提取重复代码,但凡有 JS 代码需要复制粘贴的时候,那么这时候就可以考虑使用函数封装了. 当函数写在对象中的时候,这时候它 ...

  8. 化学数据分析AI实验室?ChatMoney帮你打造

    本文由 ChatMoney团队出品 AI确实是个好东西,但AI到底是有什么用?其实很多人都没搞明白.AI如果在某个行业用的好,是能带来很大经济价值的.就拿AI在化学应用来说,AI在化工领域上的应用和化 ...

  9. HyperWorks基础培训教程:批处理网格划分

    批处理网格划分流程 HyperWorks一个典型的 BatchMesher 作业由以下环节组成: (1) 设置网格类型(Mesh Type): 在 Configuration Tab 面板下,选择已有 ...

  10. ChatGPT学习之旅 (7) 参数化表达的魔力

    大家好,我是Edison. 上一篇:聊聊AI人设 通过人设模版可以有效给AI"洗脑",这体现的是结构化的表达.但想要AI实现精准控制多分支,实现千人千面的功能,就得使用参数化表达了 ...