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

线程数对并行同步/异步方法执行时间的影响及相关疑问

线程数对同步/异步并行执行性能的影响测试疑问解答

测试背景

我编写了一个测试,验证可用线程数对并行化同步、异步函数执行时间的影响,过程中产生了诸多疑问,恳请解答。

测试结果

默认线程设置下的测试结果

(展示内容:不同迭代次数(100/1000/5000/15000)下,四个测试方法的执行耗时对比)

自定义线程池设置下的测试结果

配置:<ThreadPoolMinThreads>1000</ThreadPoolMinThreads> 和 <ThreadPoolMaxThreads>1000</ThreadPoolMaxThreads>
(展示内容:该线程配置下,不同迭代次数下四个测试方法的执行耗时对比)

疑问解答

1. 为何将线程数固定为1000后,异步操作的性能出现下降?

异步操作(如Task.Delay)依赖IO完成端口(IOCP)实现无阻塞等待,不需要持续占用线程池线程。强制将线程池线程数拉到1000时,会产生额外的线程调度开销:每个线程需要占用内存(线程栈)、CPU时间片切换成本。异步操作本身不需要这么多线程,过多线程会让CPU在调度上消耗更多资源,反而拖慢整体执行速度。

2. GetSyncExecutionTimeThread与GetSyncExecutionTimeDelay的结果为何存在差异?GetSyncExecutionTimeDelay异步执行却未使用await,这是为何?

两个方法的核心差异在于底层等待逻辑:

  • GetSyncExecutionTimeThread调用的Delay方法用Thread.Sleep,会阻塞当前线程直到超时结束。当任务数量超过线程池默认容量时,线程池需要缓慢扩容(默认每秒新增2个线程),导致大量任务排队等待线程,耗时更长。
  • GetSyncExecutionTimeDelay调用的DelayAsync方法用Task.Delay,是异步无阻塞逻辑:遇到await时会释放当前线程回线程池,线程可继续处理其他任务,并行效率更高,耗时更短。

关于“未使用await”的误解:DelayAsync内部已经用await Task.Delay实现异步等待,GetSyncExecutionTimeDelay外层通过await Task.WhenAll等待所有任务完成,整个流程是完整的异步等待逻辑,只是Task.Run的委托直接返回了DelayAsync的Task,不需要额外加async/await包装。

3. 为何GetSyncExecutionTimeDelay比GetAsyncExecutionTimeInTasks更快?GetSyncExecutionTimeDelay异步执行却未使用await,这是为何?

两者的差异在于异步委托的包装开销:

  • GetSyncExecutionTimeDelay中,Task.Run(() => DelayAsync(...))直接调用异步方法DelayAsync,Task.Run会自动处理内部Task的等待,不需要额外生成异步状态机,开销极小。
  • GetAsyncExecutionTimeInTasks中,Task.Run(async () => await ...)多了一层async委托的包装,会生成额外的异步状态机,带来微小但可感知的性能开销。

重复的“未使用await”疑问解答同问题2:DelayAsync内部已实现异步等待,外层Task.WhenAll完成了整体等待,不存在未等待的情况。

4. GetAsyncExecutionTime与GetAsyncExecutionTimeInTasks有何区别?是否GetAsyncExecutionTime由Task自主决定处理线程数量?

核心区别在于是否强制使用线程池线程:

  • GetAsyncExecutionTime:直接调用DelayAsync,生成的是异步IO任务,不需要主动占用线程池线程(仅在await恢复时可能用到线程池,因使用了ConfigureAwait(false))。任务调度完全由.NET异步运行时管理,仅在必要时使用线程,线程数量使用效率极高。
  • GetAsyncExecutionTimeInTasks:用Task.Run包装DelayAsync调用,会强制将异步任务放到线程池线程上执行。虽然DelayAsync本身无阻塞,但Task.Run会先占用线程池线程直到await释放,带来额外的线程调度开销,线程池的线程数量会直接影响该方法的执行效率。

