1

У меня есть FileSystemWatcher, который ищет новые файлы, помещая имена файлов в Queue. В отдельной теме очередь отработана. Мой код работает, но я сомневаюсь, может ли потеряться информация из-за асинхронного процесса. Пожалуйста, посмотрите код объяснена комментариев: (я думаю, что, может быть, мне нужно что-то вроде замка где-нить?) (код упрощается)BackgroundWorker и ConcurrentQueue

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(); 
    } 

} 
+0

Пожалуйста, не включайте ярлык в заголовок, если это не имеет смысла без него. Теги служат для этой цели. –

ответ

2

Да, у вас может возникнуть проблема с Execute. Он может оставить file не обработанным вашим worker.

Вы можете решить это двумя путями:
1) Ваш worker не закончил после обработки всех файлов в очереди. Он ждет на AutoResetEvent для следующего файла для обработки. В этом случае Execute должен уведомить worker по телефону AutoResetEvent.Set.
Пример:

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(); 

Примечание: если вы решите использовать BackgroundWorker.RunWorkerCompleted в приложении без GUI, то вы должны быть осторожны в Start потому BackgroundWorker.RunWorkerCompleted может быть вызван не на потоке, в котором вы называете Execute и условия гонки будут происходить в Start. Более подробная информация: BackgroundWorker.RunWorkerCompleted and threading

, если вы звоните Start() из двух разных потоков одновременно, то оба они могут видеть, что worker.IsBusy == false и они оба будут называть worker.RunWorkerAsync(). Поток, который вызывает worker.RunWorkerAsync() немного позже другого потока, выдает InvalidOperationException. Поэтому вы должны поймать это исключение или обернуть IsBusy + RunWorkerAsync в критический раздел с блокировкой, чтобы избежать состояния гонки и исключения.

+0

Спасибо! Я использовал ваше второе решение. Да, это приложение, отличное от GUI. Спасибо за примечание! «Execute» вызывается созданным событием FileSystemWatcher, поэтому его уже выполняли разные потоки, но его все еще синхронно? – JDeuker

+0

@ J.D. Я добавил еще одно замечание к моему ответу, чтобы ответить на ваш вопрос. Надеюсь, я правильно понял ваш вопрос. –

+0

Помог мне много, спасибо! – JDeuker

1

Чтобы не придется беспокоиться о проблеме, когда очередь пустой и Start вызываются перед выходом работника, вы можете попробовать, не выходя из рабочего метода вообще:

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

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

Другой возможность, без спального безвкусного будут использовать ManualResetEvent класс сигнализировать, когда очередь пуста и перестает быть пустым.

+0

Проблема не в самой очереди. Очередь работает нормально и стабильно. Я беспокоюсь о предметах, которые находятся в очереди после завершения dequeuing. Пожалуйста, посмотрите на то место, где я разместил здесь // // Thread lock? Комментарий – JDeuker

+0

@ J.D. Неясно, чего вы хотите достичь. Там нет кода, поэтому, очевидно, блокировки не требуется. Что касается добавления элементов, то также синхронизируется метод «Enqueue». – BartoszKP

+0

Да, после этого не так много кода, но я думаю, что может пройти еще миллисекунды ... Я просто хочу сделать это как можно безопаснее. – JDeuker