【问题标题】:cannot make multiple thread safe calls to windows forms control无法对 Windows 窗体控件进行多线程安全调用
【发布时间】:2013-11-11 13:54:29
【问题描述】:

我正在增强一个通过 web 服务与 SalesForce CRM 交互的 c# win 表单应用程序。

我有以下代码可以对表单上的标签进行“线程安全”更新:

 delegate void SetTextCallback(string text);

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

我的表单(称为SFDetachifier.cs)有一个执行按钮,它获取两个日历控件的日期,然后调用:

lblAccessStatus.Visible = true;
picProgress.Visible = true;

string fromDate = dtManualFromDate.Value.ToString("yyyy-MM-dd") + fromTime;
string toDate = dtManualToDate.Value.ToString("yyyy-MM-dd") + toTime;
string[] arg = new string[] { "S1", fromDate, toDate };
SFCtrl._SessionLog.Append("Manual detach requested on " + SFOrgName + "\n");
SFCtrl._SessionLog.Append("Date range requested: " + fromDate + " to " + toDate + "\n");

bgProcessing_Production.RunWorkerAsync(arg);

bgProcessing_Production 具有以下后台工作人员代码,其中包括对 setText 的调用

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

        // clear the datagrid view
        gvTaskCases.DataSource = null;
        //gvTaskCases.Rows.Clear();

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

        SetText("login to SalesForce (on " + SFOrgName + ") ...please wait");

现在,当我运行我的应用程序时,我设置了一些日期,然后通过单击执行按钮执行上面的代码,它就可以正常工作(调用 SalesForce XML Web 服务)并将结果放入一个网格中。

一切正常,当我尝试再次运行该程序时出现问题(例如使用不同的日期)

然后我得到一个错误 'Cross-thread operation not valid: Control '' 从创建它的线程以外的线程访问。'

我不明白,我有委托设置?

我做错了什么 - 我猜当后台工作人员再次运行时,它会在新线程上运行,因此线程不安全。

请问我该如何解决这个错误?

【问题讨论】:

  • 只是检查 - gvTaskCases.DataSource = null; 是否会导致异常?
  • 有趣的是错误信息中的控件名称是''
  • 以上两个优点,但是,`gvTaskCases.DataSource = null;` 不是问题(尽管我在setText 调用上方的注释代码中确实遇到了该控件的一些线程安全问题
  • BeginInvoke 会解决问题吗?我不明白为什么,但你可以试一试

标签: c# .net winforms thread-safety


【解决方案1】:

您可以使用这样的匿名委托来缩短时间:

    private void removeGridDS()
    {
        this.gvTaskCases.Invoke((MethodInvoker)delegate
        {
            if (this.gvTaskCases.DataSource != null)
            {
                this.gvTaskCases.DataSource = null;
            }
        });
    }

    private void clear_gvTaskCases()
    {
        this.gvTaskCases.Invoke((MethodInvoker)delegate
        {
            if (this.gvTaskCases.Rows.Count != 0)
            {
                this.gvTaskCases.Rows.Clear();
            }
        });
    }

现在您不需要硬编码的委托了。此外,您没有使用传入的布尔值。

【讨论】:

    【解决方案2】:

    这是我最终为了解决这个问题所做的事情,正如上面的 cmets 之一所建议的那样,看起来很可能是对控件 gvTaskCases 的调用引发了错误,因此我创建了线程安全方法也会调用该控件:

    delegate void SetTextCallback(string text);
    delegate void clear_gvTaskCasesCallback(bool clearIt);
    delegate void remove_gvTaskCasesDSCallback( bool removeDS);
    
    private void removeGridDS(bool removeDS)
        {
            // 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.gvTaskCases.InvokeRequired)
            {
            remove_gvTaskCasesDSCallback d = new remove_gvTaskCasesDSCallback(removeGridDS);
            this.Invoke(d, new object[] { removeDS });
            }
            else
            {
                if (this.gvTaskCases.DataSource !=null)
                {
                this.gvTaskCases.DataSource=null;
                }
            }
        }
    
        private void clear_gvTaskCases(bool clearIt)
        {
        // 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.gvTaskCases.InvokeRequired)
        {
            clear_gvTaskCasesCallback d = new clear_gvTaskCasesCallback(clear_gvTaskCases);
            this.Invoke(d, new object[] { clearIt });
            }
            else
            {
            if (this.gvTaskCases.Rows.Count != 0)
            {
            this.gvTaskCases.Rows.Clear();
            }
        }
    }
    

    【讨论】:

    • 好吧,不要那样做。只需在调用 RunWorkerAsync 之前重置数据绑定。没什么区别,只是代码少了很多。这同样适用于设置标签的文本,没有明显的理由要从工作人员那里这样做,所以不妨提前一毫秒。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2023-03-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多