TaskFactory 默认使用 TaskScheduler.Default,即基于线程池的调度器;它不执行任务,仅创建 Task 并交由指定 TaskScheduler 排队执行。

TaskFactory 默认用哪个 TaskScheduler?
TaskFactory 本身不执行任务,它只负责创建 Task 实例;真正决定「何时、在哪一线程上运行」的是 TaskScheduler。默认情况下,TaskFactory 使用 TaskScheduler.Default —— 这个调度器背后是 .NET 的线程池(ThreadPool),也就是你调用 Task.Run(...) 或无参 new TaskFactory() 时实际走的路径。
关键点:不是 TaskFactory 决定调度,而是它把创建好的 Task 交给某个 TaskScheduler 去排队和执行。你可以显式传入自定义调度器,也可以改写 TaskFactory 的 Scheduler 属性。
怎么给 TaskFactory 指定自定义 TaskScheduler?
最直接的方式是在构造 TaskFactory 时传入自定义调度器实例:
var myScheduler = new ConcurrentExclusiveSchedulerPair().Scheduler; var factory = new TaskFactory(myScheduler);
之后所有通过该 factory 创建的任务(如 factory.StartNew(...))都会被提交到 myScheduler 执行。注意:Task.Run(...) 不会受此影响,它始终使用 TaskScheduler.Default。
常见错误:以为设置了 TaskScheduler.Default = myScheduler 就能全局生效 —— 这是无效的,Default 是只读属性,不能赋值。
-
TaskFactory的Scheduler属性可读可写,但修改它只影响后续创建的任务,不影响已排队的 - 若用
Task.Factory(静态实例),它的Scheduler也是可写的,但不建议全局修改,容易引发跨模块冲突 - 自定义调度器必须继承
TaskScheduler并实现QueueTask和GetScheduledTasks(后者仅调试需要)
自定义 TaskScheduler 最小可行实现长什么样?
一个最简可用的调度器只需把任务立即在当前线程同步执行(用于测试或 UI 线程强制同步场景):
public class SyncTaskScheduler : TaskScheduler, IDisposable
{
protected override void QueueTask(Task task) => TryExecuteTask(task);
protected override IEnumerable GetScheduledTasks() => Enumerable.Empty();
protected override void ExecuteTask(Task task) => TryExecuteTask(task);
public void Dispose() { }
}
这种调度器没有队列、不启新线程,StartNew 提交的任务会立刻在调用线程上运行。适合单元测试隔离异步行为,或 WinForms/WPF 中确保回调回到 UI 线程(此时应改用 WindowsFormsSynchronizationContext 或 DispatcherSynchronizationContext 封装)。
性能提示:不要在生产环境用纯同步调度器处理耗时操作,会阻塞调用方线程;真实自定义调度器通常要管理自己的线程/队列(比如限流、优先级、单线程串行等)。
TaskScheduler.UnobservedTaskException 是谁抛的?
这个事件由 TaskScheduler 触发,不是 TaskFactory。当某个 Task 抛出异常但从未被 await、Wait() 或读取 Exception 属性时,.NET 运行时会在该 Task 被 GC 回收前,通过其关联的 TaskScheduler 触发 UnobservedTaskException。
这意味着:如果你用了自定义调度器,且没重写 UnobservedTaskException 的触发逻辑(通常不需要重写),事件仍会按默认机制上报——但上报时机取决于该调度器如何管理任务生命周期。例如,某些自定义调度器延迟释放任务引用,可能导致异常“滞留”更久才被发现。
容易忽略的一点:即使你全程用 TaskFactory 创建任务,只要没处理异常,最终兜底的仍是调度器层面的未观测异常机制,而不是工厂本身。










