在.NET的异步编程体系中,TaskScheduler负责控制任务的执行方式,默认的线程池任务调度器虽然能满足大部分基础场景,但在需要限制并发数、绑定特定线程、实现自定义调度策略时,就需要我们手动创建自定义TaskScheduler。

自定义TaskScheduler的核心原理
TaskScheduler是一个抽象类,位于System.Threading.Tasks命名空间下,所有自定义的任务调度器都需要继承这个类,并重写两个核心方法和一个属性:
- GetScheduledTasks:返回一个枚举器,用于调试时查看当前调度的所有任务,实际调度中可以不实现具体逻辑
- QueueTask:当任务需要被调度时,框架会调用这个方法,我们需要在这个方法里实现自己的任务入队和执行逻辑
- TryExecuteTaskInline:尝试以内联方式执行任务,也就是在当前线程直接执行任务,返回true表示执行成功
实现限制并发数的自定义TaskScheduler
下面我们实现一个最多允许同时执行N个任务的自定义调度器,首先定义调度器类:
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
namespace CustomTaskSchedulerDemo
{
// 自定义任务调度器,限制最大并发数
public class LimitedConcurrencyTaskScheduler : TaskScheduler
{
// 最大并发数
private readonly int _maxConcurrency;
// 当前正在执行的任务数
private int _runningTasksCount = 0;
// 待执行的任务队列
private readonly Queue<Task> _taskQueue = new Queue<Task>();
// 队列锁,保证线程安全
private readonly object _lockObj = new object();
public LimitedConcurrencyTaskScheduler(int maxConcurrency)
{
if (maxConcurrency < 1)
{
throw new ArgumentOutOfRangeException(nameof(maxConcurrency), "最大并发数必须大于等于1");
}
_maxConcurrency = maxConcurrency;
}
// 重写调度任务方法
protected override void QueueTask(Task task)
{
lock (_lockObj)
{
// 将任务加入队列
_taskQueue.Enqueue(task);
// 尝试执行队列中的任务
TryExecuteNextTask();
}
}
// 尝试执行下一个任务
private void TryExecuteNextTask()
{
// 如果当前执行数达到上限,或者队列为空,直接返回
if (_runningTasksCount >= _maxConcurrency || _taskQueue.Count == 0)
{
return;
}
// 从队列取出任务
Task task = _taskQueue.Dequeue();
// 增加正在执行的任务计数
Interlocked.Increment(ref _runningTasksCount);
// 在线程池线程执行任务
ThreadPool.QueueUserWorkItem(_ =>
{
try
{
// 执行任务
TryExecuteTask(task);
}
finally
{
// 任务执行完成后减少计数
Interlocked.Decrement(ref _runningTasksCount);
// 再次尝试执行队列中的下一个任务
lock (_lockObj)
{
TryExecuteNextTask();
}
}
});
}
// 重写内联执行任务方法,这里不实现内联执行,直接返回false
protected override bool TryExecuteTaskInline(Task task, bool taskWasPreviouslyQueued)
{
return false;
}
// 重写获取调度任务的方法,用于调试
protected override IEnumerable<Task> GetScheduledTasks()
{
lock (_lockObj)
{
return _taskQueue.ToArray();
}
}
}
}
自定义TaskScheduler的使用方式
创建好自定义调度器之后,我们可以通过TaskFactory或者Task的Start方法指定使用我们的调度器:
using System;
using System.Threading;
using System.Threading.Tasks;
namespace CustomTaskSchedulerDemo
{
class Program
{
static void Main(string[] args)
{
// 创建最大并发数为2的自定义调度器
LimitedConcurrencyTaskScheduler scheduler = new LimitedConcurrencyTaskScheduler(2);
// 创建基于自定义调度器的TaskFactory
TaskFactory factory = new TaskFactory(scheduler);
// 创建10个任务
for (int i = 0; i < 10; i++)
{
int taskId = i;
factory.StartNew(() =>
{
Console.WriteLine($"任务{taskId}开始执行,线程Id:{Thread.CurrentThread.ManagedThreadId}");
Thread.Sleep(1000);
Console.WriteLine($"任务{taskId}执行完成");
});
}
Console.WriteLine("所有任务已提交,等待执行完成");
Console.ReadLine();
}
}
}
运行上面的代码,你会看到最多同时只有2个任务在执行,符合我们自定义调度器的并发限制逻辑。
自定义TaskScheduler的注意事项
- 自定义调度器中的队列操作和计数修改都需要做好线程同步,避免多线程操作导致的异常
- 如果任务执行过程中抛出异常,不会影响调度器的后续运行,但是需要业务层自行处理任务异常
- TryExecuteTaskInline方法如果返回true,框架就不会再调用QueueTask方法,需要根据实际场景决定是否实现内联执行逻辑
- 不要在QueueTask方法中阻塞线程,否则会影响任务提交的效率
适用场景说明
自定义TaskScheduler除了限制并发数之外,还可以实现很多其他场景:比如指定所有任务都在UI线程执行,适合WPF、WinForm的UI更新场景;或者实现定时调度,让任务在指定的时间点执行;还可以实现任务优先级调度,让高优先级的任务先执行。只要理解了核心的调度逻辑,就可以根据业务需求扩展出各种符合场景的调度策略。
C#TaskScheduler自定义任务计划异步编程任务调度修改时间:2026-07-24 01:42:27