Search code examples
c#.netmultithreadingthread-safetybackgroundworker

making thread safe calls to controls


This is quite strange.

I have a background worker doing some work on my Windows Form. As part of this work a dat grid control is updated.

Once the process is completed, all is well.

If I click the button again to kick off the background worker and start the process again, I get an error cross thread not valid on the below code:

private void bgProcessing_Production_DoWork(object sender, DoWorkEventArgs e)
    {
        String[] args = (String[])e.Argument;
        e.Result = args[0];

gvTaskCases.DataSource = null;

if (gvTaskCases.Rows.Count != 0) // EXCEPTION IS THROWN HERE!
    {
    gvTaskCases.Rows.Clear(); // .Update();
    }

Now here's the thing, as I said, it works fine the first time.

But even wierder, if I click Enable Editing in the error dialog, then hit F5 it runs fine.

So have I just been lucky that my code has been running fine for several months, or am I missing something more fundamental?

How should I change this code to avoid a debug error like this? UPDATE: Here is the full error detail:

Reason: System.InvalidOperationException: Cross-thread operation not valid: Control '' accessed from a thread other than the thread it was created on.
   at System.Windows.Forms.Control.get_Handle()
   at System.Windows.Forms.Control.SetVisibleCore(Boolean value)
   at System.Windows.Forms.Control.set_Visible(Boolean value)
   at System.Windows.Forms.DataGridView.LayoutScrollBars()
   at System.Windows.Forms.DataGridView.ComputeLayout()
   at System.Windows.Forms.DataGridView.PerformLayoutPrivate(Boolean useRowShortcut, Boolean computeVisibleRows, Boolean invalidInAdjustFillingColumns, Boolean repositionEditingControl)
   at System.Windows.Forms.DataGridView.ResetUIState(Boolean useRowShortcut, Boolean computeVisibleRows)
   at System.Windows.Forms.DataGridViewRowCollection.OnCollectionChanged_PreNotification(CollectionChangeAction cca, Int32 rowIndex, Int32 rowCount, DataGridViewRow& dataGridViewRow, Boolean changeIsInsertion)
   at System.Windows.Forms.DataGridViewRowCollection.OnCollectionChanged(CollectionChangeEventArgs e, Int32 rowIndex, Int32 rowCount, Boolean changeIsDeletion, Boolean changeIsInsertion, Boolean recreateNewRow, Point newCurrentCell)
   at System.Windows.Forms.DataGridViewRowCollection.ClearInternal(Boolean recreateNewRow)
   at System.Windows.Forms.DataGridView.RefreshColumnsAndRows()
   at System.Windows.Forms.DataGridView.OnDataSourceChanged(EventArgs e)
   at System.Windows.Forms.DataGridView.set_DataSource(Object value)
   at SFDetachifier.SFDetachifier.bgProcessing_Production_DoWork(Object sender, DoWorkEventArgs e) in C:\Users\nightcopy\Documents\Visual Studio 2010\Projects\SFDetachifier_2013\SFDetachifier\SFDetachifier.cs:line 1464
   at System.ComponentModel.BackgroundWorker.OnDoWork(DoWorkEventArgs e)
   at System.ComponentModel.BackgroundWorker.WorkerThreadStart(Object argument)

EDIT: I should mention that I make thread safe calls to other controls such as a textbox using the below code:

     private void SetText(string text)
        {
        // InvokeRequired required compares the thread ID of the
        // calling thread to the thread ID of the creating thread.
        // If these threads are different, it returns true.
        if (this.lblAccessStatus.InvokeRequired)
            {
            SetTextCallback d = new SetTextCallback(SetText);
            this.Invoke(d, new object[] { text });
            }
        else
            {
            this.lblAccessStatus.Text = text;
            this.lblAccessStatus.Refresh();
            }
        }

so do I need to do something similar on the data grid?


Solution

  • Use InvokeRequired on the control:

    Action task = () => {
        gvTaskCases.DataSource = null;
    
        if (gvTaskCases.Rows.Count != 0) // EXCEPTION IS THROWN HERE!
        {
            gvTaskCases.Rows.Clear(); // .Update();
        }
    };
    
    if(gvTaskCases.InvokeRequired) {
        gvTaskCases.Invoke(task);        
    }
    else {
        task();
    }