如何批量循环IEnumerable

时间:2013-03-14 16:06:22

标签: c# ienumerable

我正在开发一个c#程序,它有一个“IEnumerable users”,可存储400万用户的ID。我需要遍历Ienummerable并每次提取批处理1000 ID以在另一种方法中执行某些操作。

如何从Ienumerable开始一次提取1000个ID ...做一些其他事情,然后获取下一批1000,依此类推?

这可能吗?

9 个答案:

答案 0 :(得分:118)

您可以使用MoreLINQ's Batch operator(可从NuGet获得):

foreach(IEnumerable<User> batch in users.Batch(1000))
   // use batch

如果不能选择简单使用库,则可以重用实现:

public static IEnumerable<IEnumerable<T>> Batch<T>(
        this IEnumerable<T> source, int size)
{
    T[] bucket = null;
    var count = 0;

    foreach (var item in source)
    {
       if (bucket == null)
           bucket = new T[size];

       bucket[count++] = item;

       if (count != size)                
          continue;

       yield return bucket.Select(x => x);

       bucket = null;
       count = 0;
    }

    // Return the last bucket with all remaining elements
    if (bucket != null && count > 0)            
        yield return bucket.Take(count);            
}

BTW的性能你可以简单地返回桶而不用调用Select(x => x)。 Select针对数组进行了优化,但仍会在每个项目上调用选择器委托。所以,在你的情况下,最好使用

yield return bucket;

答案 1 :(得分:40)

听起来你需要使用对象的Skip和Take方法。例如:

users.Skip(1000).Take(1000)

这将跳过前1000并接下来的1000.你只需要增加每次通话时跳过的金额

您可以使用带跳过参数的整数变量,您可以调整跳过的数量。然后,您可以在方法中调用它。

public IEnumerable<user> GetBatch(int pageNumber)
{
    return users.Skip(pageNumber * 1000).Take(1000);
}

答案 2 :(得分:28)

最简单的方法可能就是在LINQ中使用GroupBy方法:

var batches = myEnumerable
    .Select((x, i) => new { x, i })
    .GroupBy(p => (p.i / 1000), (p, i) => p.x);

但是对于更复杂的解决方案,请参阅此blog post,了解如何创建自己的扩展方法来执行此操作。这里重复了后人:

public static IEnumerable<IEnumerable<T>> Batch<T>(this IEnumerable<T> collection, int batchSize)
{
    List<T> nextbatch = new List<T>(batchSize);
    foreach (T item in collection)
    {
        nextbatch.Add(item);
        if (nextbatch.Count == batchSize)
        {
            yield return nextbatch;
            nextbatch = new List<T>(); 
            // or nextbatch.Clear(); but see Servy's comment below
        }
    }

    if (nextbatch.Count > 0)
        yield return nextbatch;
}

答案 3 :(得分:9)

尝试使用:

  public static IEnumerable<IEnumerable<TSource>> Batch<TSource>(
        this IEnumerable<TSource> source,
        int batchSize)
    {
        var batch = new List<TSource>();
        foreach (var item in source)
        {
            batch.Add(item);
            if (batch.Count == batchSize)
            {
                 yield return batch;
                 batch = new List<TSource>();
            }
        }

        if (batch.Any()) yield return batch;
    }

并使用上述功能:

foreach (var list in Users.Batch(1000))
{

}

答案 4 :(得分:5)

您可以使用Take和Skip Enumerable扩展方法实现此目的。有关使用结帐linq 101

的更多信息

答案 5 :(得分:4)

这样的事情会起作用:

List<MyClass> batch = new List<MyClass>();
foreach (MyClass item in items)
{
    batch.Add(item);

    if (batch.Count == 1000)
    {
        // Perform operation on batch
        batch.Clear();
    }
}

// Process last batch
if (batch.Any())
{
    // Perform operation on batch
}

您可以将其概括为通用方法,如下所示:

static void PerformBatchedOperation<T>(IEnumerable<T> items, 
                                       Action<IEnumerable<T>> operation, 
                                       int batchSize)
{
    List<T> batch = new List<T>();
    foreach (T item in items)
    {
        batch.Add(item);

        if (batch.Count == batchSize)
        {
            operation(batch);
            batch.Clear();
        }
    }

    // Process last batch
    if (batch.Any())
    {
        operation(batch);
    }
}

答案 6 :(得分:1)

怎么样

int batchsize = 5;
List<string> colection = new List<string> { "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12"};
for (int x = 0; x < Math.Ceiling((decimal)colection.Count / batchsize); x++)
{
    var t = colection.Skip(x * batchsize).Take(batchsize);
}

答案 7 :(得分:0)

您可以使用Take operator linq

链接:http://msdn.microsoft.com/fr-fr/library/vstudio/bb503062.aspx

答案 8 :(得分:-1)

在流式上下文中,枚举数可能会在批处理的中间被阻塞,这仅仅是因为尚未生成该值(产量),所以有一种超时方法很有用,以便在给定给定值之后生成最后一个批处理时间。我以这个为例,在MongoDB中添加了一个游标。这有点复杂,因为枚举必须在另一个线程中完成。

    public static IEnumerable<List<T>> TimedBatch<T>(this IEnumerable<T> collection, double timeoutMilliseconds, long maxItems)
    {
        object _lock = new object();
        List<T> batch = new List<T>();
        AutoResetEvent yieldEventTriggered = new AutoResetEvent(false);
        AutoResetEvent yieldEventFinished = new AutoResetEvent(false);
        bool yieldEventTriggering = false; 

        var task = Task.Run(delegate
        {
            foreach (T item in collection)
            {
                lock (_lock)
                {
                    batch.Add(item);

                    if (batch.Count == maxItems)
                    {
                        yieldEventTriggering = true;
                        yieldEventTriggered.Set();
                    }
                }

                if (yieldEventTriggering)
                {
                    yieldEventFinished.WaitOne(); //wait for the yield to finish, and batch to be cleaned 
                    yieldEventTriggering = false;
                }
            }
        });

        while (!task.IsCompleted)
        {
            //Wait for the event to be triggered, or the timeout to finish
            yieldEventTriggered.WaitOne(TimeSpan.FromMilliseconds(timeoutMilliseconds));
            lock (_lock)
            {
                if (batch.Count > 0) //yield return only if the batch accumulated something
                {
                    yield return batch;
                    batch.Clear();
                    yieldEventFinished.Set();
                }
            }
        }
        task.Wait();
    }