C#中的并行迭代?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/523194/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-04 06:40:53  来源:igfitidea点击:

Parallel iteration in C#?

c#loopsforeachiteration

提问by recursive

Is there a way to do foreachstyle iteration over parallel enumerables in C#? For subscriptable lists, I know one could use a regular forloop iterating an int over the index range, but I really prefer foreachto forfor a number of reasons.

有没有办法foreach在 C# 中对并行枚举进行样式迭代?对于可下标列表,我知道可以使用for在索引范围内迭代 int的常规循环,但出于多种原因,我真的更喜欢foreach这样for做。

Bonus points if it works in C# 2.0

如果它在 C# 2.0 中工作,则加分

采纳答案by Zooba

Short answer, no. foreachworks on only one enumerable at a time.

简短的回答,没有。foreach一次只对一个可枚举对象起作用。

However, if you combine your parallel enumerables into a single one, you can foreachover the combined. I am not aware of any easy, built in method of doing this, but the following should work (though I have not tested it):

但是,如果将并行可枚举项合并为一个,则可以foreach合并。我不知道有任何简单的内置方法可以做到这一点,但以下应该有效(尽管我还没有测试过):

public IEnumerable<TSource[]> Combine<TSource>(params object[] sources)
{
    foreach(var o in sources)
    {
        // Choose your own exception
        if(!(o is IEnumerable<TSource>)) throw new Exception();
    }

    var enums =
        sources.Select(s => ((IEnumerable<TSource>)s).GetEnumerator())
        .ToArray();

    while(enums.All(e => e.MoveNext()))
    {
        yield return enums.Select(e => e.Current).ToArray();
    }
}

Then you can foreachover the returned enumerable:

然后你可以foreach遍历返回的枚举:

foreach(var v in Combine(en1, en2, en3))
{
    // Remembering that v is an array of the type contained in en1,
    // en2 and en3.
}

回答by Jon Skeet

Zooba's answer is good, but you might also want to look at the answers to "How to iterate over two arrays at once".

Zooba 的回答很好,但您可能还想查看“如何一次迭代两个数组”的答案。

回答by Hosam Aly

Would this work for you?

这对你有用吗?

public static class Parallel
{
    public static void ForEach<T>(IEnumerable<T>[] sources,
                                  Action<T> action)
    {
        foreach (var enumerable in sources)
        {
            ThreadPool.QueueUserWorkItem(source => {
                foreach (var item in (IEnumerable<T>)source)
                    action(item);
            }, enumerable);
        }
    }
}

// sample usage:
static void Main()
{
    string[] s1 = { "1", "2", "3" };
    string[] s2 = { "4", "5", "6" };
    IEnumerable<string>[] sources = { s1, s2 };
    Parallel.ForEach(sources, s => Console.WriteLine(s));
    Thread.Sleep(0); // allow background threads to work
}

For C# 2.0, you need to convert the lambda expressions above to delegates.

对于 C# 2.0,您需要将上面的 lambda 表达式转换为委托。

Note: This utility method uses background threads. You may want to modify it to use foreground threads, and probably you'll want to wait till all threads finish. If you do that, I suggest you create sources.Length - 1threads, and use the current executing thread for the last (or first) source.

注意:此实用程序方法使用后台线程。您可能希望修改它以使用前台线程,并且您可能希望等到所有线程完成。如果您这样做,我建议您创建sources.Length - 1线程,并将当前正在执行的线程用作最后一个(或第一个)源。

(I wish I could include waiting for threads to finish in my code, but I'm sorry that I don't know how to do that yet. I guess you should use a WaitHandleThread.Join().)

(我希望我可以在我的代码中包含等待线程完成,但很抱歉我还不知道如何做到这一点。我想你应该使用 一种 WaitHandleThread.Join().)

回答by Andre

.NET 4's BlockingCollection makes this pretty easy. Create a BlockingCollection, return its .GetConsumingEnumerable() in the enumerable method. Then the foreach simply adds to the blocking collection.

.NET 4 的 BlockingCollection 使这变得非常容易。创建一个 BlockingCollection,在 enumerable 方法中返回它的 .GetConsumingEnumerable()。然后 foreach 简单地添加到阻塞集合中。

E.g.

例如

private BlockingCollection<T> m_data = new BlockingCollection<T>();

public IEnumerable<T> GetData( IEnumerable<IEnumerable<T>> sources )
{
    Task.Factory.StartNew( () => ParallelGetData( sources ) );
    return m_data.GetConsumingEnumerable();
}

private void ParallelGetData( IEnumerable<IEnumerable<T>> sources )
{
    foreach( var source in sources )
    {
        foreach( var item in source )
        {
            m_data.Add( item );
        };
    }

    //Adding complete, the enumeration can stop now
    m_data.CompleteAdding();
}

Hope this helps. BTW I posted a blog about thislast night

希望这可以帮助。BTW我发布了关于这个博客昨晚

Andre

安德烈

回答by Rob Volk

I wrote an implementation of EachParallel() from the .NET4 Parallel library. It is compatible with .NET 3.5: Parallel ForEach Loop in C# 3.5Usage:

我从 .NET4 Parallel 库中编写了一个 EachParallel() 的实现。它与 .NET 3.5 兼容: C# 3.5 中的并行 ForEach 循环用法:

string[] names = { "cartman", "stan", "kenny", "kyle" };
names.EachParallel(name =>
{
    try
    {
        Console.WriteLine(name);
    }
    catch { /* handle exception */ }
});

Implementation:

执行:

/// <summary>
/// Enumerates through each item in a list in parallel
/// </summary>
public static void EachParallel<T>(this IEnumerable<T> list, Action<T> action)
{
    // enumerate the list so it can't change during execution
    list = list.ToArray();
    var count = list.Count();

    if (count == 0)
    {
        return;
    }
    else if (count == 1)
    {
        // if there's only one element, just execute it
        action(list.First());
    }
    else
    {
        // Launch each method in it's own thread
        const int MaxHandles = 64;
        for (var offset = 0; offset < list.Count() / MaxHandles; offset++)
        {
            // break up the list into 64-item chunks because of a limitiation             // in WaitHandle
            var chunk = list.Skip(offset * MaxHandles).Take(MaxHandles);

            // Initialize the reset events to keep track of completed threads
            var resetEvents = new ManualResetEvent[chunk.Count()];

            // spawn a thread for each item in the chunk
            int i = 0;
            foreach (var item in chunk)
            {
                resetEvents[i] = new ManualResetEvent(false);
                ThreadPool.QueueUserWorkItem(new WaitCallback((object data) =>
                {
                    int methodIndex = (int)((object[])data)[0];

                    // Execute the method and pass in the enumerated item
                    action((T)((object[])data)[1]);

                    // Tell the calling thread that we're done
                    resetEvents[methodIndex].Set();
                }), new object[] { i, item });
                i++;
            }

            // Wait for all threads to execute
            WaitHandle.WaitAll(resetEvents);
        }
    }
}

回答by mafu

If you want to stick to the basics - I rewrote the currently accepted answer in a simpler way:

如果您想坚持基础知识 - 我以更简单的方式重写了当前接受的答案:

    public static IEnumerable<TSource[]> Combine<TSource> (this IEnumerable<IEnumerable<TSource>> sources)
    {
        var enums = sources
            .Select (s => s.GetEnumerator ())
            .ToArray ();

        while (enums.All (e => e.MoveNext ())) {
            yield return enums.Select (e => e.Current).ToArray ();
        }
    }

    public static IEnumerable<TSource[]> Combine<TSource> (params IEnumerable<TSource>[] sources)
    {
        return sources.Combine ();
    }