是的,GetAsyncExecutionTime的线程使用由.NET异步运行时自主决定,无需手动通过Task.Run分配线程,更适合IO密集型任务。

5. 为何单独运行各方法时结果最优,而连续调用时后续方法的结果不如单独运行时理想?

主要原因有两点:

  1. 线程池状态残留:前面的方法(尤其是调用Thread.Sleep的GetSyncExecutionTimeThread)会导致线程池线程数量被扩容。后续方法运行时,线程池内可能存在大量闲置线程,带来额外的内存开销和调度成本;同时线程池的饥饿检测、扩容逻辑可能仍处于调整状态,影响任务执行效率。
  2. 系统资源占用:前面的方法运行时已消耗部分系统资源(CPU、内存),比如大量并行任务会导致CPU缓存命中率下降、内存占用升高,后续方法无法获得最优的资源状态,耗时增加。

测试代码

using System.Diagnostics;

var upperLimitsOfRanges = new List<int>() { 100, 1000, 5000, 15000 };

var millisecondsTimeout = 100;

foreach (var upperLimitOfRange in upperLimitsOfRanges)
{
    var collection = Enumerable.Range(1, upperLimitOfRange);

    var syncExecutionTimeThread = await GetSyncExecutionTimeThread(collection, millisecondsTimeout);

    var syncExecutionTimeDelay = await GetSyncExecutionTimeDelay(collection, millisecondsTimeout);

    var asyncExecutionTime = await GetAsyncExecutionTime(collection, millisecondsTimeout);

    var asyncExecutionTimeInTasks = await GetAsyncExecutionTimeInTasks(collection, millisecondsTimeout);

    Console.WriteLine($"Iterations: {upperLimitOfRange}; " +
        $"Sync thread: {syncExecutionTimeThread} ms; " +
        $"Sync delay: {syncExecutionTimeDelay} ms; " +
        $"Async: {asyncExecutionTime} ms; " +
        $"Async in tasks: {asyncExecutionTimeInTasks} ms");
}

Console.Read();

async Task<long> GetSyncExecutionTimeThread(IEnumerable<int> collection, int millisecondsTimeout)
{
    var executionTimeWithoutAsync = Stopwatch.StartNew();

    await Task.WhenAll(collection.Select(_ => Task.Run(() => Delay(millisecondsTimeout))));

    executionTimeWithoutAsync.Stop();

    return executionTimeWithoutAsync.ElapsedMilliseconds;
}

async Task<long> GetSyncExecutionTimeDelay(IEnumerable<int> collection, int millisecondsTimeout)
{
    var executionTimeWithoutAsync = Stopwatch.StartNew();

    await Task.WhenAll(collection.Select(_ => Task.Run(() => DelayAsync(millisecondsTimeout))));

    executionTimeWithoutAsync.Stop();

    return executionTimeWithoutAsync.ElapsedMilliseconds;
}

async Task<long> GetAsyncExecutionTime(IEnumerable<int> collection, int millisecondsTimeout)
{
    var executionTimeWithAsync = Stopwatch.StartNew();

    await Task.WhenAll(collection.Select(async _ => await DelayAsync(millisecondsTimeout).ConfigureAwait(false)));

    executionTimeWithAsync.Stop();

    return executionTimeWithAsync.ElapsedMilliseconds;
}

async Task<long> GetAsyncExecutionTimeInTasks(IEnumerable<int> collection, int millisecondsTimeout)
{
    var executionTimeWithAsync = Stopwatch.StartNew();

    await Task.WhenAll(collection.Select(_ => Task.Run(async () => await DelayAsync(millisecondsTimeout).ConfigureAwait(false))));

    executionTimeWithAsync.Stop();

    return executionTimeWithAsync.ElapsedMilliseconds;
}

bool Delay(int millisecondsTimeout)
{
    Thread.Sleep(millisecondsTimeout);
    return true;
}

async Task<bool> DelayAsync(int millisecondsTimeout)
{
    await Task.Delay(millisecondsTimeout);
    return true;
}

内容的提问来源于stack exchange,提问作者Web FR

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.18 08:40:06