来自 TabControl 的非 UI 线程的 UI 调用处理不一致

Inconsistent handling of UI calls from non-UI thread for TabControl

这不是重复的。 Whosebug 上尚未探索 WinForms TabControl 的这种特殊行为。

查看下面的示例:

我在 .NET 4.0 中有一个带有两个选项卡的 TabControl。每个选项卡中都有一个 Label。当我单击 Button 时,我启动了 BackgroundWorker,它现在在非 UI 线程上运行。如果我尝试从 tabPage1 更改 Label,由于跨线程调用,我会收到 InvalidOperationException。但是修改 tabPage2 上的 Label 的第二行运行得非常好——没有例外。

public Form1()
{
    InitializeComponent();
}

private void button1_Click(object sender, EventArgs e)
{
    BackgroundWorker bgw = new BackgroundWorker();
    bgw.DoWork += Bgw_DoWork;
    bgw.RunWorkerAsync();
}

private void Bgw_DoWork(object sender, DoWorkEventArgs e)
{
    try
    {
        label1.Text = "Testing tabPage1"; // This is sitting on tabPage1 - THROWS CROSS THREAD OPERATION
        label2.Text = "Testing tabPage2"; // This is sitting on tabPage2 - RUNS FINE
    }
    catch (Exception ex)
    {
        throw ex;
    }
}

为什么这在 tabPage2 上允许但在 tabPage1 上不允许。在这两种情况下,我们似乎都在从非 UI 线程修改 UI。

This isControl.Textsetter属性:

set
{
    // some code omitted

    this.WindowText = value;
    this.OnTextChanged(EventArgs.Empty);

    // some code omitted
}

它只是转发到 Control.WindowText 属性。让我们检查一下:

set 
{
    if (value == null) value = "";
    if (!WindowText.Equals(value)) {
        if (IsHandleCreated) {
            UnsafeNativeMethods.SetWindowText(new HandleRef(window, Handle), value);
        }
        else {
            if (value.Length == 0) {
                text = null;
            }
            else {
                text = value;
            }
        }
    }
}

InvalidOperationException来源于Handle属性getter,调用获取HandleRef实例,用于native方法调用

get {
    if (checkForIllegalCrossThreadCalls &&
        !inCrossThreadSafeCall &&
        InvokeRequired) {
        throw new InvalidOperationException(SR.GetString(SR.IllegalCrossThreadCall, Name));
    }

    // further code omitted
}

Handle 属性 只有在 IsHandleCreatedtrue 时才会被访问。在您的情况下,第二个标签位于其子项尚未显示的选项卡项上,因此第二个标签的 IsHandleCreatedfalse。这意味着,第二个标签的 Handle 属性 不会在 BackgroundWorker 的线程上访问。相反,文本值只是缓存在控件内的 text 字段中。因此——也不例外。

当您激活第二个选项卡项时,将创建 Label 的句柄,并且 .NET Framework 代码从 text 字段中获取缓存的文本值并将其应用于标签.这发生在 UI 线程上,所以再次 - 那里没有例外。

您可以切换到第二个选项卡项,然后然后按下您的按钮。您将观察到异常。这是因为在这种情况下已经创建了第二个标签的句柄。

作为一般注意事项 - 您应该 永远不会 从工作线程访问 UI 元素,无论您是否观察到这些异常。对于所有与来自其他线程的UI元素的交互,使用同步:Control.InvokeControl.BeginInvokeSynchronizationContextTaskScheduler