Parallel.ForEach多次迭代集合中的项目

时间:2011-08-15 20:15:01

标签: c# parallel-processing

我在Task中运行了Parallel.ForEach。它迭代一组电子邮件地址,并将一个MailMessage发送到SMTP队列,一旦发送它就会更新数据库中的表,并显示结果。

我可以在数据库中看到它多次向队列发送MailMessage,有时最多6次。这是我的简化代码,任何人都可以推荐更好的方法吗?

点击按钮,我创建了一个新的任务...

CampaignManager.Broadcast.BroadcastService broadcastService = new CampaignManager.Broadcast.BroadcastService();

        var task = Task<CampaignManager.Broadcast.Results.Broadcast>.Factory.StartNew(() => { 
            return broadcastService.BroadcastCampaign();
        }, TaskCreationOptions.LongRunning);

        Task.WaitAny(task);

        if (task.Result != null)
        {
            Broadcast.Results.Broadcast broadcastResult = task.Result;
            MessageBox.Show(broadcastResult.BroadcastSent.GroupName + " completed. " + broadcastResult.NumberSuccessful + " sent.");
        }

这会创建一个任务,它基本上会获得订阅者的ConcurrentBag(自定义类),遍历集合并发送消息......

public Results.Broadcast BroadcastCampaign()
{
// Get ConcurrentBag of subscribers
subscribers = broadcast.GetSubscribers();

// Iterate through subscribers and send them a message
Parallel.ForEach(subscribers, subscriber =>
{
    // do some work, send to SMTP queue

    // Add to DB log
});

// return result
}

我被认为ConcurrentBag是线程安全的,所以我不确定为什么它会多次迭代集合中的一些。在千分之一中,它将为10%的集合排队至少2条消息。

谢谢,

格雷格。

1 个答案:

答案 0 :(得分:7)

  

我被认为ConcurrentBag是线程安全的,所以我不确定为什么它会多次迭代集合中的一些。

你的假设是真的。事实上,ConcurrentBag<T>的{​​{1}}方法(用于枚举集合)实际上在那时制作了内部集合的完整副本,因此您将迭代集合的副本。

如果您看到为单个订阅者多次调用队列,则表示您已将该订阅者多次添加到GetEnumerator<T>,或者还有其他问题正在进行...


另外,在这里使用任务确实没有必要。它只增加了开销(在这种情况下创建一个专用线程,然后立即阻塞并等待它)。如果只是重写它来调用你的方法会更好:

ConcurrentBag<T>

创建任务只是为了立即等待它(CampaignManager.Broadcast.BroadcastService broadcastService = new CampaignManager.Broadcast.BroadcastService(); Broadcast.Results.Broadcast broadcastResult = broadcastService.BroadcastCampaign(); MessageBox.Show(broadcastResult.BroadcastSent.GroupName + " completed. " + broadcastResult.NumberSuccessful + " sent."); )根本没有帮助。此外,如果您希望将任务用于其他目的,而不是使用Task.WaitAny,则只需调用Task.WaitAny(...),因为这将阻止任务完成。