1

.NET 框架 3.5

我有两个线程使用相同的通用集合。foreach一个线程使用以下语句循环遍历集合:

while(HaveToContinue)
{
   // Do work 1

   try
   {
      foreach(var item in myDictionary)
      {
         // Do something with/to item
      }

      // Do work 2 (I need to complete the foreach first)
   }
   catch(InvalidOperationException)
   {
   }
}

同时另一个线程修改集合:

// The following line causes the InvalidOperationException (in the foreach)
myDictionary.Remove(...);

那么,有没有办法避免这种情况InvalidOperationException?如果我能避免这个异常,我可以一直完成我的工作(工作 1 + 工作 2),相反,每次我捕获异常时,我都无法完成工作。

我想使用一个ManualResetEvent对象,像这样:

while(HaveToContinue)
{
   // Do work 1

   try
   {
      myResetEvent.Reset();
      foreach(var item in myDictionary)
      {
         // Do something with/to item
      }
      myResetEvent.Set();

      // Do work 2 (I need to complete the foreach first)
   }
   catch(InvalidOperationException)
   {
   }
}

并且每次其他线程修改集合时:

// Expect the foreach is completed
myResetEvent.WaitOne();
// And then modify the collection
myDictionary.Remove(...);

但可能有更好的解决方案。

4

1 回答 1

1

如果您使用的是 .NET 4,则应改用ConcurrentBagorConcurrentDictionary类,它们是线程安全的。

如果您使用的是早期版本,那么最简单(尽管效率低下)的解决方案是使用lock. 您可以将其实例化为普通对象:

private readonly object sync = new object();

然后,当你需要访问列表时,先获取锁:

while (HaveToContinue)
{
   // Do work 1

   lock (sync)
   {
      foreach (var item in myDictionary)
      {
         // Do something with/to item
      }
   }

   // Do work 2 (I need to complete the foreach first)
}

同样在修改集合时,获取相同的锁:

lock (sync)
{
    myDictionary.Remove(...);
}

如果您必须在每个项目上做的工作量很大,那么首先获取字典的本地副本,释放锁,然后继续迭代所述副本,让竞赛线程能够更有效修改全局字典:

while (HaveToContinue)
{
   // Do work 1

   Dictionary<Key,Value> localDictionary;

   lock (sync)
   {
      localDictionary = new Dictionary<Key,Value>(myDictionary);
   }

   foreach (var item in localDictionary)
   {
      // Do something with/to item
   }

   // Do work 2 (I need to complete the foreach first)
}
于 2012-11-20T09:59:19.127 回答