使线程安全调用控件

时间:2014-05-30 20:33:08

标签: c# .net multithreading thread-safety backgroundworker

这很奇怪。

我有一个后台工作人员在我的Windows窗体上做一些工作。 作为这项工作的一部分,更新了数据网格控件。

一旦完成整个过程,一切都很顺利。

如果我再次单击该按钮以启动后台工作程序并再次启动该过程,则会在以下代码中收到错误cross thread not valid

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();
    }

现在就是这样,正如我所说,它第一次运作正常。

但更糟糕的是,如果我在错误对话框中单击启用编辑,然后点击 F5 它就可以正常运行。

所以我很幸运,我的代码已运行好几个月,或者我错过了一些更基本的东西?

我应该如何更改此代码以避免这样的调试错误? 的更新: 以下是完整的错误详细信息:

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)

修改 我应该提一下,我使用以下代码对其他控件(如文本框)进行线程安全调用:

     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();
            }
        }

所以我需要在数据网格上做类似的事情吗?

3 个答案:

答案 0 :(得分:4)

   gvTaskCases.DataSource = null;

您可以从调用堆栈中判断出语句导致崩溃。它第一次工作,因为尚未设置DataSource属性。所以取消它不会产生任何影响。但第二次影响很大,网格需要更新,因为它不再有任何数据。 Kaboom在UI线程以外的任何线程上发生。

DataSource属性线程安全。

简单的解决方法是在调用RunWorkerAsync()之前将其设置为null。

答案 1 :(得分:1)

在控件上使用InvokeRequired:

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();
}

答案 2 :(得分:1)

这是关于Windows窗体线程模型的问题。

来自MSDN:Windows窗体使用单线程单元(STA)模型,因为Windows窗体基于本机Win32窗口,这些窗口本质上是单元线程的。 STA模型意味着可以在任何线程上创建窗口,但是一旦创建它就不能切换线程,并且对它的所有函数调用必须在其创建线程上进行。在Windows窗体之外,.NET Framework中的类使用免费的线程模型。

所以你应该使用Invoke,或者你可以使用BackgroundWorker自动处理这类问题。