2011-08-15 50 views
8

我有一个在任务内运行的Parallel.ForEach。它遍历一组电子邮件地址并向SMTP队列发送一个MailMessage,一旦它发送,它就会更新数据库中的一个表格并显示结果。Parallel.ForEach多次迭代集合中的项

我可以在数据库中看到它将多次发送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条消息。

谢谢,

Greg。

+0

我不明白你为什么在任务中产生一个并行。为什么不只是没有任务,并调用broadcastService.BroadcastCampaign();? –

+0

我有这个任务,因为最终,一旦我在Parallel.ForEach内部正常工作,它将成为一个Windows服务,每隔几秒发送一次broadcastService,它显然需要一些工作,我只是把它放在那里以向你展示它在Task内部运行,而不是它是最终的代码。 – gfyans

回答

6

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

你的假设是真实的。实际上,ConcurrentBag<T>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)是没有帮助的。另外,如果您想将该任务用于其他目的,您可以拨打broadcastResult = task.Result;,因为这样会阻塞,直到任务完成为止,而不是使用Task.WaitAny(...)

+0

我认为肯定会出现其他一些问题,如果我将其更改为使用简单for或foreach,它会进行迭代,每个订阅者发送一条消息(它只需要两次)。我将在明天左右更改代码,摆脱任务并将ConcurrentBag更改为IEnemurable(如果这就是它的作用)并查看它是如何发生的。将回报。 – gfyans

+0

@Greg F:我怀疑你的“做一些工作”在内部是不是线程安全的...... –

+0

是的,你是对的,这是内部的工作不是线程安全的!今天早上重新工作,现在就开始工作。谢谢你的帮助。 – gfyans