【问题标题】:Can exceptions be handled behind a delegate-driven "Control.Invoke" call?可以在委托驱动的“Control.Invoke”调用之后处理异常吗?
【发布时间】:2012-11-13 22:14:53
【问题描述】:

我有一个在套接字处理线程中运行的事件处理程序,它使用Invoke 来更新 UI 状态。

我在调用堆栈的某个位置有一个失控的FormatException,我试图抓住它来分析它,但我发现我无法让调试器在 UI 线程中中断 - 异常似乎无论我做什么,都会冒泡到调用线程。

Private Delegate Sub newDataDelegate(ByVal data As String)
Private Sub onNewData(ByVal data As String) Handles _server.clientHasData
   If Me.InvokeRequired Then
      Me.Invoke(New newDataDelegate(AddressOf onNewData), data)
      Exit Sub
   End If

   Try
      updateGuiWith(data)
   Catch ex As FormatException
      System.Diagnostics.Debugger.Break()
   End Try
End Sub

堆栈跟踪:

at System.Windows.Forms.Control.MarshaledInvoke(Control caller, Delegate method, Object[] args, Boolean synchronous)
at System.Windows.Forms.Control.Invoke(Delegate method, Object[] args)
at <X>.MainForm.onNewData(String data)
   in <X>.vb:line 377
at <X>.Server.onProbeData(String data)
   in <X>:line 104

&lt;X&gt; = 已编辑)

结果是调试器分解调用堆栈(在套接字线程中调用onNewData 的代码中)并且堆栈跟踪在调用站点结束。我无法找出导致异常的原因。 (更糟糕的是,该调用大部分时间都适用于同一个参数,因此如果没有调试器的帮助,我无法预测和追踪它。)

在我进一步提取一个孤立的测试用例之前,这是委托驱动调用背后引发的异常的预期行为吗?

【问题讨论】:

  • 注意:这是 Visual Basic 2008 Express Edition,它使用 .NET 3.5。

标签: .net vb.net exception-handling .net-3.5 invoke


【解决方案1】:

这是委托驱动调用背后引发的异常的预期行为吗?

不,那是因为您实际上并没有使用委托的 Begin/Invoke() 方法。您正在使用 Control.Invoke(),这是 Control 类的一种方法,它仅将委托作为参数。它的行为与委托的 Begin/Invoke 方法非常不同,命名选择有点不幸。主要区别是:

  • 调用 BeginInvoke 时无需调用 EndInvoke
  • Invoke 调用将异常封送回,BeginInvoke 调用不会
  • 现在让您感到痛苦的行为是,为 Invoke 封送回的异常是最内部的 InnerException。您只能看到引发事故的异常。如果它被另一个异常捕获并重新引发,并且在其 InnerException 成员中传递了原始异常,那么您根本看不到这些异常。

是的,当您查看堆栈跟踪时,这会使您很难弄清楚您是如何从 A 点到达 B 点的。非常不幸的行为,设计选择并不明显。除了在调用的代码中捕获和处理异常之外,没有简单的解决方法。

或者支持 BeginInvoke,通常是您想要使用的方法,因为您不希望线程因 UI 更新延迟而陷入困境。异常将在具有完整堆栈跟踪诊断的 UI 线程上引发。

【讨论】:

  • 但我正在抓住它。或者至少我正在尝试?!我会考虑切换到BeginInvoke(我不需要EndInvoke?这是否意味着我目前应该使用它?)但我仍然很好奇发生了什么这里。目前我的理论是报告的异常类型在某种程度上是不正确的,并等待问题再次出现,并使用更通用的Catch ex As Exception 来证明这一点。
  • 好吧,正如我所解释的,您只会捕获最内部的异常。请注意,跨线程捕获和处理异常的智慧有限。如果没有编写调用方法,您将无法正确恢复程序状态。
  • 知道了。最后一件事 - 你能否证实或否认我对 EndInvoke 的怀疑(来自我的第一条评论)?
  • 这是我的回答,第一个项目符号。
  • 我想我的后续问题真的是“我应该目前Invoke一起做EndInvoke吗?”,因为当你说它不需要@ 987654327@ 你似乎说这是BeginInvokeInvoke 之间的区别。
【解决方案2】:

看完这篇,

http://charlieflowers.wordpress.com/2005/04/26/controlinvoke-and-exception-propogation-short-form/

我要说是的,这是为了冒泡异常。如果我正确理解了这篇文章,异常就会被吞没,异常信息会返回到重新抛出它的 Invoke 函数。但是,如果你使用BeginInvoke进行异步调用,异常不会通过BeginInvoke调用冒泡出来。

我没有使用过 VS Express,但通常在 Debug>Exceptions 下,选中“Common Language Runtime Exceptions”旁边的“Thrown”框。然后调试器应该在第一个异常发生的地方停止。

虽然情况可能并非如此,但您可以检查代码中的属性,例如 System.Diagnostics.DebuggerStepThrough,这可能会导致调试器跳过引发异常的代码。

【讨论】:

  • check the 'Thrown' box next to 'Common Language Runtime Exceptions' 这对于一个使用和处理大量异常的应用程序来说是不可行的,唉。
【解决方案3】:

Catch ex As Exception 替换Catch ex As FormatException 给了我正在寻找的调用线程中断,而有问题的实际 异常结果是InvalidCastException。内部异常是FormatException,这就是它没有被捕获的原因。

我对 .NET 中异常的理解仍然相当初级(首先是 C++ 开发人员),因此这种不同级别的异常包装器的概念是相当陌生的。但是,最终,这种机制正如 Hans 在他的回答中所解释的那样。

从长远来看,切换到BeginInvoke 将、it seems 完全避免这种头痛。不过现在,我有我的堆栈跟踪,我可以解决实际问题。

可以在委托驱动的“Control.Invoke”调用之后处理异常吗?可以

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-10-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-01-01
    • 2019-09-28
    • 1970-01-01
    相关资源
    最近更新 更多