views:

59

answers:

2

This question has been brought up many times, but I'd like to ask it again because I've read some things here that didn't seem right to me (could be because one is related to the .NET CF), and I am asking for validation of a specific approach that I think is reasonable, and would appreciate any feedback you might have.

Anyhow, the situation is much like everyone elses -- I want to handle errors originating from a thread. Currently, the code I have works fine because I'm throwing exceptions from a thread, but I am using Invoke, rather than BeginInvoke.

You're probably wondering at this point, "Why would he create a thread and then call Invoke? Why not just call a function?". The issue is that I want to give the caller flexibility via a parameter that dictates whether or not the operation should be synchronous or asynchronous.

As a test, I have tested it in the asynchronous mode of operation, and as expected, it fails silently and the application dies in the debugger at the point the exception is thrown from the worker thread.

To make sure I have a basic understanding of the behavior of exceptions in threads, as well as the disprove the statement in this question on SO (at least as far as .NET 3.5 goes), I wrote some test code in a WPF app:

code removed

So it seems like there should be no problem handling events in the main thread that originate from another thread.

Provided that you agree with my tests above, my question is simply this -- is it an acceptable practice to write my method that offers both sync and async behavior where all of the business logic is wrapped in a try/catch block? Internal errors get trapped inside of the thread, and if it's a sync call, just throw the exception, and if it's an async call, just raise the event? In my sample code above, I also throw the exception after raising the event. I'm not sure if or why this would cause me any problems.

Pseudo-pseudocode:

TestFunc(async);

private TestFunc(bool async)
{
  try {
    throw new MyAppException("error occurred.");
  } catch(MyAppException ex) {
    async ? RaiseErrorEvent : throw;
  }
}

UPDATE

Ok, as Hans said, it will cause issues no matter what if I throw the exception from the thread -- period. I was able to test this case out and sure enough, the exception gets thrown and if you hit F5, it just gets thrown over and over again and never terminates the thread. I don't understand this particular behavior -- is it just how it works when running through the debugger? I guess this means that I have to check if the method is getting called sync or async. If sync, throw error. If async, raise event.

Perhaps a better way to do it is to force the client to always handle errors with events, rather than catching exceptions? Anyone have thoughts on this approach?

+2  A: 

Not sure what you're trying to prove with this code. Just write an exception handler for AppDomain.Current.UnhandledException and log or display the value of e.ExceptionObject.ToString().

If you are contemplating actually handling the exception then you've got an entirely different kettle of fish to fry. You've got a bit of code that runs and dies at a random spot. You don't really know how far it got along and how much of your program state got altered by that thread. Handling an exception requires restoring the program state, undoing whatever the thread did. Whatever event handler you write to process the "it bombed" event cannot possibly guess what needs to be done to restore state, it doesn't know nearly enough about the thread. It only knows that it didn't work. Restoring state needs to be done by the thread itself.

Handling the "it bombed" event is difficult in itself. The client code cannot even display a message box, it will easily disappear behind your main window. Updating the UI directly is not permitted. The call needs to be marshaled to the UI thread. The best way is to leave it up to the client code to decide how it want to be notified. The FileSystemWatcher.SynchronizingObject property is a good pattern.

Hans Passant
Maybe I shouldn't have posted that code, I was merely showing a contrived example of throwing exceptions from threads as a bit of background info.Yes, I do need to handle the exception. What I am doing now is catching the exception in my thread, and then raising an Error event *and* throwing an exception. This way, my caller, whether sync or async, can handle the error however it likes. It will use try / catch if it wants to call synchronously. It will register / deregister an event if it wants to call asynchronously. I am merely asking for opinions about this approach; it seems to work.
Dave
Throwing the exception will bomb the program with no way for the client code to catch it. Because the event is raised on a thread.
Hans Passant
+1  A: 

If you're talking about executing a task asynchronously, the most typical pattern is to catch the exception in thread and rethrow it once you eventually synchronize to wait for task completion. For instance, the IAsyncResult pattern has a BeginX(), EndX() call structure where you call EndX() to block until completion. The EndX() call should throw any exception that happened in the asynchronous operation so that the caller can decide how to handle it.

If you're using .NET 4.0, the Task Parallel Library has extensive support for capturing and even aggregating exceptions while performing asynchronous tasks so that the client that launched the tasks can decide how to handle them.

Dan Bryant
I read about .NET 4.0's Task and was intrigued. Unfortunately, I'm not upgrading to 4.0 until the beginning of next year.I understand what you're saying with BeginX, EndX. The reason why I don't use this approach is that my GUI could be a consumer of the sync/async method. In the GUI, I want to call it non-blocking for obvious reasons. If I call the method, and that method calls EndX after BeginX, then it's essentially blocking. That's why I call BeginX only. I could use a callback in this case, but instead I'm just waiting for the event to get raised before the thread exits.
Dave
@Dave, a typical usage pattern for IAsyncResult is to have a callback. Within the callback, you are expected to call EndX() to close out the task and throw any exception that occurred during the operation. It's generally considered a usage error to call BeginX() without ever calling EndX(), though this practice is often not followed.
Dan Bryant
@Dan thanks for the advice. I'll give that a try and we'll see how it works out.
Dave
@Dan thanks, I now see that my original approach to using thread, while it "worked", it wasn't quite correct. Supplying a callback and rejoining the main thread and catching exceptions in the callback is working out very nicely.
Dave