这样在 C# 使用 LongRunnigTask 是错的

简介: Task.Factory.StartNew 有一个重载,是支持 TaskCreationOptions.LongRunning 参数来指定 Task 的特征的。但是可能在没有注意的情况下,你就使用了错误的用法。那么本文我们来简单阐述一下这个参数的作用,和使用的注意要点。

Task.Factory.StartNew 有一个重载,是支持 TaskCreationOptions.LongRunning 参数来指定 Task 的特征的。但是可能在没有注意的情况下,你就使用了错误的用法。那么本文我们来简单阐述一下这个参数的作用,和使用的注意要点。

这样其实是错误的

有的时候,你可能会这么写:

Task.Factory.StartNew(async () =>
{
    while (true)
    {
        // do something
        await Task.Delay(1000);
    }
}, TaskCreationOptions.LongRunning);

但其实,这是个错误的写法。

为什么需要 LongRunning

我们通常两种情况下会想到使用 TaskCreationOptions.LongRunning 参数:

  1. 你的任务需要长时间运行,比如一个循环,或者一个死循环。用来从队列中取数据,然后处理数据,或者是一些定时任务。
  2. 你的任务需要占用大量的 CPU 资源,是一个很大的循环,比如要遍历一个很大的数组,并做一些处理。

那么这个时候,我们就需要使用 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 参数,单独开启的线程就可以一直运行下去。

实际上还有很多考量

要考量 TaskScheduler 的实现

本文采用的是 aspnetcore 的实现,但是在其他的实现中,可能会有不同的实现。你也完全有可能实现一个 await 之后,不回到 ThreadPool 的实现。

LongRunning 也不是就不能用异步

正如开篇提到的第二种场景,如果你的业务是在第一个 await 之前有大量的同步代码,那么此时单独开启一个线程,也是有意义的。

我就是一个死循环,里面也是异步的怎么办

那么你可以考虑让这个 LongRuning 的 Task,不要 await,而是通过 Wait() 来等待。这样就可以避免 LongRunning 的 Task 直接结束。

总结

本文我们简单阐述了 TaskCreationOptions.LongRunning 参数的作用,和使用的注意要点。

参考

感谢您的阅读,如果您觉得本文有用,快点击下方点赞按钮👍,让更多的人看到本文。

欢迎关注作者的微信公众号“newbe技术专栏”,获取更多技术内容。


  1. https://www.cnblogs.com/eventhorizon/p/15912383.html
  2. https://threads.whuanle.cn/3.task/
  3. https://learn.microsoft.com/en-us/dotnet/api/system.threading.tasks.taskcreationoptions?view=net-7.0&WT.mc_id=DX-MVP-5003606
目录
相关文章
|
9月前
|
网络协议 网络安全 API
C# 与三菱FX5U PLC通讯交互指南
C# 与三菱FX5U PLC通讯交互指南
3134 121
|
编译器 C# Windows
Inno Setup制作安装包教程
Inno Setup制作安装包教程
4911 0
|
3月前
|
人工智能 缓存 监控
打造 AI 冒险团:HagiCode 多 Agent 协作配置实战
本文介绍HagiCode项目首创的多AI Agent协作配置方案:通过统一接口(IAIProvider)、工厂模式、ACP通信协议及任务分流机制,让Claude Code、Codex、CodeBuddy等异构AI助手各司其职、协同作战,构建高效稳定的“AI冒险团”,显著提升复杂项目开发效率与输出质量。(239字)
750 2
|
监控 物联网 API
【.NET+MQTT】.NET6 环境下实现MQTT通信,以及服务端、客户端的双边消息订阅与发布的代码演示
MQTT广泛应用于工业物联网、智能家居、各类智能制造或各类自动化场景等。MQTT是一个基于客户端-服务器的消息发布/订阅传输协议,在很多受限的环境下,比如说机器与机器通信、机器与物联网通信等。好了,科普的废话不多说,下面直接通过.NET环境来实现一套MQTT通信demo,实现服务端与客户端的双边消息发布与订阅的功能和演示。
2481 0
【.NET+MQTT】.NET6 环境下实现MQTT通信,以及服务端、客户端的双边消息订阅与发布的代码演示
Axure高保真原型设计:移动端多选图片上传
本文介绍了如何在Axure中利用中继器实现移动端应用的多选图片上传功能,适用于如微信、微博等社交平台。文章详细描述了主页面、相册页面和大图页面的制作步骤,并展示了如何通过中继器和交互设置实现图片的选择、上传及删除等功能。此教程有助于提升用户体验和应用功能性。
527 9
|
存储 SQL 关系型数据库
TiDB,金融级开源NewSQL
本文介绍了国内自研且开源的NewSQL数据库TiDB,它具备分布式强一致性事务、水平扩展、高可用等特性,几乎满足了对数据库的所有需求,堪称数据库中的“六边形战士”。文章回顾了数据库技术的发展历程,从人工管理阶段到文件系统阶段,再到现代的数据库系统阶段。最后,文章总结了TiDB的前景和挑战,指出虽然部署成本较高,但在特定行业和业务领域中具有巨大潜力。
1206 11
TiDB,金融级开源NewSQL
|
安全 网络协议 网络安全
【网络连接】ping不通的常见原因+解决方案,如何在只能访问网关时诊断,并修复IP不通的问题
【网络连接】ping不通的常见原因+解决方案,如何在只能访问网关时诊断,并修复IP不通的问题
35249 0
inno setup打包软件学习
如何使用Inno Setup打包软件,包括打包结果的展示、示例打包脚本的提供、常见错误的解决方法,以及参考资料的链接。文中详细解释了解决“另一个程序正在使用此文件”和“桌面图标无法修改”等问题的方法,以及如何正确设置打包脚本中的文件路径和图标。
1272 1
inno setup打包软件学习
|
存储 JSON NoSQL
MongoDB 教程
10月更文挑战第9天
285 0

热门文章

最新文章