Async operations and exceptions
We have had the case where we’re creating a class that allows clients to block while internally it reads an entire stream asynchronously. This class encapsulates the state required to perform such a task.
While attempting to write unit tests for exceptions, we found that an exception thrown during the asynchronous operation would not be thrown to client. Debugging showed that the exception was being thrown, but no notification was being sent to the parent thread.
No such thing as unhandled exceptions on managed threads
[MSDN] [since .NET Framework v2.0] There is no such thing as an unhandled exception on a thread pool [or finalizer] thread. When a task throws an exception that it does not handle, the runtime prints the exception stack trace to the console and then returns the thread to the thread pool.
Errors raised on a child thread are essentially lost when the thread exits. This means there is some work required to propagate these exceptions.
This requires a blocking wait on the part of the client, and a mechanism for storing the exception so the parent thread can read it.
As an example, we have implemented an AsyncStreamReader which contains a blocking ReadAll method. If an asynchronous read fails with an exception, that exception is exposed internally as a field, and the waiting thread is then signalled. Once the waiting thread wakes up it checks the exception field and throws it if required.
We have blocking Read operation that waits for an async read to complete. The notification mechanism is a ManualResetEvent (WaitHandle).
- T1: Invoke ReadAll.
- T1: Start async operation (spawns T2).
- T1: Wait.
- T2: Async operation encounters exception.
- T2: Store exception in _error field.
- T2: Signals T1.
- T2: Returns without triggering any subsequent reads.
- T2: Thread exits
- T1: Parent thread resumes (still inside ReadAll).
- T1: Checks _error field. If it is not null, throw it, otherwise return.
- T1: Exception is now propagated