views:

522

answers:

2

I am working on a J2ME project that spawns worker threads for numerous tasks such as downloading HTTP content. The basic thread layout is similar to most java apps--there is a main UI thread and worker threads spawned to do stuff behind the scenes. My question is what is the best way to handle exceptions that occur in the worker threads?

I usually adhere to the design rationale that most exceptions should be percolate as far as possible. When I write single threaded apps, it is common for me to percolate the exceptions all the way up to the UI layer and then report them in an error dialog to the user. Is there a similar practice for multithreaded apps? The most intuitive thing to me is to catch exceptions in the Thread.run() and then call an invokeLater on the UI thread to report it in a dialog. The issue I see here is that outside of the worker thread dying off prematurely, this approach does not really notify the UI thread there was an error. I do not see a clear way to throw an exception across threads so to speak.

Thanks, Andy

+4  A: 

You should NOT jam UI code into your workers!

/**
 * TWO CHOICES:
 * - Monitor your threads and report errors,
 * - setup a callback to do something.
 */
public class ThreadExceptions {

    /** Demo of {@link RunnableCatch} */
    public static void main(String[] argv) throws InterruptedException {
     final Runnable bad = new NaughtyThread();
     // safe1 doesnt have a callback
     final RunnableCatch safe1 = new RunnableCatch(bad);
     // safe2 DOES have a callback
     final RunnableCatch safe2 = new RunnableCatch(bad, new RunnableCallback() {
      public void handleException(Runnable runnable, Exception exception) {
       System.out.println("Callback handled: " + exception.getMessage());
       exception.printStackTrace();
      }

     });
     final Thread t1 = new Thread(safe1, "myThread");
     final Thread t2 = new Thread(safe2, "myThread");
     t1.start();
     t2.start();
     t1.join();
     t2.join();
     if (safe1.getException() != null) {
      System.out.println("thread finished with exceptions");
      safe1.getException().printStackTrace();
     }
     System.out.println("done");
    }


}

/** Throws an exception 50% of the time */
class NaughtyThread implements Runnable {
    public void run() {
     try {
      if (Math.random() > .5) {
       throw new RuntimeException("badness");
      }
     } finally {
      System.out.println("ran");
     }
    }
}

/** Called when an exception occurs */
interface RunnableCallback {
    void handleException(Runnable runnable, Exception exception);
}

/**
 * Catches exceptions thrown by a Runnable,
 * so you can check/view them later and/or
 * deal with them from some callback.
 */
class RunnableCatch implements Runnable {

    /** Proxy we will run */
    private final Runnable _proxy;

    /** Callback, if any */
    private final RunnableCallback _callback;

    /** @guarded-by(this) */
    private Exception _exception;

    public RunnableCatch(final Runnable proxy) {
     this(proxy, null);
    }

    public RunnableCatch(final Runnable proxy, RunnableCallback target) {
     _proxy = proxy;
     _callback = target;
    }

    public void run() {
     try {
      _proxy.run();
     } catch (Exception e) {
      synchronized (this) {
       _exception = e;
      }
      if (_callback != null) {
       _callback.handleException(_proxy, e);
      }
     }
    }

    /** @return any exception that occured, or NULL */
    public synchronized Exception getException() {
     return _exception;
    }
}
Stuph
Thanks Stuph. That makes more sense. A quick question on your example: is the synchronized(this) block really necessary around the assignment to _exception? I was under the impression that object assignments were atomic.
Andy
A: 

Another option other than what Stuph has given is to set exceptions in the thread local. If another exception happens before that exception is cleared then an assert occurs. That at least gives someone a chance to notice the exception and process it.

Todd Hoff