Случайное операционное исключение перекрестного потока для Winforms многопоточная операция UI

Один путь состоит в том, чтобы заставить классы использовать помехи инициализаторы... Я не думаю, что они наследованы (это не будет работать, если они будут):

public class Dog extends Animal{

static
{
   Animal a = new Dog();
   //add a to the List
}

Это требует, чтобы Вы добавили этот код ко всем включенным классам. Но это старается не иметь большой ужасный цикл где-нибудь, тестируя каждый класс, ищущий детей Животного.

6
задан duplode 17 August 2019 в 18:29
поделиться

6 ответов

Попробуйте переопределить из Label, чтобы создать новый класс метки. Переопределите свойство текста и поместите на нем точку останова. Измените метку подозреваемого, чтобы вместо этого использовать ваш новый класс отладки. Я также обнаружил, что этот метод отлично подходит для выполнения базового профилирования и / или отладки в ваших формах, если вам нужно выяснить, где и как что-то обновляется.

public class MyLabel : Label
{
    public override string Text
    {
        get
        {
            return base.Text;
        }
        set
        {
            base.Text = value;
        }
    }
}

Используйте свой код и попытайтесь поймать heisenbug, вы иметь возможность прерывать каждый доступ к метке, поэтому, если он исходит из трассировки стека, чего вы не ожидаете и / или не из пути вашего кода вызова, у вас есть ошибка?

5
ответ дан 8 December 2019 в 03:01
поделиться

Это может иметь или не иметь непосредственное отношение к вашей ситуации, но может дать ключ к разгадке. Одна важная ненадежная абстракция, которую следует помнить о Windows Forms, заключается в том, что дескриптор окна Handle не создается до тех пор, пока он не потребуется. Свойство Handle создает настоящую Windows hwnd только при первом вызове get , чего не происходит, когда объект, производный от Control . (как Windows Form) создается экземпляр. (Объект, производный от Control , в конце концов, является просто классом .NET.) Другими словами, это свойство, которое лениво инициализируется.

Я уже обжигался этим раньше: Проблема в моем случае я правильно создал экземпляр формы в потоке пользовательского интерфейса, но я не использовал Show () до тех пор, пока данные не вернулись из вызова веб-службы, которая работала в рабочем потоке. Сценарий заключался в том, что никто не запрашивал у дескриптор формы , до тех пор, пока к нему не обращались в рамках проверки InvokeRequired , которая произошла, когда рабочий поток завершил свою работу. Итак, мой фоновый рабочий поток спросил форму: нужен ли мне InvokeRequired ? Реализация формы InvokeRequired затем сказала: хорошо, позвольте мне взглянуть на мой дескриптор , чтобы я мог увидеть, в каком потоке был создан мой внутренний hwnd , а затем Я посмотрю, будете ли вы в той же теме. И тогда реализация Handle сказала: ну, я еще не существую, так что позвольте мне прямо сейчас создать для себя hwnd . (Вы видите, к чему это идет. Помните, мы все еще находимся в фоновом потоке, невинно обращаясь к свойству InvokeRequired .)

Это привело к Handle (и лежащему в его основе hwnd ) создается в потоке worker , который мне не принадлежит, и в котором не настроен насос сообщений для обработки сообщений Windows. Результат: мое приложение заблокировалось, когда другие вызовы были сделаны в ранее скрытое окно, поскольку эти вызовы были сделаны в основном потоке пользовательского интерфейса, который разумно предполагал, что все другие объекты, производные от Control , также были созданы в эта ветка. В других случаях это могло вызвать странные исключения между потоками, потому что InvokeRequired неожиданно возвращал false, поскольку дескриптор был создан в потоке, который отличается от потока, в котором была создана форма.

Но только иногда. У меня была функция, с помощью которой пользователь мог вызвать саму форму Show () через меню, а затем она отключила бы себя, пока заполнялась данными в фоновом режиме (показывая анимацию пульсации). Если бы они сделали это первыми, то все было бы в порядке: дескриптор был создан в потоке пользовательского интерфейса (в обработчике событий пункта меню), и поэтому InvokeRequired вел себя должным образом, когда рабочий поток завершил получение данных из веб-службы. Но если мой фоновый поток, который периодически запускался (это был планировщик событий, похожий на диалоговое окно напоминания о событии в Outlook), обращался к веб-службе и пытался открыть форму,

19
ответ дан 8 December 2019 в 03:01
поделиться

Вы видите эту ошибку при запуске приложения из отладчика или при автономном запуске?

У меня .NET Framework некорректно вызывал это исключение при работе в отладчике. Есть что-то особенное в отладчике, из-за которого флаг InvokeRequired элемента управления неправильно возвращает значение true, даже если код выполняется внутри основного потока пользовательского интерфейса. Это очень важно для меня, и это всегда происходит после того, как наш контроль был удален. Наша трассировка стека выглядит так:

System.InvalidOperationException: Cross-thread operation not valid: Control 'cboMyDropDown' accessed from a thread other than the thread it was created on.
   at System.Windows.Forms.Control.get_Handle()
   at System.Windows.Forms.TextBox.ResetAutoComplete(Boolean force)
   at System.Windows.Forms.TextBox.Dispose(Boolean disposing)
   at OurApp.Controls.OurDropDownControl.Dispose(Boolean disposing)
   at System.ComponentModel.Component.Dispose()

Вы можете увидеть источник ошибки в исходном коде .NET Framework:

public class Control //...
{ //...
        public IntPtr Handle
        {
            get
            {
                if ((checkForIllegalCrossThreadCalls && !inCrossThreadSafeCall) && this.InvokeRequired)
                {
                    throw new InvalidOperationException(System.Windows.Forms.SR.GetString("IllegalCrossThreadCall", new object[] { this.Name }));
                }
                if (!this.IsHandleCreated)
                {
                    this.CreateHandle();
                }
                return this.HandleInternal;
            }
        }
}

При запуске в отладчике checkForIllegalCrossThreadCalls истинно, inCrossThreadSafeCall ложно, а this.InvokeRequired истинно, несмотря на то, что он находится в потоке пользовательского интерфейса!

Обратите внимание, что Control.InvokeRequired делает следующее:

int windowThreadProcessId = System.Windows.Forms.SafeNativeMethods.GetWindowThreadProcessId(ref2, out num);
int currentThreadId = System.Windows.Forms.SafeNativeMethods.GetCurrentThreadId();
return (windowThreadProcessId != currentThreadId);

Также обратите внимание, что наше приложение использует .NET Framework 2.0. Не уверен, что это проблема в будущих версиях, но я подумал, что все равно напишу этот ответ для потомков.

3
ответ дан 8 December 2019 в 03:01
поделиться

Подводя итог, у вас есть метод частного экземпляра SetProgressBarValue . Это метод экземпляра элемента управления или формы. Этот элемент управления или форма содержит другие элементы управления, statusProgressBar и statusLabel . Итак, по сути, вы делаете следующее:

if (this.InvokeRequired)
{
    Invoke(
        (Action) delegate
                 {
                     statusProgressBar.Value = 0;                 // TOUCH
                     statusLabel.Text = string.Format("{0}%", 0); // TOUCH
                 });
}
else
{
    statusProgressBar.Value = 0;                                 // TOUCH
    statusLabel.Text = string.Format("{0}%", 0);                 // TOUCH
}

Этот код предполагает, что если this.InvokeRequired == false, то это подразумевает statusProgressBar.InvokeRequired == false и statusLabel. InvokeRequired == false. Я предполагаю, что вы обнаружили ситуацию, когда это не так.

Попробуйте изменить код на:

private void SetProgressBarValue(int progressPercentage)
{
    InvokeIfNecessary(
        this, () =>
              {
                  var value = progressPercentage;
                  if (progressPercentage < 0)
                  {
                      value = 0;
                  }
                  else if (progressPercentage > 100)
                  {
                      value = 100;
                  }

                  InvokeIfNecessary(
                      statusProgressBar.GetCurrentParent(),
                      () => statusProgressBar.Value = value);

                  InvokeIfNecessary(
                      statusLabel.GetCurrentParent(),
                      () =>
                      statusLabel.Text = string.Format("{0}%", value));
              });
}

private static void InvokeIfNecessary(Control control, Action setValue)
{
    if (control.InvokeRequired)
    {
        control.Invoke(setValue);
    }
    else
    {
        setValue();
    }
}

Я подозреваю, что вы могли каким-то образом вызвать дескрипторы окон этих трех элементов управления, которые были созданы в разных потоках. Я думаю, что этот код будет работать, даже если все три дескриптора окна были созданы в разных потоках.

1
ответ дан 8 December 2019 в 03:01
поделиться

Попробуйте использовать BeginInvoke () вместо Invoke ().

2
ответ дан 8 December 2019 в 03:01
поделиться

Ответ Николаса Пьясеки пролил для меня много света на эту проблему. У меня часто возникала эта странная ошибка, и я благодарен за информацию о причинах ее возникновения (Handle для элемента управления, вероятно, лениво загружается при первом вызове this.InvokeRequired из фонового потока)

Я создаю много пользовательского интерфейса динамически (в потоке UI) и связываю с презентерами (паттерн MVP), которые часто запускают рабочие потоки до первого показа пользовательского интерфейса. Разумеется, UI обновляется, и эти обновления передаются в поток UI с помощью this.InvokeRequired/BeginInvoke, однако в этот момент я предполагаю, что хэндл может быть создан в рабочем потоке.

Для меня нарушение межпотокового взаимодействия происходит в методе MainForm dispose, когда пользователь закрывает приложение. В качестве обходного пути я рекурсивно перебираю дочерние элементы управления, избавляясь от них и их дочерних элементов при закрытии главной формы. Затем я сократил список элементов управления, которые я утилизировал, и в конце концов свел его к одному элементу управления, который вызывал нарушение доступа. К сожалению, я не смог решить проблему напрямую (вызов CreateControl() или CreateHandle() для данного элемента управления не привел к решению проблемы), но я смог обойти проблему, оставив рекурсивное удаление на месте при закрытии приложения.

Почему это работает, а встроенный метод Form.Dispose() - нет, я не знаю.

В любом случае, в будущем я буду более осторожен при создании элементов управления рядом с рабочими потоками, теперь я знаю, что Handles лениво загружаются, так что спасибо!

1
ответ дан 8 December 2019 в 03:01
поделиться
Другие вопросы по тегам:

Похожие вопросы: