【问题标题】:BackgroundWorker and ConcurrentQueueBackgroundWorker 和 ConcurrentQueue
【发布时间】:2014-02-21 08:15:57
【问题描述】:

我有一个FileSystemWatcher,它正在寻找新文件,将文件名放在Queue 中。 在一个单独的线程中,队列被关闭。我的代码正在运行,但我怀疑是否会因为异步过程而丢失信息。请看cmets解释的代码: (我想也许我需要某个地方的线程锁之类的东西?) (代码已简化)

public class FileOperatorAsync
{
  private ConcurrentQueue<string> fileQueue;
  private BackgroundWorker worker;
  private string inputPath;

  public FileOperatorAsync(string inputPath)
  {
     this.inputPath = inputPath;
     fileQueue = new ConcurrentQueue<string>();
     worker = new BackgroundWorker();
     worker.WorkerSupportsCancellation = true;
     worker.DoWork += worker_DoWork;
     Start();
  }

  void worker_DoWork(object sender, DoWorkEventArgs e)
  {
     try
     {
        string file;
        while (!worker.CancellationPending && fileQueue.TryDequeue(out file)) //As long as queue has files
        {
          //Do hard work with file
        }
        //Thread lock here?
        //If now Filenames get queued (Method Execute -> Worker is still busy), they wont get recognized.. or?
     }
     catch (Exception ex)
     {
        //Logging
     }
     finally
     {
        e.Cancel = true;
     }
  }

  public void Execute(string file) //called by the FileSystemWatcher
  {
     fileQueue.Enqueue(file);
     Start(); //Start only if worker is not busy
  }

  public void Start()
  {
     if (!worker.IsBusy)
        worker.RunWorkerAsync();
  }

  public void Stop()
  {
     worker.CancelAsync();
  }

}

【问题讨论】:

  • 请不要在标题中包含语言标签,除非没有它就没有意义。标记用于此目的。

标签: c# queue backgroundworker filesystemwatcher concurrent-queue


【解决方案1】:

是的,您可能对Execute 有疑问。它可以留下您的worker 未处理的file

您可以通过两种方式解决它:
1) 你的worker 在处理完所有排队的文件后没有完成。它等待AutoResetEvent 等待下一个文件处理。在这种情况下,Execute 应该通过调用AutoResetEvent.Set 来通知worker
示例:

AutoResetEvent event;
...
// in worker_DoWork
while(!worker.CancellationPending){
    event.WaitOne();
    // Dequeue and process all queued files
}

...
// in Execute
fileQueue.Enqueue(file);
event.Set();

2) 您的工作人员在处理完所有排队的文件后完成(就像您现在所做的那样),但您可以检查BackgroundWorker.RunWorkerCompleted 是否还有文件要处理并再次运行工作人员。
在这种情况下,如果 Execute 没有启动 worker 因为它很忙,那么 worker 将在 BackgroundWorker.RunWorkerCompleted 中再次启动,并且待处理的 file 将被处理。

// in  worker_RunWorkerCompleted
if (!fileQueue.IsEmpty())
    Start();

注意:如果您决定在非 GUI 应用程序中使用 BackgroundWorker.RunWorkerCompleted,那么您应该小心使用 Start,因为 BackgroundWorker.RunWorkerCompleted 不能在您调用 @ 的线程上调用987654341@ 并且将在Start 中发生竞争条件。更多信息:BackgroundWorker.RunWorkerCompleted and threading

如果你同时从两个不同的线程调用Start(),那么它们都可以看到worker.IsBusy == false,它们都会调用worker.RunWorkerAsync()。比另一个线程稍晚调用worker.RunWorkerAsync() 的线程将抛出InvalidOperationException。因此,您应该捕获该异常或将 IsBusy+RunWorkerAsync 包装到带有锁的临界区中,以避免竞争条件和异常抛出。

【讨论】:

  • 谢谢!我使用了您的第二个解决方案。是的,它是一个非 GUI 应用程序。感谢您的注意! “Execute”是由 FileSystemWatcher 创建的事件调用的,所以它已经被不同的线程执行了,但它仍然是同步的?
  • @J.D.我在回答中添加了一个注释来解决您的问题。希望我正确理解了您的问题。
【解决方案2】:

为了不用担心这个问题,当队列为空并且在worker退出之前调用Start时,你可以尝试完全不离开worker方法:

while (!worker.CancellationPending)
{
    while (!worker.CancellationPending && !fileQueue.TryDequeue(out file))
    {
        Thread.Sleep(2000);
    }

    if (worker.CancellationPending)
    {
        break;
    }
    //
}

如果没有不优雅的睡眠,其他可能性是使用ManualResetEvent 类在队列为空并停止为空时发出信号。

【讨论】:

  • 问题不在于队列本身。队列工作正常且稳定。我担心出队完成后排队的项目。请看我在这里放置“//线程锁”的地方?评论
  • @J.D.目前尚不清楚您想要实现什么。那里没有代码,所以显然不需要锁。至于添加项目 - Enqueue 方法也是同步的。
  • 是的,之后没有那么多代码,但我认为可能还有几毫秒的时间……我只是想让它尽可能安全。
  • @J.D.但是你为什么要lock?您到底想同步哪些操作?
  • On "//这里的线程锁?"我希望 Execute() 方法等待,直到 worker_DoWork 完成,所以 worker.IsBusy = false
猜你喜欢
  • 2018-10-11
  • 2012-03-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多