12. Noodle Threads (threading, progress, console)

Design recipes are the equivalent of soccer ball handling techniques, writing techniques, techniques of arrangements, and drawing skills. A single design recipe represents a point of the program design space. We have studied this space and have identified many important categories. This book selects the most fundamental and the most practical recipes and presents them in increasing order of difficulty.

Figure 49. Thread noodles (noodle recipe)

Thread noodles (noodle recipe)

In the last section we defined a generic Scheme interpreter API, and made it public.

We need to implement this generic Scheme Interpreter API using a Scheme interpreter (such as SISC). But before doing so, we need to learn how to do basic things with the NetBeans Platform.

We need to learn how to run a task in background (and how to cancel it), and how to show progress to the user while the task runs. And, of course, how to print out the results of those tasks in the Output Window. With those basic idioms we may start cooking some more complex recipes, such as using our favourite Scheme interpreter.

So let's get some threads chopped.

12.1. Scheduling Threads for Execution

You can schedule Threads for execution as you always have done. Of course. There's nothing new under the sun here. You just create a Thread and you "start()" it. And you're all set.

But the NetBeans Platform contains some other mechanisms for scheduling threads. The first is the so called "RequestProcessor" class, and the second is the "Progress UI API", used to show progress of your tasks to the user. Let's see how easy is to use them (and again let me complain of not having studied all this before).

12.1.1. Threading with the RequestProcessor

The RequestProcessor (included in the Utilities API) has been around probably before 2000. So it's probably seven or eight years old now.

Eight years seems to me as sound. I mean, if a piece of software has been around during almost eight years (being used daily, by a lot of people, and with lots of eyes looking for issues and bugs and improvements) then I think that piece of software must be solid as rock, right?

RequestProcessors are roughly equivalent to Java Executors and Java Executor Services, this is, they're responsible for scheduling Runnables for execution in a set of threads.

Using a RequestProcessor is extremely simple. You may use the default RequestProcessor (a sort of default thread pool) to post your Runnables, like this:

Runnable myRunnable = ...
RequestProcessor.Task myTask =
  RequestProcessor.getDefault().post( myRunnable );

And your task is automatically scheduled for execution in a worker thread. That simple.

Note that you get a RequestProcessor.Task in turn, which is roughly equivalent to a Java FutureTask: an object you can use to cancel the task, for instance.

You can also create your own RequestProcessor with a predefined number of threads. That's probably a good idea if you don't know how many task user may spawn.

So that's all we wanted to know about RequestProcessors. As we've seen they're quite powerful (and quite simple to use as well).

I recommend you to review RequestProcessor's javadoc, just to see for yourself what you can do with this nice piece of (reusable) code.

Interrupting tasks

To interrupt a task you just use the RequestProcessor.Task handle and invoke "cancel()" on it. And you may be able to interrupt it.

May interrupt it? How so, you ask?

Figure 50. Progress UI interface

Progress UI interface

Well, the fact is that not all Java threads are really interruptible. I mean, you can "interrupt()" all Java threads, but not all of them will be really interrupted.

If you thread performs I/O operations (such as reading a file, or reading data through a JDBC connection) then your thread may be interrupted. But if your task does some heavy maths (or other computations without I/O operations) then your thread may not be interruptible. (I blogged about this earlier). This is so because all I/O operations invoke the "Thread.interrupted()" method periodically, and if the method returns true then they stop operation.[11]

But if you just burn your CPU with a long math computation and you don't check if "Thread.interrupted()" for yourself, then your task may be really uninterruptible. It will keep going even you invoke RequestProcessor.Task on it.

So if you want to make your tasks interruptible remember to check for "Thread.interrupted()" periodically. Just in case.

12.1.2. Showing progress

Of course we want to show progress of our task to our users. I mean, scheduling things for execution and not telling the user is somewhat rude and frustrating to them.

The NetBeans Platform includes utilities to help you show progress to the user, all of them packed under the Progress API module. This is just a module with two public classes.

There're different use-cases you may want to explore (by reading the javadoc), but the following one is good enough for our needs:

Cancellable myCancellable = ...
ProgressHandle myProgressHandle =
ProgressHandleFactory.createHandle("Cooking noodles, please wait...", 
  myCancellable );

This is, you create a ProgressHandle using some text (shown to the user) and a Cancellable object. And then you start() the handle or finish() it.

By default this "ProgressHandle" object is undeterminate (you'll see a progress bar that is continuosly moving). But you can make it determinate and specify the percentage of task you've already finished (see ProgressHandle javadoc for more info).

If you create your ProgressHandle with a Cancellable task (as I've done) then a small button will appear in the UI that allows the user to cancel the task. (See Figure 50, “Progress UI interface”)

A lazy progress UI

The user interface for the progress bar is automatically shown in the lower-left part of the window. As you can see in Figure 50, “Progress UI interface”.

This special component, that visually shows progress to the user, does not appear immediately. This is, if you "start" your progress handle, then the UI does not appear immediately, but a few seconds later. This is good because if your task executes very quickly then nothing is shown to the user (and nothing gets her distracted!). This is a cool behaviour, I'd say. So don't get confused if your UI does not show immediately, just take it easy (and or have some more noodles cooked in your task ;-)).

Figure 51. Using the I/O APIs

Using the I/O APIs

12.2. The Output Window

Of course we will want to print out the results of our Scheme computations in the NetBeans Output Window. We will also want to read user input from there. [12]

The so called "Output Window" is contained in the I/O APIs, a small package with three classes, two interfaces and a highly scalable mechanism to show very large files in your Swing based application.

To use the "Output Window" you need an "InputOutput" object, used for input (reading stuff from the user) and showing output to the user. And you get an InputOutput object like this:

InputOutput console = 
  IOProvider.getDefault().getIO("Scheme evaluation", true );

(Now, that's what I call a simple API!).

where "Scheme evaluation" is the name of the output tab, and "true" means that we want to open a new tab in case there's none already open (you may want to go take a look at the IOProvider Javadoc for other use cases).

Once you have your "InputOutput" object you can do lots of things:

  • You can select() it, so that it appears on front of other tasks in case it's hidden (you usually do this before starting to output anything to the console, so the user knows that your task is really printing some stuff).

  • You can getOut().println("Hello, world"), for instance, to print out stuff in your output window.

  • And you can do many other things (reading stuff, adding buttons to the console, etc.). To do that you'll need to see the InputOutput javadoc for more details.

12.3. Small summary

So in this section we've learned how to spawn tasks for execution in background, how to show progress to the user and how to print out stuff into the console window.

With these tools we're ready to start implementing our Scheme interpreter.

But that'll require another section. Let's have a rest now. After all our noodle threads are already cooked!

[11] Well, this is not really so, but it's a fairly good explanation, I think.

[12] The Output Window can hold lots of text, and do it quickly and efficiently. The amount of stuff you can hold in the Output Window is limited by the amount of memory in your hard disk, and not in RAM. So don't try to build your own "Output Window". You'll be missing lots of functionality.

blog comments powered by Disqus