为什么 Enumerable 不获取下一批,而是总是获取前 3 项

Vij*_* Vj 2 c# async-await .net-core asp.net-core

我正在做一个 POC,将字符串列表分成批次并异步处理每个批次。但是当我运行该程序时,它总是采用第一组项目(根据批量大小为 3)。那么任何人都可以帮助我如何移动到下一组项目。 Take是我写的一个扩展方法。我尝试使用async/await模式。

提前致谢

public class Program
{
    public static async Task Main(string[] args)
    {
        var obj = new Class1();
        List<string> fruits = new()
            {
                "1",
                "2",
                "3",
                "4",
                "5",
                "6",
                "7",
                "8",
                "9",
                "10"
            };
        
        await Class1.Start(fruits);
        Console.ReadLine();
    }
}

public class Class1
{
    private const int batchSize = 3;
    public static async Task Start(List<string> fruits)
    {
        if (fruits == null)
            return;

        var e = fruits.GetEnumerator();
        while (true)
        {    
            var batch = e.Take(3); // always taking the first 3 items and not moving to the next items of the list
            if (batch.Count == 0)
            {
                break;
            }
            await StartProcessing(batch);
        }
    }

    public static async Task StartProcessing(List<string> batch)
    {
        await Parallel.ForEachAsync(batch, async (item, CancellationToken) =>
        {
            var list = new List<string>();
            await Task.Delay(1000);
            Console.WriteLine($"Fruit Name: {item}");
            list.Add(item);
        });
    }
}
Run Code Online (Sandbox Code Playgroud)

扩展.cs

public static class Extensions
        {
            public static List<T> Take<T>(this IEnumerator<T> e, int num)
            {
                List<T> list = new List<T>(num);
                int taken = 0;
                while (taken < num && e.MoveNext())
                {
                    list.Add(e.Current);
                    taken++;
                }

                return list;
            }
}
Run Code Online (Sandbox Code Playgroud)

Hei*_*nzi 6

List<T>.Enumerator是一个结构体。因此,您的枚举器的副本在您的扩展方法中被修改Take这是使用扩展方法( fiddle )的更简单的示例:

using System;
using System.Collections.Generic;

public class Program
{
    public static void Main()
    {
        List<string> fruits = new() { "1", "2", "3", "4", "5", "6", "7", "8", "9", "10" };
        
        var e = fruits.GetEnumerator();
        var firstThree = e.Take(3);
        var nextThree = e.Take(3);
        
        // prints 1, 2, 3
        foreach (var x in firstThree)
            Console.WriteLine(x);

        // also prints 1, 2, 3
        foreach (var x in nextThree)
            Console.WriteLine(x);
    }
}

public static class Extensions
{
    public static List<T> Take<T>(this IEnumerator<T> e, int num)
    {
        List<T> list = new List<T>(num);
        int taken = 0;
        while (taken < num && e.MoveNext())
        {
            list.Add(e.Current);
            taken++;
        }

        return list;
    }
}
Run Code Online (Sandbox Code Playgroud)

您可以通过替换来确保e包含盒装枚举器来解决此问题

var e = fruits.GetEnumerator();
Run Code Online (Sandbox Code Playgroud)

IEnumerable<string> e = fruits.GetEnumerator();
Run Code Online (Sandbox Code Playgroud)

小提琴


或者,较新版本的 C# 允许您使用ref扩展方法,这将使您能够执行如下操作 ( fiddle ):

var e = fruits.GetEnumerator();
    
// For some reason generic type inference won't work here
var firstThree = e.Take<string, List<string>.Enumerator>(3);
var nextThree = e.Take<string, List<string>.Enumerator>(3);

...

public static class Extensions
{
    public static List<T> Take<T, TEnum>(ref this TEnum e, int num)
        where TEnum : struct, IEnumerator<T>
    {
        ...
    }
}
Run Code Online (Sandbox Code Playgroud)

但是,老实说,您的代码不起作用的真正原因是因为枚举器不应该这样使用。内置的Enumerable.Take方法适用于 Enumerable,而不适用于 Enumerator,这是在 .NET 中执行这些操作的惯用方法。

对于您的用例,Enumerable.Chunk是最合适的内置方法。如果您想了解如何从头开始实施它以用于教育目的,请查看以下相关问题: