You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为何单线程异步控制台应用中ThreadPool大小会增长?

控制台异步代码中线程池线程数增长的原因分析

我参照Stephen Toub的一篇旧博客,尝试让控制台应用仅通过主线程运行异步函数。虽然成功实现了所有异步回调都在主线程执行,但运行后发现ThreadPool的线程数仍出现增长,希望分析背后的原因。

运行环境与结果

我在.NET 6、MacOS环境下测试,Debug和Release模式的输出如下:

Debug模式输出:

$ dotnet ./bin/Debug/net6.0/publish/con.dll
ThreadCount: 0
ThreadCount went from 0 to 2 at i=0
ThreadCount went from 2 to 3 at i=91
[1, 1000]
Completing on 1
ThreadCount: 3

Release模式输出:

$ dotnet ./bin/Release/net6.0/publish/con.dll
ThreadCount: 0
ThreadCount went from 0 to 2 at i=0
ThreadCount went from 2 to 3 at i=401
[1, 1000]
Completing on 1
ThreadCount: 3

代码改动说明

注意:我在Program.cs中强制将最后的Complete()调用调度到TaskScheduler.FromCurrentSynchronizationContext(),这与原博客使用TaskScheduler.Default的做法不同。使用默认调度器时,Complete()会在线程池中触发,符合预期。

核心疑问

我想明确两个问题:

  • 是什么导致线程池分配新线程?
  • Task.Delay总会引发这种线程池线程增长的情况吗?

完整测试代码

using System.Collections.Concurrent;

internal class Program
{
    private sealed class SingleThreadSynchronizationContext : SynchronizationContext
    {
        private readonly BlockingCollection<KeyValuePair<SendOrPostCallback, object?>> m_queue = new();

        public override void Post(SendOrPostCallback d, object? state)
        {
            m_queue.Add(new KeyValuePair<SendOrPostCallback, object?>(d, state));
        }

        public void RunOnCurrentThread()
        {
            KeyValuePair<SendOrPostCallback, object?> workItem;
            while (m_queue.TryTake(out workItem, Timeout.Infinite))
            {
                workItem.Key(workItem.Value);
            }
        }

        public void Complete()
        {
            Console.WriteLine($"Completing on {Thread.CurrentThread.ManagedThreadId}");
            m_queue.CompleteAdding();
        }

        public static void Run(Func<Task> func)
        {
            var syncCtx = new SingleThreadSynchronizationContext();
            var prevCtx = SynchronizationContext.Current;
            try
            {
                SynchronizationContext.SetSynchronizationContext(syncCtx);
                var t = func();

                t.ContinueWith(
                    delegate { syncCtx.Complete(); },
                    TaskScheduler.FromCurrentSynchronizationContext());
                syncCtx.RunOnCurrentThread();
                t.GetAwaiter().GetResult();
            }
            finally
            {
                SynchronizationContext.SetSynchronizationContext(prevCtx);
            }
        }
    }

    private static async Task MainAsync(string[] args)
    {
        var d = new Dictionary<int, int>();

        for (int i = 0; i < 1000; i++)
        {
            int id = Thread.CurrentThread.ManagedThreadId;
            int count;

            d[id] = d.TryGetValue(id, out count) ? count + 1 : 1;

            var threadCount = ThreadPool.ThreadCount;
            await Task.Delay(1);
            if (ThreadPool.ThreadCount != threadCount)
            {
                Console.WriteLine($"ThreadCount went from {threadCount} to {ThreadPool.ThreadCount} at i={i}");
            }
        }

        foreach (var pair in d) Console.WriteLine(pair);
    }

    public static void Main(string[] args)
    {
        Console.WriteLine($"ThreadCount: {ThreadPool.ThreadCount}");
        SingleThreadSynchronizationContext.Run(() => MainAsync(args));
        Console.WriteLine($"ThreadCount: {ThreadPool.ThreadCount}");
    }
}

内容的提问来源于stack exchange,提问作者Steven Luu

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.16 04:45:36