c# 如何用c#实现一个简单的线程池

来源:这里教程网 时间:2026-02-21 17:36:38 作者:

为什么不用
ThreadPool
而要自己实现?

因为 .NET 内置的

ThreadPool
是全局、静态、不可配置线程数上限的(
SetMaxThreads
影响整个进程),且无法感知任务排队/执行状态。如果你需要:固定线程数、独立生命周期、自定义拒绝策略、或调试时清晰看到每个线程在干什么,就得手写一个轻量级线程池。

核心组件怎么组织?

一个最小可用线程池只需三部分:

Task
队列、工作线程集合、控制开关。不推荐用
Thread
手动管理(易泄漏、难回收),改用
BackgroundService
+
BlockingCollection<action></action>
更安全。

BlockingCollection<action></action>
自带线程安全与阻塞取值,省去手动加锁
每个工作线程用
while (!stoppingToken.IsCancellationRequested)
循环取任务
启动时用
Task.Run
启动固定数量的后台任务,不是
new Thread(...).Start()

如何避免常见死锁和资源泄漏?

关键在「关闭」逻辑。不能只停线程,必须让所有待处理任务有机会完成,同时阻止新任务入队。常见错误是调用

CompleteAdding()
后没等
IsCompleted
就 Dispose 队列。

对外暴露
Submit(Action)
方法,内部先检查
_queue.IsAddingCompleted
,已关闭则抛
InvalidOperationException
StopAsync()
中先调用
_queue.CompleteAdding()
,再
await Task.WhenAll(_workers)
每个工作线程循环体里用
_queue.TryTake(out var work, timeout: -1, cancellationToken)
,支持取消信号穿透
public class SimpleThreadPool : IHostedService
{
    private readonly BlockingCollection<Action> _queue = new();
    private readonly List<Task> _workers = new();
    private readonly int _workerCount;
    public SimpleThreadPool(int workerCount = 4) => _workerCount = Math.Max(1, workerCount);
    public Task StartAsync(CancellationToken cancellationToken)
    {
        for (int i = 0; i < _workerCount; i++)
        {
            _workers.Add(Task.Run(() => WorkerLoop(cancellationToken), cancellationToken));
        }
        return Task.CompletedTask;
    }
    private void WorkerLoop(CancellationToken ct)
    {
        while (!ct.IsCancellationRequested)
        {
            if (_queue.TryTake(out var work, -1, ct))
                work();
        }
    }
    public void Submit(Action action)
    {
        if (_queue.IsAddingCompleted)
            throw new InvalidOperationException("Pool is stopping or stopped.");
        _queue.Add(action);
    }
    public async Task StopAsync(CancellationToken cancellationToken)
    {
        _queue.CompleteAdding();
        await Task.WhenAll(_workers).WaitAsync(cancellationToken);
        _queue.Dispose();
    }
}

什么时候该换回
ThreadPool
TaskScheduler

当你开始给线程加优先级、绑定 CPU 核心、做 IO 与计算任务分离调度,或者需要和

async/await
深度集成时,手写池就变成负优化。此时应转向
ConcurrentExclusiveSchedulerPair
、自定义
TaskScheduler
,或直接用
ParallelOptions.TaskScheduler
控制并发粒度。

真正容易被忽略的是:线程池不是万能加速器。如果任务本身是同步阻塞 IO(如

File.ReadAllText
),开 100 个线程只会压垮磁盘;换成
await File.ReadAllTextAsync
再配合
Task.Run
包裹 CPU 密集操作,才合理。

相关推荐