Background: I have a async call, that somewhere deep down throws an exception... In my real application, it is a plugin that is running async, so I would like to wrap the exception and decorate the exception with the plugin name that the exception originates from.
But .Net throws away the outer exception, so additional details are lost,
I have made an example windows forms application with two buttons, and setup a general exception handler
Application.ThreadException += new ThreadExceptionEventHandler(Application_ThreadException);
application.SetUnhandledExceptionMode(UnhandledExceptionMode.CatchException);
Then one button:
private async void buttonThrowAndWrapExceptionAsync_Click(object sender, EventArgs e)
{
_textBoxToSetTextOnGivenLastCall = textBox1;
try
{
await Task.Run(() =>
ThrowExceptionA());
}
catch (Exception a)
{
throw new Exception("B", a);
}
}
the other button:
private async void buttonThrowAndRethrowNewExceptionAsync_Click(object sender, EventArgs e)
{
_textBoxToSetTextOnGivenLastCall = textBox2;
try
{
await Task.Run(() =>
ThrowExceptionA());
}
catch (Exception)
{
throw new Exception("B");
}
}
When I
throw new Exception("B", a);
in the context, Exception "A" occurs in the Application_ThreadException callback with no trace of 'B'... B does never appear, this is not what I would expect.
When I
throw new Exception("B");
Exception "B" appears in Application_ThreadException, and Exception "A" does not appear, this is to me understandable
In my 'real' application, I am contemplating making a 'hack' and just adding "A".ToString() to my "B" exception, but I know it is pretty bad, and I am sure we have lots of logic expecting to be able to wrap exceptions
BTW... using a background worker with exception handling in OnComplete works in the same way as the 'async' code here...
The example can be found here: https://github.com/buildcomplete/dotnet-winforms-async-exceptionhandling
EDIT: After viewing the suggested Why does the inner exception reach the ThreadException handler and not the actual thrown exception? wich is simelar, and provides an answer to why, I am actually more interested in stopping this behaviour.
For now I have made an ugly hack... When Throwing my wrapper Exception, I wrap the wrapper B in a new Exception ProtectedFromASyncUnfoldException
throw new ProtectedFromASyncUnfoldException(new Exception("B", a));
This breaks the normal behaviour of InnerExceptions... and is not really what I wan't to do... any better sollutions?
Here's the Hack class:
public class ProtectedFromASyncUnfoldException : Exception
{
public Exception InternalException { get; }
public ProtectedFromASyncUnfoldException() { }
public ProtectedFromASyncUnfoldException(string message)
: base(message) { }
/// <summary>
/// Hack of exception.
/// Setting InternalException instead of innerException
/// to avoid strange behaviour of async exception handling in windows forms
/// that otherwise throws away all but the base exception
/// </summary>
/// <param name="message"></param>
/// <param name="innerException">transformed to InternalException</param>
public ProtectedFromASyncUnfoldException(string message, Exception innerException)
: base(message)
{
InternalException = innerException;
}
public ProtectedFromASyncUnfoldException(Exception innerException)
: this(innerException.Message, innerException) { }
protected ProtectedFromASyncUnfoldException(SerializationInfo info, StreamingContext context)
: base(info, context) { }
public override string ToString()
{
return base.ToString()
+ Environment.NewLine
+ Environment.NewLine
+ "---------------------"
+ InternalException?.ToString();
}
}