Непоследовательная обработка вызовов пользовательского интерфейса из не-пользовательского потока для TabControl - PullRequest
0 голосов
/ 17 октября 2018

Это не дубликат.Это специфическое поведение WinForms TabControl не было исследовано в StackOverflow.

Посмотрите на приведенный ниже пример:

У меня есть TabControl в .NET 4.0 с двумя вкладками.На каждой вкладке размещена метка .Когда я нажимаю кнопку , я запускаю BackgroundWorker , который теперь работает в потоке без пользовательского интерфейса.Если я пытаюсь изменить Label из tabPage1, я получаю InvalidOperationException из-за вызова между потоками.Но та вторая строка, которая изменяет Label на tabPage2, работает отлично - без исключений.

Simple test form to demonstrate the problem

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.В обоих случаях мы, по-видимому, модифицируем пользовательский интерфейс из не-пользовательского потока.

1 Ответ

0 голосов
/ 17 октября 2018

Это установщик свойства Control.Text:

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, который вызывается для получения экземпляра HandleRef для вызова нативного метода.

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

    // further code omitted
}

Свойство Handle будет доступно только тогда, когда IsHandleCreated равно true.В вашем случае вторая метка находится на элементе вкладки, дочерние элементы которого еще не отображаются, поэтому IsHandleCreated для второй метки равно false.Это означает, что свойство Handle второй метки не доступно в потоке BackgroundWorker.Вместо этого текстовое значение просто кэшируется в поле text внутри элемента управления.Поэтому - не исключение.

Когда вы активируете второй элемент вкладки, создается дескриптор Label, и код .NET Framework берет кэшированное текстовое значение из поля text и применяет его кэтикетка.Это происходит в потоке пользовательского интерфейса, так что опять - никаких исключений.

Вы можете сначала переключиться на второй элемент вкладки и , а затем нажать вашу кнопку.Вы увидите исключение.Это связано с тем, что в этом случае дескриптор для второй метки будет уже создан.

Как общее примечание - никогда не следует обращаться к элементам пользовательского интерфейса из рабочих потоков, независимо от того, соблюдаете ли вы этиисключения или нет.Для всего взаимодействия с элементами пользовательского интерфейса из других потоков используйте синхронизацию: Control.Invoke или Control.BeginInvoke, SynchronizationContext, TaskScheduler и т. Д.

...