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

По некоторым причинам этот безопасно выглядящий метод повышает классическое исключение.

Операция перекрестного потока, не допустимая: Управляйте 'statusLabel', к которому получают доступ от потока кроме потока, на котором он был создан.

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

У кого-либо была подобная проблема?

    private void SetProgressBarValue(int progressPercentage)
    {
        Action setValue = () => 
        {
            var value = progressPercentage;
            if (progressPercentage < 0)
                value = 0;
            else if (progressPercentage > 100)
                value = 100;
            statusProgressBar.Value = value;
            statusLabel.Text = string.Format("{0}%", value);
        };
        if (InvokeRequired)
            Invoke(setValue);
        else
            setValue();
    }

Вот отслеживание стека:

at System.Windows.Forms.Control.get_Handle()
at System.Windows.Forms.Control.SetBoundsCore(Int32 x, Int32 y, Int32 width, Int32 height, BoundsSpecified specified)
at System.Windows.Forms.ToolStrip.SetBoundsCore(Int32 x, Int32 y, Int32 width, Int32 height, BoundsSpecified specified)
at System.Windows.Forms.ToolStrip.System.Windows.Forms.Layout.IArrangedElement.SetBounds(Rectangle bounds, BoundsSpecified specified)
at System.Windows.Forms.Layout.DefaultLayout.xLayoutDockedControl(IArrangedElement element, Rectangle newElementBounds, Boolean measureOnly, ref Size preferredSize, ref Rectangle remainingBounds)
at System.Windows.Forms.Layout.DefaultLayout.LayoutDockedControls(IArrangedElement container, Boolean measureOnly)
at System.Windows.Forms.Layout.DefaultLayout.xLayout(IArrangedElement container, Boolean measureOnly, ref Size preferredSize)
at System.Windows.Forms.Layout.DefaultLayout.LayoutCore(IArrangedElement container, LayoutEventArgs args)
at System.Windows.Forms.Layout.LayoutEngine.Layout(Object container, LayoutEventArgs layoutEventArgs)
at System.Windows.Forms.Control.OnLayout(LayoutEventArgs levent)
at System.Windows.Forms.ScrollableControl.OnLayout(LayoutEventArgs levent)
at System.Windows.Forms.Form.OnLayout(LayoutEventArgs levent)
at System.Windows.Forms.Control.PerformLayout(LayoutEventArgs args)
at System.Windows.Forms.Control.System.Windows.Forms.Layout.IArrangedElement.PerformLayout(IArrangedElement affectedElement, String affectedProperty)
at System.Windows.Forms.Layout.LayoutTransaction.DoLayout(IArrangedElement elementToLayout, IArrangedElement elementCausingLayout, String property)
at System.Windows.Forms.Control.PerformLayout(LayoutEventArgs args)
at System.Windows.Forms.Control.System.Windows.Forms.Layout.IArrangedElement.PerformLayout(IArrangedElement affectedElement, String affectedProperty)
at System.Windows.Forms.Layout.LayoutTransaction.DoLayout(IArrangedElement elementToLayout, IArrangedElement elementCausingLayout, String property)
at System.Windows.Forms.ToolStripItem.InvalidateItemLayout(String affectedProperty, Boolean invalidatePainting)
at System.Windows.Forms.ToolStripItem.OnTextChanged(EventArgs e)
at System.Windows.Forms.ToolStripItem.set_Text(String value)
at App.Image.Replace.ReplacementImageProcessForm.<>c__DisplayClass8.b__7() in ReplacementImageProcessForm.cs: line 114
at App.Image.Replace.ReplacementImageProcessForm.SetProgressBarValue(Int32 progressPercentage) in ReplacementImageProcessForm.cs: line 119
at App.Image.Replace.ReplacementImageProcessForm.replacer_BeginReplace(Object sender, EventArgs e) in ReplacementImageProcessForm.cs: line 76
at App.Image.Replace.DocumentReplacer.OnBeginReplace() in IDocumentReplacer.cs: line 72
at App.Image.Replace.DocumentReplacer.Replace(Int32 documentId, String replacementDocumentPath) in IDocumentReplacer.cs: line 108 

