【问题标题】:Call method in Parallel.Foreach and save the result into the thread safe list在 Parallel.Foreach 中调用方法并将结果保存到线程安全列表中
【发布时间】:2017-02-17 08:32:56
【问题描述】:

我有这段代码在单线程中运行良好

foreach (var meetingRoom in meetingRooms)
{
            try
            {
                var res = calendarService.GetEvents(meetingRoom.Email, startUtc, endUtc, lightweight, limit);
                result.AddRange(res);
            }
            catch (Exception ex)
            {
                Log.Error(ex);
            }
}

但问题是 meetingRooms 列表包含超过 40 000 个条目。我正在尝试接近 Parallel.Foreach - 这是我到目前为止所得到的:

Parallel.ForEach(meetingRooms, () => 0,(meetingRoom) =>
        {
            var res = calendarService.GetEvents(meetingRoom.Email, startUtc, endUtc, lightweight, limit);

        }
        ,
        finalResult =>
        {
            lock (result)
            {
                result.Add(finalResult);
            }
        });

我不知道第四个参数 localFinally 是如何工作的。

请注意,result 和 meetingRooms 是具有不同数据类型的列表。

编辑:

正如其中一个答案所示,我已经实施了 PLINQ 解决方案。看起来像它的工作,但 Exchange Web 服务(EWS)库没有实现并行调用其方法之一。获取和错误“不是唯一的键!”这是由 Microsoft 代码抛出的。

【问题讨论】:

  • 这里没有真正的问题-我没有看到“localFinally”的pramater ..具体是什么问题
  • 这是在什么背景下? ASP.NET、控制台、WPF、Windows 窗体?
  • 样本在result.Add()result.AddRange()的使用上是不一致的,最好解决这个问题。

标签: c# multithreading parallel.foreach


【解决方案1】:

您可以使用 Plinq。

result = meetingRooms
  .AsParallel()
  .Select(meetingRoom => calendarService.GetEvents(meetingRoom.Email, startUtc, endUtc, lightweight, limit) )
  .ToList();

当您仍想使用 Parallel.ForEach() 时,localFinally 的重载就不是那么有用了。使用基础:

Parallel.ForEach(meetingRooms, meetingRoom =>
    {
        var res = calendarService.GetEvents(meetingRoom.Email, ...);

        lock (result) // only OK when you don't lock on it anywhere else
        {
            result.Add(res);
        }
    });

【讨论】:

  • 我不明白你为什么认为带有本地状态的 ForEach 版本没有用。不就是让每个分区都有自己的本地状态,不用加锁就可以访问吗?
  • 你认为这有什么用?当您的中间结果非常小时,可以使用它。当 GetEvents() 做一些实质性的事情时,localResults 不会有太大的不同,
  • 假设有 40000 个会议室,您的版本将为循环的每次迭代调用 lock 语句。具有本地状态的版本每个分区只会锁定一次,这要少得多。如果对 GetEvents 的调用比获取和释放锁花费更多的时间,这可能并不重要,但这取决于 OP 来发现。
  • 一个无竞争的锁是几纳秒的问题,这在前面已经讨论过了。使您的资源复杂化以解决不存在的问题总是更糟糕。
【解决方案2】:

您还可以使用ConcurrentBag(或ConcurrentQueueConcurrentDictionary)来存储结果并避免锁定。

【讨论】:

  • 不是并发包,用于快速线程-本地存储。
  • 取决于应用程序的需要。在这种情况下,使用 ConcurrentBag 添加项目会更快,并且读取它们(当然是从同一个线程)将使用内部锁。
  • @PanagiotisKanavos - “并发包用于线程本地存储”?嗯?这需要引用。
  • @HenkHolterman 文档、源代码或 Stephen Toub 博客文章?每个线程添加的数据都存储在 TLS 中,以便自己的线程更快地访问。读取其他线程的数据会导致跨线程操作
  • @HenkHolterman 例如 FAQ :: Are all of the new concurrent collections lock-free? 来自 2010 年 it maintains a local queue for each thread that accesses it, and under some conditions, a thread is able to access its local queue in a lock-free manner with little or no contention.  Therefore, while ConcurrentBag<T> sometimes requires locking, it is a very efficient collection for certain concurrent scenarios (e.g. many threads both producing and consuming at the same rate).
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多