My view is that exceptions should be caught and held for later inspection, or re-raised at some synchronization point. What do you think should happen to the exceptions? Should they silently disappear, tear-down the entire application, or should some mechanism be in place to allow the programmer to decide what to do with them?

5 comments:

I certainly would NOT want to seem them silently eaten, and retribution will be swift and severe if such an approach is taken in any proposed concurrency library :-)

I'd favour an approach where the programmer can subscribe to an event to get notification of work item exceptions, so they can take the relevant action. I also would not want to see an exception in work item A affect work item B (unless there was a mechanism which had allowed a definition of a dependency between these two work items).

The main loop of that class calls MsgWaitForMultipleObjects, waiting on synchronization objects, and has a try/except block that captures all exceptions while processing any signaled object.

Examples of signals that a thread might wait on: 1) a Windows event indicating that there are characters to read from a serial port; 2) a semaphore in the signaled state indicating that there is a message to process in an input queue.

The exception handler mentioned above calls a virtual method called HandleException. Descendants override that to provide exception handling. Exceptions are typically eaten with this mechanism, but descendants have an opportunity to add functionality before that happens. In general, I just log the exception to the application log in the base thread class.

Another useful method is to re-raise exceptions in the calling thread's context. This is done with a class that represents a thread message:

1. Thread A places a message into thread B's input queue.

2. Thread A calls Message.WaitFor to wait until the message is processed. WaitFor pumps the caller's message pump (either Application.ProcessMessages if Thread A is the GUI thread or the calling thread's MessagePump) while waiting for the message to be processed. The MessagePump avoids deadlock issues, and allows one to write serialized code. You have to constantly be aware that reentry can occur with this design, but it beats the asynchronous programming model because the code is much simpler and it isn't necessary to write callbacks.

2. Either the message is processed successfully, or it is canceled (usually via a user action), or an error occurs while processing the message.

3. If an exception occurs in thread B while it is processing the message, Msg.WaitFor re-raises the exception in the *caller's* thread. This is done by calling AcquireExceptionObject in the thread that processed the message.

So you might write code like this (contrived) example:

// Send a file to a remote machineMsg := TSendFile.Create(Filename, Destination);try try // Tell the network to send the file Network.SendFile(Msg, UpdateFileProgress); // Wait until the file has been sent, the send was canceled, or an error occurs Msg.WaitFor; except on e: Exception do ShowMessage(Format('The file could not be sent: %s', [e.Message])); end;finally Msg.Free; end;

If the code above was called from a form, there might be a cancel button on that form. The cancel button handler would call Msg.Cancel, which would cause Thread B to raise an EAbort exception while processing the message. The message handler in Thread B has to be aware that a message can be canceled and needs to periodically check whether the message was canceled. If that were done, in the end the user would see this message:

Please keep your comments related to the post on which you are commenting. No spam, personal attacks, or general nastiness. I will be watching and will delete comments I find irrelevant, offensive and unnecessary.