C#。是否有用于自定义线程池的 .NET class

C# .Is there a .NET class for custom thread pools

一段时间以来,我一直在努力寻找一个简单的自定义线程池,但找不到,所以写了一个快速穷人的线程池。

问题

  1. 是否有任何 .NET class 可以做到这一点? (在多次尝试找到它后找不到它,只有博客文章上的自定义实现比这复杂得多!)。

  2. 当任务进入时,最好通过轮询或在线程池上启动操作来使用线程? <1k 任务/天。这将是 运行 作为 Kestrel

  3. 上的 AspNetCore 站点

代码

public class WorkerQueue : IWorkerQueue
{
    private readonly Queue<WorkItem> _items = new Queue<WorkItem>();

    private int _max = 2; // Would be configurable
    private int _running;
    private Stopwatch _stopwatch;

    public WorkerQueue()
    {
        _stopwatch = new Stopwatch();
        _stopwatch.Start();
    }

    public void Add(WorkItem workItem)
    {
        lock (_items)
        {
            if (_running >= _max)
            {
                Log($"Queuing Item {workItem.Name} - _running >= _max");
                _items.Enqueue(workItem);
                return;
            }

            _running++;

            Log($"Running Item {workItem.Name} - _running = {_running}");
            var task = Task.Run(workItem.Action);

            task.ContinueWith(t => OnActionCompleted(workItem.Name));
        }
    }

    private void Log(string msg)
    {
        Console.WriteLine($"Thread {Thread.CurrentThread.ManagedThreadId} @ {_stopwatch.ElapsedMilliseconds}ms : {msg}");
    }

    private void OnActionCompleted(string obj)
    {
        Log($"OnActionCompleted {obj}");
        WorkItem item = null;

        lock (_items)
        {
            if (_items.Count > 0)
                item = _items.Dequeue();
            else
                _running--;
        }

        if (item != null)
        {
            // Potential Stack Overflow if big queue builds up?
            // Probably should be a while loop rather than recursion?
            Log($"Running Next Item {item.Name}");
            item.Action();
            OnActionCompleted(item.Name);
        }
        else
        {
            Log($"Sleeping. _running = {_running}");
        }
    }
}

还有一个测试:

    [Fact]
    public void Test()
    {
        var sb = new StringBuilder();
        Console.SetOut(new StringWriter(sb));

        var resetEvent = new ManualResetEventSlim();

        AddItem("A", 100);
        AddItem("B", 250);
        AddItem("C", 100);
        AddItem("D", 100);
        AddItem("E", 100);
        AddItem("G", 100, () =>
        {
            Thread.Sleep(250);
            resetEvent.Set();
        });

        resetEvent.Wait(2500);

        Assert.True(resetEvent.IsSet);

        _output.WriteLine("");
        _output.WriteLine("------------------ Test Finished ------------------");
        _output.WriteLine("------------------  Console Out  ------------------");
        _output.WriteLine("");
        _output.WriteLine(sb.ToString());
    }

并获得正确(或足够正确)的输出

Thread 14 @ 8ms : Running Item A - _running = 1
Thread 14 @ 8ms : Running Item B - _running = 2
Thread 14 @ 8ms : Queuing Item C - _running >= _max
Thread 14 @ 8ms : Queuing Item D - _running >= _max
Thread 14 @ 8ms : Queuing Item E - _running >= _max
Thread 14 @ 8ms : Queuing Item G - _running >= _max
Thread 21 @ 110ms : OnActionCompleted A
Thread 21 @ 110ms : Running Next Item C
Thread 21 @ 211ms : OnActionCompleted C
Thread 21 @ 211ms : Running Next Item D
Thread 20 @ 260ms : OnActionCompleted B
Thread 20 @ 260ms : Running Next Item E
Thread 21 @ 311ms : OnActionCompleted D
Thread 21 @ 311ms : Running Next Item G
Thread 20 @ 360ms : OnActionCompleted E
Thread 20 @ 360ms : Sleeping. _running = 1
Thread 21 @ 662ms : OnActionCompleted G
Thread 21 @ 662ms : Sleeping. _running = 0

你可以看看parallel extensions extras project,它有几个任务调度器。具体来说,WorkStealingTaskScheduler.

我记得我测试过它,托管世界中的任何东西都无法与 .NET 自己的线程池相比,至少没有不安全的代码。 .NET 的备用线程池在幕后进行了大量优化,我记得很清楚的一个是在等待之前处理一个工作项后积极地为工作项旋转。

从好的方面来说,您可以非常接近它,但是模仿 ThreadPool 所做的是一项壮举。也就是说,如果你想这样做。我希望 使用默认线程池的原因之一是其关于在 min 之后缓慢创建线程的策略。线程,但只是增加最小值。线程通常就足够了。

问题是,除非您使用 TPL ,否则您可以提供自己的任务调度程序,通常没有其他任何东西(尤其是遗留代码)会选择您的自定义线程池,因为没有库存线程池接口。不幸的是,ThreadPool 是静态 class 的事实进一步阻碍了其他线程池的使用。