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<Task> GetScheduledTasks() => Enumerable.Empty<Task>();
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创建任务,只要没处理异常,最终兜底的仍是调度器层面的未观测异常机制,而不是工厂本身。
