Parallel.ForEach iterating items in collection multiple times

不打扰是莪最后的温柔 提交于 2021-02-08 12:49:05

问题


I have a Parallel.ForEach running inside a Task. It iterates over a collection of email addresses and sends a MailMessage to the SMTP queue, once it's sent it updates a table in the DB with a result.

I can see in the DB that it's sending the MailMessage to the queue multiple times, sometimes up to 6 times. Here is my simplified code, can anyone recommend a better approach?

On button click, I create a new Task...

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.");
        }

This creates a task, which basically gets a ConcurrentBag of subscribers (custom class), iterates over the collection and sends a message...

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
}

I am led to believe that ConcurrentBag's are thread-safe, so I'm not sure why it would be iterating over some in the collection multiple times. Out of a thousand it would queue at least 2 messages for 10% of the collection.

Thanks,

Greg.


回答1:


I am led to believe that ConcurrentBag's are thread-safe, so I'm not sure why it would be iterating over some in the collection multiple times.

Your assumption here is true. In fact, ConcurrentBag<T>'s GetEnumerator<T> method (for enumerating the collection) actually makes an entire copy of the internal collection at that point, so you're iterating over a copy of the collection.

If you're seeing the queue being called multiple times for a single subscriber, it means that you added that subscriber to the ConcurrentBag<T> multiple times, or there is some other issue going on...


On a separate note, the use of a Task here is really not necessary. It only adds overhead (creates a dedicated thread in this case, then immediately blocks and waits on it). It would be much better to just rewrite this to call your method, as so:

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

Broadcast.Results.Broadcast broadcastResult = broadcastService.BroadcastCampaign();
MessageBox.Show(broadcastResult.BroadcastSent.GroupName + " completed. " + broadcastResult.NumberSuccessful + " sent.");

Creating a task just to wait on it immediately (Task.WaitAny) is not helpful at all. In addition, instead of using Task.WaitAny(...), if you want the task for some other purpose, you can just call broadcastResult = task.Result;, as that will block until the task completes.



来源:https://stackoverflow.com/questions/7070266/parallel-foreach-iterating-items-in-collection-multiple-times

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!