Tuesday, April 10, 2007

J2ME Multi-Threading and HTTP

Update: This is Part I of a two-part series about multi-threading in the J2ME environment, Part II can be found here.
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
...

In my experience, one of the most difficult things to achieve in software (and even
more so in "limited" environments, such as J2ME) is to harness certain "long-running tasks" so that:



  • they have enough time to complete their task, yet they do not
    hog the system so as to make it unresponsive to users (who would
    then have the impression the application is 'hung');
  • it is easy to both check on whether they are still running or are, indeed, 'hung';
  • it is relatively straightforward to set a timeout, expired which, they will be, rather uncerimoniously aborted;
  • it is relatively straightforward to enable a 'Cancel' option for the user to press if they get bored before the timeout expires.

The hortodox answer to this has been (and quite rightly so) to have the long running task run in a separate thread from the main (GUI) thread, with a boolean variable to control execution.

Something like this:

public class MyLongTask implements Runnable {

  private volatile boolean stopped = false;
    public void stop() {
    stopped = true;
  }

   public void run() {
     boolean finished = false;
     // do some setup here
     // ...

     while (!stopped && !finished) {
       doAChunkOfWork();
       finished = amIDone();
     }
   }
}

or something similar to that.
The controlling thread (typically the one that runs the GUI too, which has probably a "Cancel" button hooked to a cancelTask() method) looks something like this:

public class Controller {
  private MyFrameView theView;
  private MyLongTask task = null;

  public Controller(MyFrameView theView) {
    this.theView = theView;
  }

  // various other stuff...

  public void doStartTimeConsumingTask() {
    task = new MyLongTask();
    new Thread(task).start();
  }

  public void doCancel() {
    if(task != null)
      if (! task.isFinished())
        task.stop();
  }

  // other Controller "stuff"

}


This is, in fact, what the good folks at Sun in the NetBeans team have essentially done, by defining the CancellableTask interface and a generic SimpleCancellableTask class (although, the latter, quite sadly has not implemented... the cancel() method!).

This was mostly done to supporting NetBeans' WaitScreen component and, even then, I found it not terribly compelling. (see Lukas Hasik's blog here).

Initially, when confronted with the problem, I simply extended SimpleCancellableTask, and used the stopped/stop() mechanism to enable the cancel() mechanism - that enabled my application to stop a long running task that had overstayed its welcome; however, at the price of having to throw a RuntimeException to communicate back to the WaitScreen that it was done, but had not finished its task.

So far, so good.

However, there is still no easy way to set a timeout task, nor we have implemented any mechanism to let MyLongTask tell Controller they're done (let alone let the latter communicate back to the the View, which, most likely, will display something to the user to tell her we're done).

The latter problem is easily solved with the Observer pattern (see my other post - to be published later): this can be implemented by either the view or the controller - my preference has always been for the Controller to act as the observer (again, I seem to be in broad agreement with most practitioners - those times when, for expediency, I have implemented that in the View, I have always come to regret it... Thank God for Refactoring!).

Setting a timeout and generally keeping tabs on the long-running task, in fact, requires that one uses an additional, controlling thread.
In other words, to properly manage a time consuming task, you need two additional threads - just one won't do.

I decided then that a more general solution was needed, one that would allow an easy, programmatic way of controlling a long running task and was, ideally, also "pre-wired" with an observer.

My approach uses a ControllerThread, a LongRunningTask interface and an Observer interface.

The code can be downloaded here, please note that this is written specifically for 'microedition' environments(J2ME), hence no generics or any other Java 5 niceties are used.

The LongRunningTask interface is reviewed below, the ControllerThread will be analysed in Part 2 of this article.

LongRunningTask is a relatively straightforward interface, that defines a couple of 'check-me' methods (isDone() and isStopped()) the obvious cancel() one and an optional progress() method, that returns a percentage value to completion (the actual semantic of the value returned by progress(), is worth noting, is entirely application-dependent, although I like to use it as a % value, to update, for example, a Gauge UI indicator).

The 'quirk' here is that LongRunningTask does not extend Runnable: the reason for this will be clearer when we'll come to look at ControllerThread (that does implement Runnable). All the work is done in execute() that throws a TaskException (unlike run() in Runnable) and can thus indicate abnormal termination to a 'wrapper' class.

