简体   繁体   中英

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

This is not a duplicate. This particular behavior of a WinForms TabControl has not been explored on StackOverflow.

Check out the example below:

I have a TabControl in .NET 4.0 with two tabs. Each tab has a Label placed within it. When I click the Button , I launch a BackgroundWorker which is now operating on a non-UI thread. If I try to change the Label from tabPage1, I get an InvalidOperationException because of the cross-thread call. But that second line which modifies the Label on tabPage2 runs perfectly fine - no exceptions.

简单的测试表格来演示问题

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

Why is this allowed on tabPage2 but disallowed on tabPage1. In both instances, we are seemingly modifying the UI from a non-UI thread.

This is the setter of the Control.Text property:

set
{
    // some code omitted

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

    // some code omitted
}

It just forwards to the Control.WindowText property. Let's inspect that one:

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

The InvalidOperationException originates from the Handle property getter, which is called to obtain a HandleRef instance for the native method call.

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

    // further code omitted
}

The Handle property will be only accessed when IsHandleCreated is true . In your case, the second label is located on a tab item whose children are not displayed yet, so IsHandleCreated for the second label is false . That means, the Handle property of the second label is not accessed on the BackgroundWorker 's thread. Instead, the text value is just cached in a text field inside the control. Therefore - no exception.

When you activate the second tab item, the Label 's handle is created, and the .NET Framework code takes the cached text value from the text field and applies it to the label. This occurs on the UI thread, so again - no exceptions there.

You can first switch to the second tab item and then press your button. You will observe the exception. That is because the handle for the second label will be already created in this case.

As a general note - you should never access the UI elements from worker threads, regardless whether you observe those exceptions or not. For all interaction with the UI elements from other threads, use synchronization: Control.Invoke or Control.BeginInvoke , SynchronizationContext , TaskScheduler etc.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM