我在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条消息。
谢谢,
格雷格。
答案 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(...)
,因为这将阻止任务完成。