WinformsmultithreadingUI操作的随机跨线程操作exception

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

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

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

有没有人有类似的问题?

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

[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__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 

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

 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 

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

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

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

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

祝你的heisenbug好运!

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

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

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

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

我在调试器中运行时让.NET Framework错误地引发了这个exception。 调试器有一些特殊之处导致控件的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; } } } 

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

请注意, 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。 不确定这是否是未来版本中的问题,但我认为无论如何我都会为后代写这个答案。

尝试使用BeginInvoke()而不是Invoke()。

总而言之,您有一个私有实例方法SetProgressBarValue 。 它是控件或表单的实例方法。 此控件或表单包含其他控件, statusProgressBarstatusLabel 。 所以,你正在做以下,实际上:

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

我怀疑你可能会以某种方式导致在不同的线程上创建这三个控件的窗口句柄。 我认为这个代码可以工作,即使所有三个窗口句柄都是在不同的线程上创建的。

Nicholas Piasecki的回答为我解释了这个问题。 我经常遇到这个奇怪的错误,我很欣赏它为什么会发生这样的信息(一个控件的句柄可能在第一次调用this.InvokeRequired从后台线程中被加载时很懒)

我动态创建了很多UI(在UI线程上)并绑定到演示者(MVP模式),这通常在UI首次显示之前启动工作线程。 UI当然有更新,并且使用this.InvokeRequired / BeginInvoke将这些更新编组到UI线程上,但是此时我假设可以在工作线程上创建句柄。

对我来说,当用户关闭应用程序时,MainForm dispose方法中发生了跨线程违规。 作为一种解决方法,我递归地遍历子控件,在主窗体关闭时处理它们和它们的子节点。 然后减少我处理的控件列表,我最终将其缩小到导致访问冲突的单个控件。 不幸的是我无法直接解决问题(在有问题的控件上调用CreateControl()或CreateHandle()没有解决问题),但我能够通过在app上留下我的递归处理来解决问题关掉。

为什么这个工作和内置的Form.Dispose()方法不是我不知道。

无论如何我将来在工作线程附近创建控件时会更加小心,现在我知道Handles是懒惰的,所以谢谢!

我有一个类似的问题,我在实例化一个表单,该表单启动后台线程来获取一些数据并在调用Show()之前自行更新。 在这个动作的第二个实例(总是),我会在Show()上得到一个跨线程exception。 在阅读了Nicholas的优秀答案之后,我在表单的构造函数中添加了一个断点并检查了IsHandleCreated,它返回false。 然后我输入这段代码:

  if (!this.IsHandleCreated) this.CreateHandle(); 

从那以后我没有看到这个问题。 我知道msdn建议调用CreateControl而不是CreateHandle,但是,CreateControl没有为我剪切它。

有没有人知道是否有直接调用CreateHandle的副作用?