【问题标题】:Iterating on collection using asParallel vs async function()使用 asParallel vs async function() 对集合进行迭代
【发布时间】:2020-12-18 20:54:45
【问题描述】:

在尝试了解如何使用并行性和并发性时 我在对这些方法进行基准测试时比较了在三种不同方法上花费时间的列表上的登录 但是这三种方法现在让我很困惑:

我在问这段代码是怎么回事?

  • 第一种方法是同步
  • 第二个方法是同步用作并行
  • 第三种方法是异步使用正常循环和 yield
public class LookUpCollections
    {

        private const int N = 3;
        private readonly List<int> _list;
        public LookUpCollections()
        {
            _list = new List<int>();
            for (int i = 0; i < N; i++)
            {
                _list.Add(i);
            }
        }

        [Benchmark]
        public void ListLookup() => _list.ForEach(x => Thread.Sleep(TimeSpan.FromSeconds(2)));

        [Benchmark]
        public void ListLookupAsParallel() => _list.AsParallel<int>().ForAll((x) =>  
        Thread.Sleep(TimeSpan.FromSeconds(2)));

        [Benchmark]
        public async IAsyncEnumerable<int> ListLookupAsync()
        {
            foreach (var item in _list)
            {
                Task.Delay(TimeSpan.FromSeconds(2)); // the method will complete before 2sec delay 
                await Task.Delay(TimeSpan.FromSeconds(2)); // some asynchronous work
                yield return item;
            }
        }

    }

这里是benchmark results

或者这里是控制台的副本和过去,以防图像出现问题:

BenchmarkDotNet=v0.12.1, OS=Windows 10.0.19041.450
.NET Core SDK=3.1.401
 [Host]    : .NET Core 2.1.21 
 DefaultJob : .NET Core 2.1.21
                                                                                                                                                                                                      
                Method                  Mean              Error             StdDev  
|--------------------- |--------------------:|-----------------:|-----------------:|
|           ListLookup | 6,027,116,440.00 ns | 6,926,407.532 ns | 6,478,965.903 ns |
| ListLookupAsParallel | 2,008,655,313.33 ns | 5,275,609.028 ns | 4,934,807.958 ns |
|      ListLookupAsync |            27.47 ns |         0.611 ns |         1.632 ns |                                                                                                          

  LookUpCollections.ListLookupAsync: Default -> 6 outliers were removed (37.39 ns..40.15 ns) 
// * Legends * 
Mean   : Arithmetic mean of all measurements
Error  : Half of 99.9% confidence interval
StdDev : Standard deviation of all measurements
1 ns   : 1 Nanosecond (0.000000001 sec)    

【问题讨论】:

  • 您的问题是“这段代码是怎么回事”?你能说得具体点吗?
  • @TheodorZoulias 在我看来,OP 在问为什么这三种方法之间存在如此大的差异。也许是其他 StackOverflow 网站的问题?
  • 抱歉有歧义,但我不知道为什么第三种方法最快? ,我希望它会像第二种方法一样在 2 秒后完成
  • 你是怎么打电话给ListLookupAsync的?如果正确等待,大约需要 6 秒才能完成。例如使用await foreach(var i in ListLookupAsync()) { ... }。如果您对某件事进行基准测试,请绝对确保您测量的是正确的。
  • 另外,是什么让您认为第三种方法需要 2 秒?它只会连续 3 次依次等待 2 秒。 (未等待的 Task.Delay 是……好吧……毫无意义)

标签: c# collections concurrency async-await task-parallel-library


【解决方案1】:

鉴于您的 cmets,您对第三种方法的结果有一些疑问。我问过你是如何调用该方法的,因为如果像这样正确调用它确实需要大约 6 秒:

List<int> _list;

async Task Main()
{
    const int N = 3;
    
    _list = new List<int>();
    for (int i = 0; i < N; i++)
    {
        _list.Add(i);
    }
    
    await foreach(var i in ListLookupAsync())
    {
        Console.WriteLine(i);   
    }
}

public async IAsyncEnumerable<int> ListLookupAsync()
{
    Console.WriteLine($"{DateTime.Now} - Entering ListLookupAsync");
    
    foreach (var item in _list)
    {
        await Task.Delay(TimeSpan.FromSeconds(2)); // some asynchronous work
        yield return item;
    }
    
    Console.WriteLine($"{DateTime.Now} - Exiting ListLookupAsync");
}

我真的怀疑你测量的是错误的东西。基本上使用 IAsyncEnumerable 类型确实与使用不可等待的枚举具有相同的效果:迭代/收益返回是顺序完成的。由于_list 包含 3 项消耗可枚举的项目,因此使用上面的代码需要 3 次两秒延迟。

如果您想执行基于任务的方法 并行,您可以利用Task.WhenAll

List<int> _list;

async Task Main()
{
    const int N = 3;
    
    _list = new List<int>();
    for (int i = 0; i < N; i++)
    {
        _list.Add(i);
    }
    
    Console.WriteLine($"{DateTime.Now} - Before WhenAll");
    await Task.WhenAll(_list.Select(TaskBased));
    Console.WriteLine($"{DateTime.Now} - After WhenAll");
}

public async Task TaskBased(int index)
{
    await Task.Delay(TimeSpan.FromSeconds(2)); // some asynchronous work
    Console.WriteLine(index);
}

这大约需要 2 秒。

但是,无论如何,请:在质疑基准测试的结果之前,请绝对确保您了解代码的作用,否则您可能会测量错误的事物并得出错误的结论。

【讨论】:

  • 是的,你画了一个大图,作为一个菜鸟,当谈到这个话题时,你的答案很好理解,你谈论方法的使用,IAsyncEnumerable,基于任务的方法和测试正确的事情,这些提示这正是我填补空缺所需要的。
猜你喜欢
  • 2016-10-30
  • 1970-01-01
  • 1970-01-01
  • 2013-07-01
  • 2023-03-15
  • 2010-09-16
  • 1970-01-01
  • 1970-01-01
  • 2014-11-24
相关资源
最近更新 更多