绝大多数场景应使用 Task.Run;仅当需控制调度器、指定 LongRunning 选项或传入状态参数时才用 Task.Factory.StartNew。Task.Run 默认更安全,因强制使用线程池且不捕获同步上下文,避免 UI 线程异常;自动解包 async lambda,静默处理未观察异常;而 StartNew 易误捕获上下文、需手动 Unwrap、可能引发进程崩溃。StartNew 不可替代的场景包括:需 LongRunning 避免线程池饥饿、需传状态参数、需在旧版 .NET 4.0 运行。

绝大多数场景下,该用 Task.Run;只有明确需要控制调度器、任务选项(如 LongRunning)或传入状态参数时,才考虑 Task.Factory.StartNew。
Task.Run 为什么默认更安全
它强制使用 TaskScheduler.Default(即线程池),不继承当前上下文——这点在 ASP.NET Core 或 WinForms/WPF UI 线程中尤为关键。如果在 UI 线程里误用 Task.Factory.StartNew 而没指定调度器,它会默认捕获 UI 同步上下文,导致后续 .ContinueWith 回到 UI 线程,可能引发跨线程访问异常或意外阻塞。
常见错误现象:Task.Factory.StartNew(() => { /* 耗时操作 */ }).ContinueWith(t => label.Text = "done"); 在 WinForms 中直接抛 InvalidOperationException: Cross-thread operation not valid,而 Task.Run 不会。
-
Task.Run自动解包Func<task></task>:传入async () => await Task.Delay(100),返回的是Task,可直接await -
Task.Factory.StartNew对同样签名返回Task<task></task>,必须显式调用.Unwrap()或await两次,否则 await 表面完成但内部异步逻辑未执行完 - 未观察异常行为更友好:
Task.Run在 .NET 4.5+ 中会静默捕获未等待的异常;Task.Factory.StartNew若未处理,可能触发TaskScheduler.UnobservedTaskException甚至进程崩溃(尤其在旧版 .NET)
StartNew 的不可替代场景
当你要启动一个预计持续数秒以上、且不希望占用线程池资源的任务时,Task.Factory.StartNew 是唯一选择——因为 Task.Run 始终走线程池,无法绕过。
典型例子:长周期轮询、文件监听、自定义协议心跳维持等。
- 必须显式传
TaskCreationOptions.LongRunning,提示运行时分配独立线程,避免线程池饥饿 - 需手动指定
TaskScheduler.Default,否则在 UI 或 ASP.NET 上下文中可能误用当前调度器 - 支持状态参数传递:
Task.Factory.StartNew(obj => Process((string)obj), "config.json"),Task.Run不提供此重载 - 若需绑定取消令牌并注册回调(如
cancellationToken.Register(...)),StartNew的参数结构更直接
async lambda 写法的坑与修复
这是面试高频踩坑点:Task.Factory.StartNew(async () => ...) 看似和 Task.Run(async () => ...) 一样,实则语义完全不同。
根本原因:编译器把 async () => 编译为 Func<task></task>,而 StartNew 的泛型重载会把它当作同步委托包装进 Task<task></task>,而非真正“启动异步操作”。
- 错误写法:
var t = Task.Factory.StartNew(async () => await Task.Delay(1000)); await t;→t是Task<task></task>,await t只等外层任务完成(毫秒级),内部Delay实际未执行 - 正确写法一(推荐):
await Task.Run(async () => await Task.Delay(1000)); - 正确写法二(StartNew):
await Task.Factory.StartNew(() => Task.Delay(1000)).Unwrap();或await (await Task.Factory.StartNew(() => Task.Delay(1000)));
性能与兼容性差异
Task.Run 是 Task.Factory.StartNew 的封装,调用栈多一层,但开销可忽略;真正影响性能的是调度器选择和任务选项,不是 API 名称本身。
兼容性方面:Task.Run 要求 .NET Framework 4.5+ / .NET Standard 1.0+;Task.Factory.StartNew 从 .NET 4.0 就存在。若维护老旧项目(如 .NET 4.0),只能用后者。
容易被忽略的一点:在高并发短任务场景下,滥用 LongRunning 反而降低吞吐——每个 LongRunning 任务都独占一个线程,线程创建销毁成本远高于线程池复用。只有真正“长”且“稳”的任务才适合。


