To use the framework, one simply implements LongRunningTask for a class with a time-consuming method, and checks regularly on a boolean variable that may be set by cancel() (or any other sensible way to communicate to a running thread that it's time to quit it, enough is enough).
public class RecordTask implements LongRunningTask {

  private boolean stopped;
  private boolean done;
  private int progressIndicator;

  private byte[] audioData;

  /** Creates a new instance of RecordTask */
  public RecordTask() {
  }

  public boolean cancel() {
    stopped = true;
    return true;
  }

  public boolean isStopped() {
    return stopped;
  }

  public boolean isDone() {
    return done;
  }

  public int progress() {
    return progressIndicator;
  }

  /**
   * This is where all the time-consuming work is done:
   * here we record a speech segment, long at most maxDuration
   * or until the user presses a "Stop" command (that in turn will cause
   * the controller to call cancel()
   *
   * @param observer the observing class
   */
  public void execute(Observer observer) throws TaskException {
    observer.addMsg("Record started");
    try {
      done = false;
      stopped = false;
      Player player = Manager.createPlayer(getAudioMimetype());
      if (player != null) {
        player.realize();
        RecordControl rc = (RecordControl) player.getControl("RecordControl");
        if (rc != null) {
          observer.addMsg("RecordControl: OK");
        } else {
          observer.addMsg("No RecordControl, exiting");
          return;
        }

        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        rc.setRecordStream(bos);
        rc.startRecord();
        player.prefetch();
        player.start();

        long start = System.currentTimeMillis();
        while (!isStopped() && ((System.currentTimeMillis()-start) < getMaxDuration())) { 
          calcProgress(start);
          observer.announceProgress(progress());
        }
        rc.commit();
        player.close();
        audioData = bos.toByteArray();
        observer.announceProgress(100);
      }
      done = true;
    } catch (Exception ex) {
      observer.addMsg("Recorder exception: "+ex.getMessage());
      // The following is necessary to communicate back to ControllerThread
      // that an error condition was encountered
      throw new TaskException("Recorder exception: "+ex.getMessage());
    }
  }
  // Other utility methods follow....
}


Then, an instance of this class is passed, at construction, to a newly created instance of ControllerThread:
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...
}


And that's pretty much about it: with the code above, we have created and started a new task which we'll check every second (1000L msec) and will allow at most 10 sec (10000L msec) to complete.
All the magic is done in ControllerThread - explained in part 2 of this series.

10 comments:

  1. The link to this code it broken. Is it still possible to get it?

    ReplyDelete
  2. I have updated the links, they should work now (yes, the code is still very much available!)

    Thanks for your interest and if you have comments / ideas to improve it, please feel free to post!

    ReplyDelete
  3. Hi,

    The link is timing out.
    Can you please update the link to the code.

    Regards,
    Aidan

    ReplyDelete
  4. Yes, indeed: the link is pointing to my personal server who 'died' a few weeks ago.

    I'm planning to move the code to BitBucket or Google Code (code.google.com) soon as I find 10 minutes' breathing space.

    (relocating to the US whilst managing a team of 20 engineers at Google is hard work :-)

    Please do bear with me, I'll probably get round to it this weekend.

    ReplyDelete
  5. Hi Marco,

    If you get a chance to put your code up on Google Code that will be great.

    I understand if you don't have time.

    Good luck in the US don't work too hard.

    ReplyDelete
  6. Uploaded the code to BitBucket.org, for download using a Mercurial client:

    hg clone https://bitbucket.org/marco/j2me_threads

    enjoy!

    ReplyDelete
  7. Seems this code is no longer available on BitBucket?

    ReplyDelete
  8. Hi Steve,

    I've just checked, it still seems to be there:
    https://bitbucket.org/marco/j2me-threads

    It's a publicly available repository, please let me know if still can't reach it and what do you see instead.

    Thanks for reading!
    (BTW - I've moved my blog to http://codetrips.com)

    ReplyDelete
  9. Hi, Great.. Tutorial is just awesome..It is really helpful for a newbie like me.
    I am a regular follower of your blog. Really very informative post you shared here. Kindly keep blogging.
    If anyone wants to become a Java developer learn from Java EE Online Training from India.
    or learn thru Java EE Online Training from India .
    Nowadays Java has tons of job opportunities on various vertical industry.

    ReplyDelete