如何在迭代(C#)时向列表中添加ListItem?

3

我有一个小应用程序,使用BackgroundWorker来一直处理IEnumerator<T> list

代码基本上是这样的:

while(true){
    foreach(T item in list){
       // Process each item and send process
       // Add an object in child List ( List<T1> item.Result )
    }
    Thread.Sleep(500);
}

现在我有一个按钮和一个文本框,它们将直接添加到IEnumerator中。

问题是,在添加按钮后,后台工作器继续处理当前正在处理的项,但在完成该项之后会停止。它不会继续执行。

我如何安全地添加项目到列表中,而不影响后台工作器?除了后台工作器还要添加对象到该项以外。应该采取什么解决方案?

谢谢

3个回答

6

将后台工作程序迭代复制原始列表,而不是直接迭代原始列表本身。

 while (true)
 {
       foreach (T item in new List<T>( list ))
       {
          ....
       }
       Thread.Sleep(500);
 }

如果您在枚举集合时尝试修改它,枚举器将抛出异常。从文档中可以得知:
“只要集合保持不变,枚举器就保持有效。如果对集合进行更改(例如添加、修改或删除元素),则枚举器将被彻底使无效,下一次MoveNext或Reset调用会抛出InvalidOperationException异常。如果在MoveNext和Current之间修改了集合,则Current返回已设置的元素,即使枚举器已失效。”

是的,但我不修改枚举器。我正在编辑的是item.Result。这不会影响itemList。如果我复制它,item.Result将不会被修改。 - DucDigital
1
@Duc - 列表的副本将包含与原始对象相同的引用,因此如果您在副本中修改对象,则会修改原始副本(假设T不是值类型)。 您询问了在迭代时添加到列表的问题,这将修改枚举器的基础集合并使其无效,导致在下一次移动时引发异常。 - tvanfosson

2

首先,您应该学习多线程编程的基础知识,以下是一些内容。

可以尝试以下内容:

// shared queue
ConcurrentQueue<T> queue = new ConcurrentQueue<T>();
// shared wait handle
AutoResetEvent autoEvent = new AutoResetEvent();

这里的队列比列表更好,因为它允许您添加和删除元素而不必担心当前元素的索引 - 您只需在另一个端口上使用Enqueue()项,在另一个端口上使用Dequeue()。使用System.Collections.Concurrent命名空间中的类,它会自动处理线程安全访问(由于复杂的原因,您可能希望稍后阅读,并且比简单的lock()更快)。
现在是前台线程:
// schedule the work
queue.Enqueue(itemOfWork);
// and wake up our worker
autoEvent.Set();

这里有一个神奇的部分,就是在我们的WaitHandle上调用了Set()方法(是的,AutoResetEventWaitHandle的实现之一)。它唤醒了一个等待同步事件触发的单个线程,而不使用像Thread.Sleep()这样丑陋的结构。在多线程代码中调用Sleep()几乎总是一个错误的迹象!
好的,最后一部分 - 工作线程。这里没有太多变化:
while(true)
{
  // wait for the signal
  autoEvent.WaitOne();
  T item;
  // grab the work item
  queue.TryDequeue(out item);

  // handle the item here;
}

1

我刚刚加了一个锁。但是程序会让用户等待直到所有项目都枚举完成后再将其添加到列表中。它可以工作,但基本上并不适合用户体验。 :) - DucDigital

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接