0

I'm trying to update a Ultragridrow cell in a background worker, but this is throwing a InvalidOperation Exception when this is called more than 1 time.

Here you have the method that starts the RunWorkerAsync.

    private void RefreshGridCacheStart()
    {
        try
        {
            if (this.uGridCache.Rows.Count == 0)
            {
                return;
            }

            if(!workerThread.IsBusy)
            {
                workerThread.DoWork += LookUpHostnames;
                workerThread.ProgressChanged += UpdateCacheHostCell;
                workerThread.RunWorkerCompleted += WorkerCompleted;
                workerThread.WorkerReportsProgress = true;
                workerThread.RunWorkerAsync();
            }
        }
        catch (Exception ex)
        {
            Debug.WriteLine(ex.Message + "\n" + ex.Source + "\n" + ex.ToString());
        }
    }

This is the DoWork method:

    private void LookUpHostnames(object sender, DoWorkEventArgs e)
    {
        var rowValues = new object[2];

        try
        {
            foreach (UltraGridRow row in uGridCache.Rows)//here is were I get an invalid operation exception
            {
                string cellValue = row.Cells["Host"].Text;
                if (Globals.cNet.isValidIP(cellValue))
                {
                    rowValues[0] = row;
                    rowValues[1] = cellValue;

                    workerThread.ReportProgress(0, rowValues);

                    string resolvedHostname = Globals.cIPLookup.LookupHostFromIP(cellValue);
                    rowValues[1] = resolvedHostname;

                    workerThread.ReportProgress(0, rowValues);
                }
            }
        }
        catch (Exception ex)
        {
            Debug.WriteLine(ex.Message + "\n" + ex.Source + "\n" + ex.ToString());
        }

    }

And this is the Report Progress method:

    private void UpdateCacheHostCell(object sender, ProgressChangedEventArgs e)
    {
        var rowValues = e.UserState as object[];
        var row = (UltraGridRow) rowValues[0];
        var sMesage = (string) rowValues[1];

        row.Cells["Host"].Value = sMesage;
    }
Cœur
  • 37,241
  • 25
  • 195
  • 267
hyeomans
  • 4,522
  • 5
  • 24
  • 29

3 Answers3

0

You can find your answer here Different question but ultimately the same problem. Your are changing data inside a foreach loop which invalidates the enumerator.
There are 2 possible solutions I see from reading your code

  • Save all changes that need to be made to an List of changes and only report progress once after the foreach loop. This might not be a very good solution though since you are processing in the background. If there is other code running that could also change the data in the grid you would get the same error again.
  • Since you are not adding rows you could easily change the foreach to a for loop. This might also lead to an issue if code on the main thread could add, or worse, remove rows
Community
  • 1
  • 1
Eddy
  • 5,320
  • 24
  • 40
  • There isn't any code in the DoWork method that is changing the Rows collection by adding or removing items. It is more likely that the issue happens because the DoWork happens on a different thread and the collection is modified elsewhere if that is the cause of the exception. – alhalama Feb 25 '12 at 22:09
  • See http://msdn.microsoft.com/en-us/library/system.collections.ienumerator.aspx "An enumerator remains valid as long as the collection remains unchanged. If changes are made to the collection, such as adding, modifying, or deleting elements, .." Changing existing items also invalidates the collection and that IS exactly what his code does in UpdateCacheHostCell – Eddy Feb 26 '12 at 12:08
  • The code isn't modifying the collection as the same instances of UltraGridRow objects will be in the collection before and after the code is executed. You would need to either add, remove, or change the instance of an item in the collection for the InvalidOperationException to be thrown. Modifying the value of a property exposed by an object in the list will not cause this exception and that is what is being done with the code. – alhalama Feb 28 '12 at 03:11
  • So the OP resolved his issue and marked this as the answer and you are insisting he is wrong. I presume that you actually tested this and invalidated my answer using an actual ultragrid object. If so I'm interested to see it, otherwise thanks for the downvote and please use your time to answer some unanswered questions. – Eddy Feb 28 '12 at 12:27
  • In my testing the RowEnumerator for the RowsCollection which is used by the UltraGrid doesn't throw the InvalidOperationException when intentionally changing the collection by adding or removing items within the for each loop on the same thread. When I used the code from the post in a sample I couldn't reproduce the InvalidOperationException though there may be many reasons for this including using a different build of NetAdvantage. The only InvalidOperationException I managed to reproduce was from changing the value of many cells on a background thread and wasn't related to iterating. – alhalama Feb 29 '12 at 21:01
0

Sounds like something must be changing the underlying row collection hence invalidating your enumerable.

If you convert your enumerable to a list using .ToList() (this will cause the enumerable to iterate and give you a new list containing the items in the original) you will be able to iterate over this new enumerable and changes in the source won't affect you.

foreach (UltraGridRow row in uGridCache.Rows.ToList())
{
    ....
    workerThread.ReportProgress(0, rowValues);
}

You will have to be aware that if something else is changing the rows on the grid, your ReportProgress might be reporting progress of something that no longer exists in the grid, you might want to check in your ReportProgress handler whether reporting on progress for that item is still valid before doing whatever you do.

Mark Allanson
  • 1,368
  • 1
  • 10
  • 19
0

The MSDN documentation on DoWork states the following: "You must be careful not to manipulate any user-interface objects in your DoWork event handler. Instead, communicate to the user interface through the BackgroundWorker events.".

You can view the full details of the DoWork method here: http://msdn.microsoft.com/en-us/library/system.componentmodel.backgroundworker.dowork.aspx

Accessing the UltraGridRows from this event is causing you to access the UltraGrid from another thread and windows forms controls aren't thread safe.

Note that this isn't limited to accessing properties of the control. If you were to set values in the data source that the UltraGrid is bound to you would have the same issue as the change notifications would then happen on the background thread and the UI would still be manipulated from the background thread.

Note that there are only a few members that are actually thread safe on windows forms controls and these are documented in the section on Thread Safety for Control on MSDN: http://msdn.microsoft.com/en-us/library/system.windows.forms.control.aspx

Safe, Simple Multithreading in Windows Forms is a good resource for threading in windows forms even though it is older:

How to: Make Thread-Safe Calls to Windows Forms Controls is also a good resource http://msdn.microsoft.com/en-us/library/ms171728.aspx

alhalama
  • 3,218
  • 1
  • 15
  • 21