如何在 C 语言中使这个循环的主体并行运行#

How to make this loop's body run in parallel in C#

提问人:sbi 提问时间:10/15/2009 最后编辑:sbi 更新时间:10/15/2009 访问量:1914

问:

我有一个这样的循环

ICollection<Data> GetData()
{
  ICollection<Data> result = new List<Data>()
  foreach (var item in aCollection)
    item.AddData(result);
  return result;
}

我现在需要并行执行,而是迭代执行。我的第一次尝试是做这样的催眠

ICollection<Data> GetData()
{
  ICollection<Data> result = new SynchronizedCollection<Data>()
  foreach (var item in aCollection)
    new Thread(delegate() { item.AddData(result); }).Start();
  return result;
}

但是我需要一种方法来等待所有数据添加完毕,然后再返回结果。

最简单的方法是什么?

编辑:这将通过网络进行呼叫。在集合中通常最多有几十个条目。AddData

C# 多线程

评论


答:

1赞 Razzie 10/15/2009 #1

在 C#4.0 中,可以使用如下内容:

Parallel.ForEach<Data>(aCollection, delegate(Data d)
{
  d.AddData(result);
});

评论

1赞 Marcel Jackwerth 10/15/2009
有一种 3.5 的方法(如果你还买不起 4.0)blog.robvolk.com/2009/06/parallel-foreach-loop-in-c-35.html
0赞 sbi 10/15/2009
但据我了解,C# 4.0 还没有发布,是吗?我必须让它与 VS 2008 一起使用。
0赞 flq 10/15/2009
马塞尔,为什么这不是一个答案?这可能是迄今为止最好的一个。
0赞 Razzie 10/15/2009
它还没有发布,不,但你没有指定哪个框架:-)正如 Marcel 所说,您可以实现自己的版本。我还找到了这个:codeproject.com/KB/dotnet/PoorMansParallelForEach.aspx 但我当然不能说它是否有效(代码看起来还不错)。
-1赞 Blindy 10/15/2009 #2

存储对创建的线程对象的引用,并对每个对象调用 join:

ICollection<Data> GetData()
{
  ICollection<Data> result = new SynchronizedCollection<Data>()
  List<Thread> threads=new List<Thread>();

  foreach (var item in aCollection)
  {
    Thread thread=new Thread(delegate() { item.AddData(result); });
    thread.Start();
    threads.Add(thread);
  }

  foreach(Thread thread in threads)
    thread.Join();

  return result;
}

评论

2赞 Oliver Hanappi 10/15/2009
您应该使用 ThreadPool,而不是自己启动线程。ThreadPool 正是为此目的而设计的,它提供了比您自己创建的多个线程更好的性能(想象一下当 aCollection 包含超过 2.000 个元素时的开销!),ThreadPool 通常最多使用 4 个线程(取决于内核计数,Environment.ProcessorCount)。
0赞 Blindy 10/15/2009
也许吧,但我完成了他做他想做的事的尝试。
2赞 Brian Rasmussen 10/15/2009 #3

除非 AddData 非常非常慢,否则创建新线程的开销很可能是代码中的瓶颈。

如果您确实想将其移交给其他线程,则应使用 ThreadPool。至于等待线程完成,您可以使用等待句柄在线程之间发出信号。

评论

0赞 sbi 10/15/2009
布莱恩(Brian)将通过网络呼叫某些东西,因此很可能值得麻烦。但是,我这样做是为了找出它是否值得麻烦。AddData
1赞 Brian Rasmussen 10/15/2009
很公平,但我仍然建议使用 ThreadPool 线程来摊销创建线程的成本。
0赞 Brian Rasmussen 10/15/2009
@sbi:请看奥利弗的回答,如果你需要一个好的介绍,请看 albahari.com/threading
0赞 sbi 10/15/2009
@Brian:我已经通读了那个网站。只是经常,当我使用框架的一个工具做某事时,有人指出,使用其他工具,一切都会简单得多。这就是为什么我问如何做到这一点。(我说过我是一个完整的 .Net/C# 新手吗?
0赞 flq 10/15/2009 #4

最简单的方法可能是等待 parallels 框架在 4.0 中出现。

好吧,对于初学者来说,您需要小心在那里创建线程。如果您有数百甚至数千个项目,则该代码可能会扼杀您的应用,或者至少会降低您的性能。

我将尝试将 AddData 方法设计为异步调用。IAsyncResult 具有 WaitHandle。您可以等待这些句柄,直到所有 asyncresults 都表明操作已完成。然后你就可以继续了。异步执行此操作应确保使用线程池中的线程。这样一来,你就可以获得相当不错的性能,如果你的列表变得非常大,你就会达到工作线程的自然饱和度。

评论

0赞 sbi 10/15/2009
恐怕我不能告诉 TPTB,他们必须告诉客户等待 C# 4.0。通常最多有几十个条目。我究竟将如何执行此异步调用操作?
2赞 Oliver Hanappi 10/15/2009 #5

您可以使用并行扩展,它将集成在 C# 4.0 中,并作为单独的库用于 C# 3.5,您可以在此处下载:Microsoft Parallel Extensions to .NET Framework 3.5

如果不想使用并行扩展,请不要为每次迭代启动一个线程,而是改用 ThreadPool,这样可以提高性能。

正如我在这个答案的评论中了解到的那样,List<> 不是线程安全的,为什么你应该使用 SynchronizedCollection<> 代替。

下面是使用线程池的代码示例。

        IEnumerable<object> providers = null;

        var waitHandles = new List<WaitHandle>();
        foreach (var provider in providers)
        {
            var resetEvent = new ManualResetEvent(false);
            waitHandles.Add(resetEvent);

            ThreadPool.QueueUserWorkItem(s =>
             {
                 // do whatever you want.
                 ((EventWaitHandle)s).Set();
             }, resetEvent);
        }

        WaitHandle.WaitAll(waitHandles.ToArray());

评论

0赞 sbi 10/15/2009
如果我使用 ThreadPool,那么我怎么能轻松地等待所有任务完成呢?(据我了解,是线程安全的,顺便说一句。SynchronizedCollection
0赞 Oliver Hanappi 10/15/2009
在将任务排队到线程池并等待设置所有事件等待句柄时,可以将事件等待句柄作为状态提供。
0赞 LukeH 10/15/2009
@Oliver:不是线程安全的。我猜这就是为什么 OP 的多线程示例使用 ,这是线程安全的。List<T>SynchronizedCollection<T>
0赞 sbi 10/15/2009
@Oliver:而且,作为一个 C# 新手,我想知道如何以尽可能简单的方式做到这一点。