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

集合遍历:asParallel与异步函数的基准测试结果困惑解析

三种集合遍历方法耗时差异的原因分析

咱们一个个拆解这三个方法的行为和Benchmark结果的由来:

1. 同步遍历 ListLookup

这个方法用List.ForEach逐个处理元素,每个元素执行Thread.Sleep(2秒)——Thread.Sleep是阻塞当前线程的操作。因为是同步顺序执行,3个元素就是3 × 2秒 = 6秒,和你看到的6,027,116,440 ns(约6秒)完全吻合,这部分是符合预期的。

2. 并行遍历 ListLookupAsParallel

AsParallel()会把集合的处理任务分发到线程池的多个线程上并行执行。这里3个元素的Thread.Sleep(2秒)是同时进行的,所以总耗时只需要接近单个元素的处理时间(约2秒),对应结果里的2,008,655,313 ns(约2秒),这也是并行处理的正常表现——阻塞操作的并行化确实能缩短总耗时。

3. 异步遍历 ListLookupAsync(核心问题所在)

这个结果的异常完全是因为BenchmarkDotNet的测试逻辑和你的代码写法共同导致的:

  • 首先,IAsyncEnumerable是惰性执行的——调用ListLookupAsync()方法时,并不会立刻执行方法体里的循环和await Task.Delay,它只会返回一个异步枚举器对象,只有当你调用MoveNextAsync()(比如用await foreach遍历它)的时候,才会逐步执行里面的代码。
  • 而BenchmarkDotNet默认情况下,对于返回IAsyncEnumerable的方法,不会自动枚举它——你的基准测试实际上只测了「创建异步枚举器」这个极轻量的操作,完全没有执行到那些Task.Delay的逻辑,所以耗时才只有27ns左右,和实际的异步遍历耗时完全不沾边。

另外还要提一下你代码里的小问题:方法里第一个Task.Delay(2秒)没有加await,这个任务会被创建后立刻丢弃,根本不会等待,属于无用代码,不过这不是导致基准结果异常的主要原因——就算你去掉这行,只要Benchmark没枚举异步枚举器,结果还是会一样快。

如何正确测试异步遍历?

如果你想准确测试IAsyncEnumerable的遍历耗时,需要修改基准方法,让它实际枚举异步序列,比如改成这样:

[Benchmark]
public async Task ListLookupAsync()
{
    await foreach (var item in ListLookupAsyncEnumerable())
    {
        // 遍历即可,不需要额外操作
    }
}

// 把原来的异步枚举逻辑抽成单独方法
private async IAsyncEnumerable<int> ListLookupAsyncEnumerable()
{
    foreach (var item in _list)
    {
        await Task.Delay(TimeSpan.FromSeconds(2));
        yield return item;
    }
}

这样修改后,Benchmark才会真正执行所有异步等待逻辑,结果会接近6秒(因为异步遍历是顺序执行每个await Task.Delay,和同步遍历的总耗时差不多,区别只是异步等待不会阻塞线程)。

内容的提问来源于stack exchange,提问作者Sameh Hamza

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.08 22:47:41