我有一个设想。(Windows Forms, c#, .NET)

There is a main form which hosts some user control. The user control does some heavy data operation, such that if I directly call the UserControl_Load method the UI become nonresponsive for the duration for load method execution. To overcome this I load data on different thread (trying to change existing code as little as I can) I used a background worker thread which will be loading the data and when done will notify the application that it has done its work. Now came a real problem. All the UI (main form and its child usercontrols) was created on the primary main thread. In the LOAD method of the usercontrol I'm fetching data based on the values of some control (like textbox) on userControl.

伪代码看起来像这样:

代码1

UserContrl1_LoadDataMethod()
{
    if (textbox1.text == "MyName") // This gives exception
    {
        //Load data corresponding to "MyName".
        //Populate a globale variable List<string> which will be binded to grid at some later stage.
    }
}

它给出的例外是

跨线程操作无效:从创建该控件的线程以外的线程访问的控件。

为了了解更多关于这一点,我做了一些谷歌搜索,并提出了一个建议,如使用以下代码

代码2

UserContrl1_LoadDataMethod()
{
    if (InvokeRequired) // Line #1
    {
        this.Invoke(new MethodInvoker(UserContrl1_LoadDataMethod));
        return;
    }

    if (textbox1.text == "MyName") // Now it won't give an exception
    {
    //Load data correspondin to "MyName"
        //Populate a globale variable List<string> which will be binded to grid at some later stage
    }
}

但我似乎还是回到了原点。再次申请 变得反应迟钝。这似乎是由于第1行if条件的执行。加载任务再次由父线程完成,而不是由我生成的第三个线程完成。

我不知道我认为这是对还是错。

我如何解决这个问题,以及执行第1行if块的效果是什么?

情况是这样的:我想根据控件的值将数据加载到全局变量中。我不想改变子线程中控件的值。我不会从子线程中做。

因此只有访问值,才能从数据库中获取相应的数据。


当前回答

UI中的线程模型

为了理解基本概念,请阅读UI应用程序中的线程模型(旧的VB链接在这里)。该链接导航到描述WPF线程模型的页面。但是,Windows窗体利用了相同的思想。

UI线程

只有一个线程(UI线程)被允许访问System.Windows.Forms.Control及其子类成员。 试图从不同的线程访问System.Windows.Forms.Control的成员会导致跨线程异常。 因为只有一个线程,所以所有的UI操作都作为工作项进入该线程:

如果UI线程没有工作,那么就存在空闲间隙,可以由与UI无关的计算使用。 为了使用上述间隙,请使用System.Windows.Forms.Control.Invoke或System.Windows.Forms.Control.BeginInvoke方法:

BeginInvoke和Invoke方法

The computing overhead of method being invoked should be small as well as computing overhead of event handler methods because the UI thread is used there - the same that is responsible for handling user input. Regardless if this is System.Windows.Forms.Control.Invoke or System.Windows.Forms.Control.BeginInvoke. To perform computing expensive operation always use separate thread. Since .NET 2.0 BackgroundWorker is dedicated to performing computing expensive operations in Windows Forms. However in new solutions you should use the async-await pattern as described here. Use System.Windows.Forms.Control.Invoke or System.Windows.Forms.Control.BeginInvoke methods only to update a user interface. If you use them for heavy computations, your application will block:

调用

System.Windows.Forms.Control.Invoke导致单独的线程等待,直到调用的方法完成:

BeginInvoke

System.Windows.Forms.Control.BeginInvoke不会导致单独的线程等待被调用的方法完成:

代码的解决方案

阅读问题的答案如何更新GUI从另一个线程在c# ? 对于c# 5.0和。net 4.5,推荐的解决方案在这里。

其他回答

您只需要使用Invoke或BeginInvoke来完成更改UI所需的最小工作量。你的“heavy”方法应该在另一个线程上执行(例如通过BackgroundWorker),然后使用Control. invoke /Control。BeginInvoke来更新UI。这样你的UI线程就可以自由地处理UI事件等。

关于WinForms的例子,请参阅我的线程文章——尽管这篇文章是在BackgroundWorker出现之前写的,恐怕我还没有在这方面更新它。BackgroundWorker只是稍微简化了一下回调。

你需要看看Backgroundworker的例子: http://msdn.microsoft.com/en-us/library/system.componentmodel.backgroundworker.aspx 特别是它如何与UI层交互。根据你的帖子,这似乎回答了你的问题。

UI跨线程问题最干净(也是最合适)的解决方案是使用SynchronizationContext,请参见多线程应用程序中同步对UI的调用的文章,它很好地解释了这一点。

我知道现在太晚了。然而,即使在今天,如果你有问题访问跨线程控件?这是迄今为止最短的答案:P

Invoke(new Action(() =>
                {
                    label1.Text = "WooHoo!!!";
                }));

这就是我如何从线程访问任何表单控件。

例如,从UI线程的控件中获取文本:

Private Delegate Function GetControlTextInvoker(ByVal ctl As Control) As String

Private Function GetControlText(ByVal ctl As Control) As String
    Dim text As String

    If ctl.InvokeRequired Then
        text = CStr(ctl.Invoke(
            New GetControlTextInvoker(AddressOf GetControlText), ctl))
    Else
        text = ctl.Text
    End If

    Return text
End Function