Lightness Races in Orbit
Lightness Races in Orbit

Reputation: 385194

Can exceptions be handled behind a delegate-driven "Control.Invoke" call?

I have an event handler that runs in a socket-handling thread, which uses Invoke to update UI state.

I have a runaway FormatException somewhere further up the call stack and I'm trying to catch it to analyse it, but I'm finding that I cannot get the debugger to break in the UI thread — the exception seems to be bubbling up to the invoking thread no matter what I do.

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

The stack trace:

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

(<X> = redacted)

The result is that the debugger breaks lower down the call stack (in code that called onNewData in the socket thread) and the stack trace ends at the invoke site. I cannot find out what's causing the exception. (Worse, the call works most of the time for the same argument, so it's not one I can predict and hunt down without the debugger's help.)

Before I go any further extracting an isolated testcase, is this expected behaviour for exceptions raised behind delegate-driven invocations?

Upvotes: 2

Views: 832

Answers (3)

Lightness Races in Orbit
Lightness Races in Orbit

Reputation: 385194

Replacing Catch ex As FormatException with Catch ex As Exception gave me the invoked-thread break I was looking for, and the actual exception in question turns out to be an InvalidCastException. The inner-exception is the FormatException, which is why it wasn't being caught.

My understanding of exceptions in .NET is still fairly rudimentary (being a C++ developer first and foremost), so this concept of varying levels of exception wrappers is rather alien. But, ultimately, this mechanism is as Hans explained in his answer.

Long-term, switching to BeginInvoke will, it seems, avoid this headache altogether. For now, though, I have my stack trace and I can solve the actual problem.

The answer to the question can exceptions be handled behind a delegate-driven "Control.Invoke" call? is yes.

Upvotes: 0

Hans Passant
Hans Passant

Reputation: 941585

is this expected behaviour for exceptions raised behind delegate-driven invocations?

No, and that's because you don't actually use the delegate's Begin/Invoke() methods. You are using Control.Invoke(), a method of the Control class that merely takes a delegate as an argument. It behaves very differently from a delegate's Begin/Invoke methods, the naming choice was a bit unfortunate. Major differences are:

  • There's no requirement to call EndInvoke when you call BeginInvoke
  • The Invoke call marshals the exception back, the BeginInvoke call does not
  • And the behavior that pains you right now, the exception that's marshaled back for Invoke is the most inner-most InnerException. You can only see the exception that started the mishap. If it was caught and re-raised by another exception and it passed the original exception in its InnerException member then you don't see those exceptions at all.

Yes, this can make it quite hard to figure out how you got from point A to B when you look at the stack trace. Very unfortunate behavior, the design choice isn't obvious. There is no simple workaround for it beyond having the exception caught and handled in the invoked code.

Or to favor BeginInvoke, in general the method you want to use because you don't want your thread to be bogged down by UI update delays. The exception will be raised on the UI thread with full stack trace diagnostics.

Upvotes: 1

Kratz
Kratz

Reputation: 4330

From reading this,

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

I'm going to say yes that this is meant to bubble up the exception. If I understand the article correctly, the exception gets swallowed and the exception information returned to the Invoke function where it is re-thrown. However, if you use BeginInvoke to do an asynchronous call, the exception will not be bubbled up through the BeginInvoke call.

I haven't used VS Express, but normally under Debug>Exceptions, check the 'Thrown' box next to 'Common Language Runtime Exceptions'. Then the debugger should stop where ever the first exception happens.

Although this probably isn't the case, you can check your code for attributes like System.Diagnostics.DebuggerStepThrough which might cause the debugger to skip the code that throws the exception.

Upvotes: 0

Related Questions