0

I'm using Reactive Extensions and ReactiveUI to update a collection of Process objects periodically.

When a checkbox is checked the property Processes, which is bound to a DataGrid, is filled and a timer is set to update every 200ms all processes. Processes that have exited are removed.

The Collection was modified; enumeration operation may not execute. exception is sometimes thrown when doing a foreach. I don't understand, because I am not removing or adding objects to the collection when iterating (just setting a property of the ProcessModel)

Also does the timer wait until everything is completed before firing again?

ViewModel

private ReactiveList<IProcessModel> _processes = new ReactiveList<IProcessModel>() { ChangeTrackingEnabled = true };
public ReactiveList<IProcessModel> Processes { get { return _processes; } }

IDisposable timer;

private void DoShowProcesses(bool checkboxChecked)
{
    Processes.Clear();
    if (checkboxChecked)
    {
        //checkbox checked
        lock (Processes)
            Processes.AddRange(_monitorService.GetProcesses());
        timer = Observable.Timer(TimeSpan.FromMilliseconds(200.0))
            .Select(x =>
        {
            lock (Processes)
            {
                foreach (var process in Processes) //throws the 'Collection was modified; enumeration operation may not execute.'
                    process.UpdateMemory(); 

                return Processes.Where(p => p.ProcessObject.HasExited).ToList();
            }
        }).
        ObserveOnDispatcher()
        .Subscribe(processesExited =>
        {
            if (processesExited.Count() > 0)
            {
                lock (Processes)
                    Processes.RemoveAll(processesExited); //remove all processes that have exited
            }

        });
    }
    else
    {
        if (timer != null)
            timer.Dispose();
    }
}

ProcessModel

public class ProcessModel : ProcessModelBase, IProcessModel
{

    public ProcessModel(Process process)
    {
        ProcessObject = process;

    }

    public void UpdateMemory()
    {
        try
        {
            if (!ProcessObject.HasExited)
            {
                long mem = ProcessObject.PagedMemorySize64;
                ProcessObject.Refresh();
                if (mem != ProcessObject.PagedMemorySize64)
                    OnPropertyChanged(nameof(ProcessObject));
            }
        }
        catch (Exception)
        {
            //log it
        }
    }
BertAR
  • 425
  • 3
  • 18
  • You get the error because something changed the processes as you're trying to iterate. I think you should try and simplify your code. Start with the simplest code shows the list and then iterate from there. Increase the wait time before redoing stuff to a second maybe. And DO NOT go changing a collection whilst you iterate with foreach. – Andy May 03 '18 at 14:42
  • The observable `Observable.Timer(TimeSpan.FromMilliseconds(200.0))` only fires once. You need to do `Observable.Timer(TimeSpan.FromMilliseconds(200.0), TimeSpan.FromMilliseconds(200.0))` or `Observable.Interval(TimeSpan.FromMilliseconds(200.0))` to have it repeatedly fire. – Enigmativity May 04 '18 at 00:15
  • I echo Shlomo's call for a [mcve]. – Enigmativity May 04 '18 at 00:17

1 Answers1

0

It is best to post a minimal, complete, verifiable example to help you. This isn't possible to debug or test as written.

As a guess, I would assume you have some sort of handler which is being triggered by the OnPropertyChanged(nameof(ProcessObject)); call. This handler probably removes and possibly re-adds the object from the enumerable. To test, you can detach the handler, or remove the call and see if the exception still occurs.

Shlomo
  • 14,102
  • 3
  • 28
  • 43
  • 1
    When you start your answer with requesting an MCVE and use words like "guess" and "probably", you have to think if you really should have posted an answer at all... – DavidG May 03 '18 at 15:57