Winforms多线程UI操作的随机跨线程操作异常

dan*_*die 6 .net c# multithreading

出于某种原因,这种安全的方法引发了一个典型的例外.

跨线程操作无效:控制'statusLabel'从其创建的线程以外的线程访问.

显然,当需要调用时,此代码应通过Invoke调用匿名方法.但每隔一段时间就会发生异常.

有人有过类似的问题吗?

    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();
    }
Run Code Online (Sandbox Code Playgroud)

[UPDATE2]
在实施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__DisplayClass8.<SetProgressBarValue>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 
Run Code Online (Sandbox Code Playgroud)

[更新]这是问题完整性的堆栈跟踪.

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.<SetProgressBarValue>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.<SetProgressBarValue>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 
Run Code Online (Sandbox Code Playgroud)

Nic*_*cki 19

这可能与您的情况直接相关,也可能不直接相关,但可以提供线索.记住Windows窗体的一个重要漏洞抽象Handle是在实际需要之前不会创建窗口.该Handle属性仅hwnd在第一次get调用时创建真正的Windows ,当Control实例化一个非衍生对象(如Windows窗体)时不会发生这种情况.(Control毕竟,-derived对象只是一个.NET类.)换句话说,它是一个懒惰地初始化的属性.

我以前被这个烧过:我的问题是我在UI线程上正确实例化了一个表单,但是Show()直到数据从一个已经运行的Web服务调用返回时我才知道它.工人线程.情节是,在工作线程完成其工作之前,作为检查的一部分访问该表单之前,没有人曾要求该表单.所以我的后台工作者线程问了表格:我需要吗?然后表单的实现说:好吧,让我看看我的所以我可以看到我的内部创建了什么线程,然后我会看到你是否在同一个线程上.然后实施说:我还不存在,所以让我现在为自己创造一个.(你会看到它的发展方向.请记住,我们仍然在后台线程,无辜地访问该物业.)HandleInvokeRequiredInvokeRequiredInvokeRequiredHandlehwndHandlehwndInvokeRequired

这导致Handle(和它的底层hwnd)在工作线程上创建,我没有拥有它,并且没有设置消息泵来处理Windows消息.结果:当我对以前隐藏的窗口进行其他调用时,我的应用程序被锁定,因为这些调用是在主UI线程上进行的,这合理地假设所有其他Control来源的对象也已在此线程上创建.在其他情况下,这可能会导致奇怪的跨线程异常,因为它InvokeRequired会意外地返回false,因为它Handle是在与实例化表单的线程不同的线程上创建的.

但有时候只是.我有一些功能,用户可以Show()通过菜单使表单自身,然后它会在自己填充背景中的数据时自动禁用(显示一个悸动动画).如果他们首先这样做,那么一切都会好起来的:它Handle是在UI线程上创建的(在菜单项的事件处理程序中),并且InvokeRequired当工作线程完成从Web服务检索数据时,表现得如预期的那样.但是如果我定期运行的后台线程(它是一个事件调度程序,类似于Outlook中的Event Reminder对话框)访问Web服务并试图弹出表单,而用户还没有Show()它,那么工作线程的触摸InvokeRequired会引起上述的胃灼热诱发行为.

祝你的heisenbug好运!


Spe*_*nce 5

尝试从Label覆盖,以创建新的标签类.覆盖text属性并在其上放置断点.更改可疑标签以改为使用新的调试类.我还发现,如果您需要确定更新的位置和方式,这种技术非常适合对表单进行一些基本的分析和/或调试.

public class MyLabel : Label
{
    public override string Text
    {
        get
        {
            return base.Text;
        }
        set
        {
            base.Text = value;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

使用你的代码并尝试捕获heisenbug,你将能够在每次访问标签时中断,所以如果它来自你不期望和/或不是你的调用代码路径的堆栈跟踪,你有你的错误?


Pau*_*ams 3

从调试器启动应用程序或独立运行时是否看到此错误?

在调试器中运行时,.NET Framework 错误地引发了此异常。调试器有一些特殊之处,会导致控件的 InvokeRequired 标志错误地返回 true,即使代码在主 UI 线程内运行也是如此。这对我来说非常一致,而且总是在我们的控制权被处置之后发生。我们的堆栈跟踪如下所示:

System.InvalidOperationException:跨线程操作无效:从创建它的线程以外的线程访问控制“cboMyDropDown”。
   在 System.Windows.Forms.Control.get_Handle()
   在 System.Windows.Forms.TextBox.ResetAutoComplete(布尔力)
   在 System.Windows.Forms.TextBox.Dispose(布尔处置)
   在 OurApp.Controls.OurDropDownControl.Dispose(布尔处置)
   在 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;
            }
        }
}
Run Code Online (Sandbox Code Playgroud)

在调试器中运行时,checkForIllegalCrossThreadCalls为 true,inCrossThreadSafeCall为 false,this.InvokeRequired尽管在 UI 线程中但仍为 true!

请注意,Control.InvokeRequired最终会这样做:

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

另请注意,我们的应用程序使用 .NET Framework 2.0。不确定这是否是未来版本中的问题,但我想无论如何我都会为后代写下这个答案。