All the magic, however is done in the ControllerThread class, which takes care of executing the task, keeping tabs on it and, eventually, terminating it if it overstays its welcome.
Before getting on to ControllerThread, it is worth, however, to briefly review the Observer interface that will have to be implemented by the calling class.
We need an Observer here mostly for three reasons:
- because of the asynchronous nature of the call, we need a way to communicate back when we are done with our task (if you look back at the last snippet of code in Part 1, you'll notice that startRecording() returns immediately: however the recording itself will last a few seconds, until either the user stops it, or the ControllerThread "expires" - in this case, after 10 seconds at most);
- we may also want to be notified of progress (eg, by showing a "progress bar" to the user);
- most importantly, we need to be notified when the task failed (that is, a TaskException was thrown).
Bear in mind that we cannot "wrap" the call to Thread.start() with a try/catch block, as no exception will ever be thrown by this code - catching the TaskException is a job for the ControllerThread (and the reason why LongRunningTask does not implement Runnable).
The Observer interface is defined as follows:
public interface Observer { /** * A simple logging ability, this has to be implemented in a * thread-safe manner, eg wrapping the logging with synchronized blocks * * @param msg a simple logging message */ public void addMsg(String msg); /** * callback for signaling progress of time-consuming process, provides * an opportunity to either allow stopping the background operation or * update an UI element providing visual indication that the application * is not 'stuck' */ public void idle(); /** time-consuming operation complete, results, if any, are available */ public void complete(); /** acknowledgement of interruption, optional */ public void interrupted(); /** * This is called by the LongRunningTask to signal progress, could * be used to update some UI element (eg a progress bar) * * @param progress a value (meaningful to the application's semantics) * that indicates progress. Most typically, a value between 0 * and 100, to indicate percentage progress towards completion. */ public void announceProgress(int progress); /** Called when the task fails */ public void fail(TaskException tex); }
Once a
LongRunningTask
is started, it will either complete()
or fail()
: these methods will be called by ControllerThread
(not LongRunningTask
); the latter will either complete the execute()
method normally, or throw a TaskException
to indicate failure.ControllerThread
is a pretty long class: the full source code can be found here, only snippets of code will be shown in the following.public ControllerThread(LongRunningTask task, long waitInterval, long timeout, Observer observer)
The constructor takes a reference to the
task
to execute, an (optional) reference to an Observer
and two numeric values that indicate, respectively, how often it should check on progress and how long the task is given to complete its work.ControllerThread does implement
Runnable
- it is executed in its very own thread and creates (and executes) in turn another thread: as I mentioned in the first part of this article, to execute a background thread, you need two threads, just one won't do!public void run() { long start = System.currentTimeMillis(); long elapsed; Thread t = new Thread(new ControllerRunnable()); t.start(); while (!task.isDone() && !task.isStopped() && !failed) { wait_state = THREAD_STATE_WAIT; elapsed = System.currentTimeMillis()-start; if (elapsed > totalTimeout){ task.cancel(); wait_state = THREAD_STATE_EXPIRED; getObserver().interrupted(); return; } synchronized (lock) { try { // double-check to avoid race conditions if (!task.isDone() && !task.isStopped()) lock.wait(waitTimeout); } catch (InterruptedException ex) { failed = true; failureMessage = "ControllerThread interrupted"; t.interrupt(); getObserver().interrupted(); return; } } } // while }
All the magic is done in the internal class ControllerRunnable that implements Runnable itself and inside its run() method actually executes the LongRunningTask, wrapped in a try/catch block to catch failures.
private class ControllerRunnable implements Runnable { public void run() { if(task == null) { failureMessage = "No task to execute"; failed = true; getObserver().fail(new TaskException("No task to execute")); return; } try { task.execute(getObserver()); synchronized (lock) { lock.notify(); } getObserver().complete(); } catch(TaskException ex) { failureMessage = new String(ex.getMessage()); failed = true; getObserver().fail(ex); } } }
As you can see, it is
ControllerRunnable.run()
that calls the Observer
's complete()
or fail()
methods, and ensures that:- one and only one of those methods will be called;
- that they will be called at most once.
This is critically important in the very frequent case of real-life applications that have multiple tasks that may be executing at any one time (and, possibly, even concurrently) and hence are likely to have state variables to control what's going on: in this case, calling twice complete() is almost as bad as not calling it at all.
Note also the wait/notify mechanism (
lock
is simply an Object
, private
to ControllerThread
, used to synchronize the two threads) to signal the controlling thread that we are done and that is no longer necessary to wait. If the notification fails to arrive within the timeout
value set when creating the controller thread, the task will be terminated (see ControllerThread.run()
).From an application developer's point of view, however, all of the above can be treated as "magic."
All one has to do to use this framework is:
- write a time-consuming task as a single execute() method;
- wrap the time-consuming task in a class that implements LongRunningTask (bearing in mind that most of its methods are optional - only execute() is, rather obviously, mandatory);
- implement Observer in the calling class (most likely, a Controller class in an MVC pattern) - this will also include, probably, adding a "Stop" command to the UI as well as some sort of progress indicator (eg, a Gauge);
- wrap the call to the time-consuming task in the following code:
public class Multimedia implements Observer { // Observer interface implementation omitted here // ref. source code & Part II of this article public void startRecording() { task = new RecordTask(); thread = new ControllerThread(task, 1000L, 10000L, this); new Thread(thread).start(); } // Other stuff goes here... }
Update: I have uploaded the source code to BitBucket, you can easily download it using Mercurial (see here for an intro) by simply using:
https://marco@bitbucket.org/marco/j2me_threads
I have updated the links, they should work now (yes, the code is still very much available!)
ReplyDeleteThanks for your interest and if you have comments / ideas to improve it, please feel free to post!
Thanks for your great article!
ReplyDeletehow to get the percentage program from using gauge control in j2me
ReplyDelete