Reputation: 5981
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?
Upvotes: 0
Views: 835
Reputation: 6050
This is a problem regards to Windows Forms threading model.
From MSDN: Windows Forms uses the single-threaded apartment (STA) model because Windows Forms is based on native Win32 windows that are inherently apartment-threaded. The STA model implies that a window can be created on any thread, but it cannot switch threads once created, and all function calls to it must occur on its creation thread. Outside Windows Forms, classes in the .NET Framework use the free threading model.
So you should use Invoke, or you can use the BackgroundWorker to handle this kind problem automatically.
Upvotes: 1
Reputation: 866
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();
}
Upvotes: 1
Reputation: 941317
gvTaskCases.DataSource = null;
You can tell from the call stack that it is this statement that causes the crash. It works the first time because the DataSource property isn't set yet. So unsetting it cannot have any effect. But big effect the 2nd time, the grid needs to be updated since it doesn't have any data anymore. Kaboom when that happens on any thread other than the UI thread.
The DataSource property is not threadsafe.
The simple workaround is to set it to null before you call RunWorkerAsync().
Upvotes: 4