Search code examples
c#wpffilesystemwatcher

FileSystemWatcher - add and delete from ObservableCollection, C#


first of all, my code:

private void OnChangedActive(object source, FileSystemEventArgs e)
    {
        try
        {
            switch (e.ChangeType)
            {
                case WatcherChangeTypes.Created:
                    if (File.Exists(e.FullPath))
                    {
                        MachineOrder machineOrderAdded;

                        machineOrderAdded = viewModel.MachineOrdersActive.FirstOrDefault(x => x.Filename == e.Name);
                        if (machineOrderAdded != null)
                            this.Dispatcher.BeginInvoke(new Action(() => viewModel.MachineOrdersActive.Remove(machineOrderAdded)));

                        machineOrderAdded = viewModel.MachineOrdersProductionpool.FirstOrDefault(x => x.Filename == e.Name);
                        if (machineOrderAdded != null)
                            this.Dispatcher.BeginInvoke(new Action(() => viewModel.MachineOrdersProductionpool.Remove(machineOrderAdded)));

                        machineOrderAdded = viewModel.MachineOrdersInProduction.FirstOrDefault(x => x.Filename == e.Name);
                        if (machineOrderAdded != null)
                            this.Dispatcher.BeginInvoke(new Action(() => viewModel.MachineOrdersInProduction.Remove(machineOrderAdded)));


                        this.Dispatcher.BeginInvoke(new Action(() => viewModel.MachineOrdersActive.Add(mainController.generateMachineOrder(e.FullPath))));
                    }

                    break;
                case WatcherChangeTypes.Deleted:

                    MachineOrder machineOrder;
                    String message = "";

                    //ÜBERPRÜFEN, OB SIE IM AKTIVORDNER IST
                    machineOrder = viewModel.MachineOrdersActive.FirstOrDefault(x => x.Filename == e.Name);

                    if (machineOrder != null)
                    {
                        this.Dispatcher.BeginInvoke(new Action(() => viewModel.MachineOrdersActive.Remove(machineOrder)));
                        message = String.Format("Die Datei {0} existiert nicht mehr. Der dazugehörige Auftrag wurde von den aktiven Aufträgen entfernt.", machineOrder.Filename);
                        this.Dispatcher.BeginInvoke(new Action(() => setStatus(message, Level.INFO)));
                        Logger.getInstance().writeLogEntry(Logger.LogLevel.INFO, message, null);
                        break;
                    }


                    //ÜBERPRÜFEN, OB SIE IM FERTIGUNGSPOOL IST
                    machineOrder = viewModel.MachineOrdersProductionpool.FirstOrDefault(x => x.Filename == e.Name);

                    if (machineOrder != null)
                    {
                        this.Dispatcher.BeginInvoke(new Action(() => viewModel.MachineOrdersProductionpool.Remove(machineOrder)));
                        message = String.Format("Die Datei {0} existiert nicht mehr. Der dazugehörige Auftrag wurde aus dem Fertigungspool entfernt.", machineOrder.Filename);
                        this.Dispatcher.BeginInvoke(new Action(() => setStatus(message, Level.INFO)));
                        Logger.getInstance().writeLogEntry(Logger.LogLevel.INFO, message, null);
                        break;
                    }


                    //ÜBERPRÜFEN, OB SIE IM FERTIGUNGSSPEICHER IST
                    machineOrder = viewModel.MachineOrdersInProduction.FirstOrDefault(x => x.Filename == e.Name);

                    if (machineOrder != null)
                    {
                       message = String.Format("Die Datei {0} existiert nicht mehr. Der dazugehörige Auftrag wurde nicht entfernt, da er sich bereits in Produktion befindet", machineOrder.Filename);
                       this.Dispatcher.BeginInvoke(new Action(() => setStatus(message, Level.INFO)));
                       Logger.getInstance().writeLogEntry(Logger.LogLevel.INFO, message, null);
                       break;
                    }


                    //NACHRICHT AUSGEBEN
                    if (String.IsNullOrEmpty(message))
                    {
                        message = String.Format("Die Datei {0} existiert nicht mehr. Der dazugehörige Auftrag wurde nicht gefunden.", machineOrder.Filename);
                        this.Dispatcher.BeginInvoke(new Action(() => setStatus(message, Level.INFO)));
                        Logger.getInstance().writeLogEntry(Logger.LogLevel.INFO, message, null);
                    }




                    break;
                default:
                    break;
            }   
        }
        catch (Exception ex)
        {
            this.Dispatcher.BeginInvoke(new Action(() => setStatus(ex.Message, Level.ERROR)));
            Logger.getInstance().writeLogEntry(Logger.LogLevel.INFO, ex.Message, ex.StackTrace);
        }

    }

Here i have 3 dataGrid based on three different observable collections. if a add a lot of files (or delete a lot of files) of the folder, it misses a file from time to time with the error:

Collection was modified; enumeration operation may not execute

Any clues how to catch the missing files?


Solution

  • You have obvious race condition:

    machineOrderAdded = viewModel.MachineOrdersActive.FirstOrDefault(x => x.Filename == e.Name);
    if (machineOrderAdded != null)
        this.Dispatcher.BeginInvoke(new Action(() => viewModel.MachineOrdersActive.Remove(machineOrderAdded)));
    

    To fix it move everything inside Invoke:

    Dispatcher.InvokeAsync(() =>
    {
        var machineOrderAdded = viewModel.MachineOrdersActive.FirstOrDefault(x => x.Filename == e.Name);
        if(machineOrderAdded != null)
            viewModel.MachineOrdersActive.Remove(machineOrderAdded);
    });
    

    And so on for all cases, avoid accessing collection from anywhere else but UI thread.

    You can also try to synchronize access to collection, e.q. using lock or using thread-safe collection. This will not work with ObservableCollection.

    As per @HansPassant comment, you can simply invoke FileSystemWatcher events directly into UI thread and do all switch/case there.

    // using reinvoke pattern, you can invoke another method to avoid "double-checking"
    void OnChangedActive(object source, FileSystemEventArgs e)
    {
        if (!Dispatcher.CheckAccess())
            Dispatcher.InvokeAsync(() => OnChangedActive(sender, e)); // sorry for InvokeAsync :)
        else
        {
            ... // your code goes here without need to use invoke
        }
    }