代码之家  ›  专栏  ›  技术社区  ›  user19291301

平行。ForEachAsync不会更改IEnumerable项的属性

  •  2
  • user19291301  · 技术社区  · 5 月前

    我有以下课程

    class Person
    {
        public required string Name { get; set; }
        public required string Lastname { get; set; }
    
        public override string ToString() => $"{Name} - {Lastname}";
    }
    

    然后,我正在创建集合 Persons :

     // dummy data
    IEnumerable<Person> persons = Enumerable.Range(0, 1000).Select(index => new Person()
    {
        Name = index.ToString(),
        Lastname = index.ToString() + "^"
    });
    

    在此之后,如果我尝试使用以下命令修改项目 Parallel.ForEachAsync 实际 珀森斯 里面 IEnumerable 不会改变

    await ChangeByParallel(persons); // does not change the items inside IEnumerable
    
    static async Task ChangeByParallel(IEnumerable<Person> persons)
    {
        await Parallel.ForEachAsync(persons, new ParallelOptions { MaxDegreeOfParallelism = Environment.ProcessorCount },
            async (person, token) =>
            {
                person.Name = "Ermalo";
                person.Lastname = "Magradze";
                await Task.Delay(100);
            });
    }
    

    我发现,如果我转身 IEnumerable 进入之内 List<Person> 问题已解决 里面的数据 List 改变了。

    IEnumerable<Person> persons = Enumerable.Range(0, 1000).Select(index => new Person()
    {
        Name = index.ToString(),
        Lastname = index.ToString() + "^"
    }).ToList(); // ToList  added, So that underlying collection is list now.
    
    await ChangeByParallel(persons); // working
    
    static async Task ChangeByParallel(IEnumerable<Person> persons)
    {
        await Parallel.ForEachAsync(persons, new ParallelOptions { MaxDegreeOfParallelism = Environment.ProcessorCount },
            async (person, token) =>
            {
                person.Name = "Ermalo";
                person.Lastname = "Magradze";
                await Task.Delay(100);
            });
    }
    

    即使你我找到了解决方案,仍然不明白为什么 ToList 解决这个问题。

    我知道 IEnumerable 是延迟加载的,但在调试此代码时,我看到并行代码被执行。此外,我不使用struct来认为 IEnumerable 被复制。

    所以,我想知道为什么 平行。ForEachAsync 不要更改项目 IEnumerable .

    1 回复  |  直到 5 月前
        1
  •  6
  •   Marc Gravell    5 月前

    Enumerable.Range 不可重复;它将重新执行所有操作(创建新对象等) 每次迭代 。我们可以测试一下:

    var seq = Enumerable.Range(1241241, 1).Select(i => i.ToString());
    var a = seq.Single();
    var b = seq.Single();
    Console.WriteLine($"{a == b}, {ReferenceEquals(a, b)}");
    

    哪个输出 True, False 因为不同 实例 已为两次迭代创建了字符串(通过 Single() ).

    更一般地说: 任何 IEnumerable<T> 可能或可能不会 可重复。列表、数组等:通常是可重复的(只要不更改数据)。更复杂的序列——例如,从套接字、外部数据源或RNG读取:没那么多。

    添加 .ToList() 意味着原始序列只迭代一次,并缓冲到一个列表中,然后 可重复。添加 .ToList() 上面测试的第一行给了我们 True, True .

    注: .Select 本身就是 不可重复;如果你有一个列表,然后通过创建一个序列 list.Select(...) :该序列将 每次迭代时,都要重新执行所有的投影逻辑。对于大多数LINQ操作,情况也是如此。

    推荐文章