Task.Run与TaskFactory.StartNew的区别是什么?
默认行为与参数差异
Task.Run是TaskFactory.StartNew的简化封装,默认使用TaskScheduler.Default(线程池调度器),并且自动启用TaskCreationOptions.DenyChildAttach——这个选项能避免子任务意外附加到当前任务,防止父任务不必要地等待子任务完成。
而TaskFactory.StartNew如果不手动指定参数,默认采用当前线程的调度器(比如UI线程的同步上下文调度器),也没有DenyChildAttach的默认配置,子任务可能会绑定到父任务上,导致执行逻辑不符合预期。泛型任务的创建便捷性
Task.Run支持直接返回泛型结果,无需显式声明泛型类型:// 直接返回Task<int> Task<int> numTask = Task.Run(() => 42);TaskFactory.StartNew必须显式指定泛型参数才能返回对应类型的任务,否则会返回
Task<object>,后续需要拆箱:// 必须指定<int>,否则返回Task<object> Task<int> numTask = new TaskFactory().StartNew<int>(() => 42);异步委托的处理逻辑
当传入带async/await的异步委托时,Task.Run会自动识别并返回正确的任务类型,不会嵌套Task:// 返回Task,而非Task<Task> Task asyncTask = Task.Run(async () => await SomeAsyncMethod());但TaskFactory.StartNew会把异步委托当作普通的返回Task的同步委托处理,默认返回
Task<Task>,必须调用Unwrap()才能拿到真正的异步任务:// 必须Unwrap()才能得到实际的异步任务 Task asyncTask = new TaskFactory().StartNew(async () => await SomeAsyncMethod()).Unwrap();适用场景区分
Task.Run语法简洁,适合大多数简单后台线程池任务,无需复杂配置就能快速提交任务。
TaskFactory.StartNew则提供了极高的灵活性:可以自定义调度器、设置TaskCreationOptions.LongRunning标记(适合长耗时任务,避免占用线程池资源)、传入取消令牌等,适合需要精细控制任务执行逻辑的复杂场景。
内容的提问来源于stack exchange,提问作者Simple

