Task.Run和Task.Factory.StartNew都是用来创建并启动任务的方法,但它们的内部实现和使用场景有所不同。两者的主要区别:默认调度器、配置选项、异常处理、适用场景。建议用户大多数情况下使用Task.Run,除非需要StartNew的高级配置,比如特定的调度器或任务选项。
一、调度器差异
Task.Run实际上是Task.Factory.StartNew的一个封装,但做了更合理的默认设置。
在ASP.NET Core等环境中,使用正确的任务调度器很重要,避免在UI线程中阻塞,这时候Task.Run的默认调度器可能更安全。而StartNew如果不指定调度器,可能会捕获当前上下文,导致意外的行为。
Task.Run更适合用在async方法中,因为它会自动解包返回的任务,而StartNew可能需要显式处理。例如,当返回Task时,Task.Run会正确处理,而StartNew可能需要使用Unwrap。
Task.Run
- 默认使用
TaskScheduler.Default
(即线程池调度器)。 - 自动解包嵌套任务 :如果任务返回
Task<Task>
,Task.Run
会自动解包为单一任务。 - 适用于大多数场景 ,尤其是异步方法(
async
/await
)中。
Task.Factory.StartNew
- 默认使用当前任务的调度器 (通过
TaskScheduler.Current
获取),如果未明确指定,可能导致意外行为(例如在 UI 上下文中捕获调度器)。 - 不会自动解包嵌套任务 :返回
Task<Task>
,需要手动调用.Unwrap()
。 - 更灵活,但需要显式配置参数。
cs
// 在 UI 线程中调用
Task.Factory.StartNew(() =>
{
// 默认使用当前调度器(可能是 UI 调度器!)
}).ContinueWith(t =>
{
// 可能回到 UI 线程
});
Task.Run(() =>
{
// 强制使用线程池调度器
}).ContinueWith(t =>
{
// 不会回到 UI 线程
});
二、任务选项
T两者都会将任务排到线程池,但StartNew如果传递了TaskCreationOptions.LongRunning,可能会创建新线程而不是使用线程池。而Task.Run的参数更简洁,不需要显式指定任务创建选项,所以它总是使用线程池。
cs
Task.Factory.StartNew(
() => { /* ... */ },
CancellationToken.None,
TaskCreationOptions.LongRunning, // 明确标记为长时间运行
TaskScheduler.Default
);
LongRunning
选项:提示框架该任务可能需要独立线程(避免线程池饥饿)
三、异常处理
Task.Run
- 如果任务被取消(传入
CancellationToken
),会返回一个已取消的任务(Task.Status == Canceled
),不会抛出异常。 - 异步方法中通过
await
自动传播异常。
Task.Factory.StartNew
- 如果任务被取消,默认会抛出
TaskCanceledException
(需通过try/catch
捕获)。 - 需要手动处理取消逻辑。
cs
var cts = new CancellationTokenSource();
// Task.Run 不会抛出异常
var task1 = Task.Run(async () =>
{
await Task.Delay(1000, cts.Token);
}, cts.Token);
// Task.Factory.StartNew 可能抛出异常
var task2 = Task.Factory.StartNew(() =>
{
Thread.Sleep(1000);
cts.Token.ThrowIfCancellationRequested();
}, cts.Token);
四、适用场景
优先使用 Task.Run
的场景
- 异步方法(
async
/await
)中:自动处理调度和异常。 - 需要线程池任务 :默认使用
TaskScheduler.Default
。 - 简化代码:无需配置复杂参数。
使用 Task.Factory.StartNew
的场景
- 需要显式配置任务选项 (如
LongRunning
、AttachedToParent
)。 - 需要指定自定义调度器 (如
TaskScheduler.FromCurrentSynchronizationContext()
用于 UI 更新)。 - 需要兼容旧代码(.NET 4.0 之前的项目中)。
五、性能与最佳实践
-
Task.Run
更高效:内部优化了参数传递和异常处理。 - 避免滥用
Task.Factory.StartNew
:除非需要高级配置,否则优先用Task.Run
。 - 在 UI 应用中 :使用
Task.Run
避免意外捕获 UI 调度器