Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Java Generic Code - Taking Control of Threaded Actions

0.00/5 (No votes)
27 Jun 2004 1  
A simpler way to use Threads

Introduction

Sometimes it may be necessary to use Threads. One of the most obvious scenarios is when you have two processes that have to take place at the "same-time". Take for instance loading a web-page inside a web-browser. There is the time-consuming process, of actually loading the HTML, parsing it, then loading the images and any other resources the web-page requires. Then, there is the process of updating the browser's GUI with some progress-indicator, and perhaps rendering the web-page's content while it's being loaded. When the time-consuming process is complete, the browser has to finish rendering the whole page and activate any interactive elements thus transferring control back to the user.

ThreadAction

The ThreadAction class is intended to solve exactly this. By extending the ThreadAction class, you must override three methods which correspond to the three parts of the scenario:

  1. The time-consuming-process that has to be completed.
  2. A short repeatable process that usually is meant to update the User with the progression of the first process.
  3. A process that is activated once the first process completes, thus finishing the whole cycle.

Java Code

public abstract class ThreadAction implements Runnable {
    // The sleep time between cycles of whilePerformingAction() invocations..
    private int actionProgressSleep;
    // True when the ThreadAction has completely finished any required processes..
    private boolean actionFinished;
    // True while the performAction() method has not completed its run yet..
    private boolean actionPerforming;
    // The Thread invoking the performAction() method..
    private Thread actionRunner;
    // The Thread invoking the whilePerformingAction() method..
    private Thread actionProgress;

    protected ThreadAction() {
        reset();
    }

    protected Thread getThread(String name) {
        // Return a new Thread; this method can be overridden to return a Thread from
        // a Thread-Pool if you're using one..
        return new Thread(this, name);
    }

    public void reset() {
        // Set all values to default..
        actionProgressSleep = 300;
        actionFinished = true;
        actionPerforming = false;
        actionRunner = null;
        actionProgress = null;
    }

    public void setActionProgressSleep(int sleepTime) {
        // Are we active?
        if (!isActionPerforming()) {
            // Reset everything..
            reset();
            // Save the new sleep interval..
            actionProgressSleep = sleepTime;
        }
    }

    public void run() {
        // Are we on the ActionRunner Thread?
        if (actionRunner == Thread.currentThread()) {
            // Perform the action..
            performAction();
            // Signal the ThreadAction that the action is finished..
            actionPerforming = false;
            // Are we on the ActionProgressRunner Thread?
        } else if (actionProgress == Thread.currentThread()) {
            // While we're still busy performing the main action..
            while (actionPerforming) {
                // Invoke the whilePerformingAction() method..
                whilePerformingAction();
                try {
                    // Send the ActionProgressRunner Thread to sleep..
                    Thread.currentThread().sleep(actionProgressSleep);
                } catch (Exception ex) {
                }
            }
            // Invoke the whenActionFinished() method since we're done..
            whenActionFinished();
            // Forget the Thread instances and signal that we're completely done..
            actionRunner = null;
            actionProgress = null;
            actionFinished = true;
            // Notify ourselves that we've finished the action process..
            synchronized (this) {
                notifyAll();
            }
        }
    }

    public void activateAction(boolean waitForAction) {
        // Are we in the middle of processing an action already?
        if (!isActionPerforming()) {
            // Obtain a Thread to run the progress-update-process in..
            if (actionProgress == null) {
                actionProgress = getThread("ActionProgressUpdateThread");
            }
            // Obtain a Thread to run the time-consuming-process in..
            if (actionRunner == null) {
                actionRunner = getThread("ActionRunnerThread");
            }
            // Mark the action flags..
            actionFinished = false;
            actionPerforming = true;
            // Start the Threads..
            actionProgress.start();
            actionRunner.start();
            // Were we asked to wait for the action to complete before returning?
            if (waitForAction) {
                waitForAction();
            }
        }
    }

    public void waitForAction() {
        // Are we in the middle of processing an action already?
        if (isActionPerforming()) {
            synchronized (this) {
                try {
                    // Wait here until the action process completes..
                    wait();
                } catch (InterruptedException ex) {
                }
            }
        }
    }

    public boolean isActionPerforming() {
        // Return True is the action is still running..
        return actionPerforming || (!actionFinished);
    }

    // Specifies a time-consuming process executing when ThreadAction activates..
    public abstract void performAction();
    // Specifies a process to invoke repeatedly while performAction() is running..
    public abstract void whilePerformingAction();
    // Specifies a process to run at the end of performAction()..
    public abstract void whenActionFinished();
}

Testing the Code

We can add a main(..) method to the ThreadAction class, or add it in a different class altogether, to test how ThreadAction actually works:

public static void main(String[] args) {
    ThreadAction ta = new ThreadAction() {
        public void performAction() {
            long time = System.currentTimeMillis() + 600;
            while (time > System.currentTimeMillis()) {
                System.out.println("|    X    |          |          |");
                try {
                    Thread.sleep(250);
                } catch (InterruptedException ex) {
                }
            }
        }
        public void whilePerformingAction() {
            System.out.println("|         |    X     |          |");
        }
        public void whenActionFinished() {
            System.out.println("|         |          |     X    |");
        }
    };
    ta.setActionProgressSleep(200);
    System.out.println("+---------+----------+----------+");
    System.out.println("| Working | Updating | Complete |");
    System.out.println("+---------+----------+----------+");
    ta.activateAction(true);
    System.out.println("+---------+----------+----------+");
}

As you can see, the main(..) method defines an inner class extending ThreadAction and overrides the abstract methods with simple implementation that will print out an 'X' character corresponding to whatever the method is supposed to be doing at the time.

The output, on my machine, looks like this:

+---------+----------+----------+
| Working | Updating | Complete |
+---------+----------+----------+
|         |    X     |          |
|    X    |          |          |
|         |    X     |          |
|    X    |          |          |
|         |    X     |          |
|    X    |          |          |
|         |    X     |          |
|         |          |     X    |
+---------+----------+----------+

Please notice the activation of the ThreadAction with the value of True sent to the activateAction(..) method. If we were to invoke activateAction(..) with False instead, the printing from the main Thread would have occurred before our ThreadAction could have started.

If you have not activated the ThreadAction with True specified in activateAction(..) and you want to wait for the ThreadAction to complete its course, you can use the waitForAction() method at any time before the ThreadAction is done.

I have used this class in my applications for many scenarios, logging-in, fetching complex data from a database, anything where a background process has to take place beside a time-consuming one.

Another Example

The following code demonstrates using ThreadAction in a log-in process:

public class LoginAction extends ThreadAction {
    public String userName;
    public String passWord;
    public String networkResource;
    private boolean loginFailed;
    private int errorCode;

    public void performAction() {
      // Perform the log-in code, access the network-resource with the User-Name
      // and Password, set the loginFailed flag to True or False accordingly
      // and the errorCode with the corresponding result from the network
      // resource.
    }

    public void whilePerformingAction() {
      // Display some sort of login-progress-dialog, rotating logo or whatever is
      // graphic animation was designed.
    }

    public void whenActionFinished() {
      // If the login process succeeded, progress with the Application allowing the
      // User to interact with the network-resource, if however, login has failed
      // prompt the User to re-enter the password/username if there was an error
      // with either.
    }
}

Please note that ThreadAction is designed so that its instances can be used repeatedly within the life cycle of an application, and also in such a way that enables using a Thread-Pool class to provide the Threads instead of re-creating them each time the activateAction(..) method is invoked.

Hope this one helps you and happy coding.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here