Я все еще получаю ту же ошибку после реализации предложения John Saunders:

at System.Windows.Forms.Control.get_Handle()
at System.Windows.Forms.Control.SetBoundsCore(Int32 x, Int32 y, Int32 width, Int32 height, BoundsSpecified specified)
at System.Windows.Forms.ToolStrip.SetBoundsCore(Int32 x, Int32 y, Int32 width, Int32 height, BoundsSpecified specified)
at System.Windows.Forms.ToolStrip.System.Windows.Forms.Layout.IArrangedElement.SetBounds(Rectangle bounds, BoundsSpecified specified)
at System.Windows.Forms.Layout.DefaultLayout.xLayoutDockedControl(IArrangedElement element, Rectangle newElementBounds, Boolean measureOnly, ref Size preferredSize, ref Rectangle remainingBounds)
at System.Windows.Forms.Layout.DefaultLayout.LayoutDockedControls(IArrangedElement container, Boolean measureOnly)
at System.Windows.Forms.Layout.DefaultLayout.xLayout(IArrangedElement container, Boolean measureOnly, ref Size preferredSize)
at System.Windows.Forms.Layout.DefaultLayout.LayoutCore(IArrangedElement container, LayoutEventArgs args)
at System.Windows.Forms.Layout.LayoutEngine.Layout(Object container, LayoutEventArgs layoutEventArgs)
at System.Windows.Forms.Control.OnLayout(LayoutEventArgs levent)
at System.Windows.Forms.ScrollableControl.OnLayout(LayoutEventArgs levent)
at System.Windows.Forms.Form.OnLayout(LayoutEventArgs levent)
at System.Windows.Forms.Control.PerformLayout(LayoutEventArgs args)
at System.Windows.Forms.Control.System.Windows.Forms.Layout.IArrangedElement.PerformLayout(IArrangedElement affectedElement, String affectedProperty)
at System.Windows.Forms.Layout.LayoutTransaction.DoLayout(IArrangedElement elementToLayout, IArrangedElement elementCausingLayout, String property)
at System.Windows.Forms.Control.PerformLayout(LayoutEventArgs args)
at System.Windows.Forms.Control.System.Windows.Forms.Layout.IArrangedElement.PerformLayout(IArrangedElement affectedElement, String affectedProperty)
at System.Windows.Forms.Layout.LayoutTransaction.DoLayout(IArrangedElement elementToLayout, IArrangedElement elementCausingLayout, String property)
at System.Windows.Forms.ToolStripItem.InvalidateItemLayout(String affectedProperty, Boolean invalidatePainting)
at System.Windows.Forms.ToolStripItem.OnTextChanged(EventArgs e)
at System.Windows.Forms.ToolStripItem.set_Text(String value)
at App.Image.Replace.ReplacementImageProcessForm.<>c__DisplayClassa.<>c__DisplayClassc.b__9() in ReplacementImageProcessForm.cs: line 147
at App.Image.Replace.ReplacementImageProcessForm.InvokeIfNecessary(Control control, Action setValue) in ReplacementImageProcessForm.cs: line 156
at App.Image.Replace.ReplacementImageProcessForm.<>c__DisplayClassa.b__7() in ReplacementImageProcessForm.cs: line 145
at App.Image.Replace.ReplacementImageProcessForm.InvokeIfNecessary(Control control, Action setValue) in ReplacementImageProcessForm.cs: line 156
at App.Image.Replace.ReplacementImageProcessForm.SetProgressBarValue(Int32 progressPercentage) in ReplacementImageProcessForm.cs: line 132
at App.Image.Replace.ReplacementImageProcessForm.replacer_BeginReplace(Object sender, EventArgs e) in ReplacementImageProcessForm.cs: line 74
at App.Image.Replace.DocumentReplacer.OnBeginReplace() in IDocumentReplacer.cs: line 87
at App.Image.Replace.DocumentReplacer.Replace(Int32 documentId, String replacementDocumentPath) in IDocumentReplacer.cs: line 123 

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

5 ответов

Попробуйте переопределить из 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
поделиться
Другие вопросы по тегам:

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