I need for exceptions thrown within a timed event to be bubbled up and handled outside of the event handler context. I had read that System.Threading.Timers
would be able to do that, provided that, as outlined in the answer to this question, the exception is caught in the callback method and a mechanism is used to re-throw it. Using this example as a guide, I created an event handler which throws and a method which should catch and re-throw the exception using an IProgress
object:
void ThrowerThreaded() {
var progress = new Progress<Exception>((ex) => {
throw ex;
});
Timer timer = new Timer(x => onTimerElapsedThrow2(progress), null, 10, -1);
Thread.Sleep(1000);
}
void onTimerElapsedThrow2(IProgress<Exception> progress) {
try {
throw new Exception();
} catch (Exception ex) {
progress.Report(ex);
}
}
I then wrote a unit test to see if the exception would bubble up:
[TestMethod]
public void TestThreadedTimerThrows() {
Assert.ThrowsException<Exception>(ThrowerThreaded);
}
The test case fails indicating no exception was thrown. If I debug the test case, I can clearly see that the exception is caught and re-thrown in ThrowerThreaded()
however the method still continues and exists normally. Why is the exception still being suppressed?
CodePudding user response:
I'm guessing ThrowerThreaded
is running on a background thread. That means it does not have a synchronizationContext, since these are intended to synchronize UI applications. This means the callback is called on the threadpool:
Any handler provided to the constructor or event handlers registered with the ProgressChanged event are invoked through a SynchronizationContext instance captured when the instance is constructed. If there is no current SynchronizationContext at the time of construction, the callbacks will be invoked on the ThreadPool.
Rethrowing the exception on a threadpool thread will probably kill that thread, and I'm somewhat surprised it did not kill the application, but it's possible that such behavior is overridden by the testing framework you are using.
To solve this you really need to handle the exception in the callback instead of re-throwing it. If you are not handling the exception, who should? there is a unhandledExceptionEvent, but that is intended for logging before you close your app.
You could handle the exception taking a callback in ThrowerThreaded
that you delegate the exception handling to. Another alternative would be to create a TaskCompletionSource that allow you to return a task, and set the task to 'failed' by calling SetException on the source.
It is also poor practice to re-throw the same exception object, since you will lose the call stack, you should instead wrap the exception in a new exception that is thrown.