jueves ene 19, 2006

Look, Ma: Running SwingWorkers...

See ma? They're running there...

Screenshot of test application

So continuing my last post I decided to build an easy-to-use SwingWorkerManager to monitor how my SwingWorkers run in the wild.

I've built a SwingWorkerManager.getInstance() singleton to which I "submit( SwingWorker )" for execution. The SwingWorkerManager has a little ListModel that keeps track of the last executed SwingWorkers (by adding PropertyChangeListeners to them) and monitors their activity.

Apart of this ListModel the SwingWorkerManager can create also a ListCellRenderer to better visualize SwingWorker progress.

Source code (LGPL'ed) is available. Of course you can also run this little Java WebStart demo (note: JDK 5.0 required). Instructions for using the SwingWorkerManager are included in the javadoc documentation.

Sizing the pool...

So as I said before this SwingWorkerManager uses an internal thread pool to execute SwingWorkers. As Jeffrey wisely pointed out in his comment, the size of the pool should depend on the number of available processors NCPU in the environment (that is, it should be a linear function of NCPU=Runtime.getRuntime().availableProcessors()). So, continuing our previous discussion:

M = F \* NCPU < N 

Where M is the number of threads in the pool, F is a constant, NCPU is the number of CPUs in the system and N is the number of scheduled tasks.

Since usually many threads are just waiting for I/O it would be a waste of time to set F=1 (since then many CPUs would be just hanging around waiting for I/O). So I think a good F value could be around 3. That way we'll kick those lazy CPUs. You know, CPU speeds are very high nowadays, and CPU cycles are probably cheaper than any other kind of resource.

So let's assume F=3 is a good bet. Are we missing something? Well, er.. what about those slow ones?

Come on, you lazy one, hurry up, get out of my way...

Assume you're downloading a 10 Gb file from somewhere. You, of course, want to use a SwingWorker for that. But that's gonna take quite some time. And we're using a fixed thread pool, right? So that task is going to reduce the size of our fixed thread pool (and, thus, reduce the responsiveness of our GUI).

How could we handle this? Well, two solutions come to mind...

  • a) Increase M slightly to make room for these slow ones.
  • b) Use another fixed thread pool to schedule these very long-running, very time-consuming slow processes.
  • c) Use one of the above and provide a timeout setting for scheduling tasks. That way those tasks that have not finished before the given timeout value would be automatically interrupted.

The two first ones are not difficult to implement...

Solution a) would require this value for M:

M = F \* NCPU + SLOW < N 

Where SLOW is a fixed value that approximates the number of simultaneous slow processes you think may be running in your application.

And solution b) would require adding a new fixed thread pool (of size, say, SLOW) and a new method (say "submitSlowTask( SwingWorker )") that schedules things into this new fixed thread pool.

The third solution won't be too difficult to implement, either (by slightly increasing M to allocate timeout watcher SwingWorkers).

What do you think? Which one do you like best? Come on, give me a hand here! ;-)

The wonderfully complex world of task scheduling

Many Task Scheduling algorithms are NP hard problems. The SwingWorkerManager presented here implements a simple FIFO (First In-First Out) solution to scheduling SwingWorkers in a set of threads.

SwingWorkerManager may be a good way to experiment different task scheduling algorithms (say, in a Computer Science classroom), but I think a FIFO is good enough for me. And, of course, I can watch my SwingWorkers running. See ma? I hope it's good for you too (and if not, then just complain here! ;-)).

Please let me know of any improvements or bugs.

Happy swinging,
Antonio

domingo ene 08, 2006

SwingWorker: Throttling and monitoring?

The problem

I have a lot of RSS feeds to read. And I want to read them in my Swing application without blocking the event dispatch thread.

So, of course, I want to use SwingWorkers to fetch and parse all RSS feeds.

My first approach would be to create a SwingWorker for each of the RSS feed URLS. The SwingWorker would then download the RSS feed from the given URL (using a java.net.HttpURLConnection, for instance) and then parse it.

So I instantiate 200 SwingWorkers, each one holding a different URL, and "execute()" them. Cool.

But... wait a moment. 200 SwingWorkers? That's 200 threads, right?

Well, yes. That's 200 threads. And that's a problem too!

So, to summarize:

If you have a big number of tasks to execute asynchronously don't use a thread for each one.

Most operating systems won't complain directly if you create a big number of threads, but probably the system will be slow. There will be a lot of context switching between threads. And synchronization between them (if any) may be a problem.

Throttling

So in order to keep resource-consumption under control we would probably want to introduce some throttling. This is, we want to define a number M of simultaneous threads to execute those N tasks. And, of course, having M < N.

A pool of M threads seems a good idea to me. Building a pool of threads using Java 5 is a piece of cake:

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
...
int M = 3;
ExecutorService threadPool = Executors.newFixedThreadPool( M );

And, well, executing SwingWorkers using that thread pool is also a piece of cake: we just need to invoke the "submit()" method on our Executor. Like this:

SwingWorker mySwingWorker = ...;
threadPool.submit( mySwingWorker );

Note that the SwingWorker can be cancelled as usual, this is, the SwingWorker semantics are not lost if you use your preferred ExecutorService. What I mean is that in order to cancel a SwingWorker you still can use the "cancel()" command as usual (and it's not necessary to handle the threadPool in any way). Like this:

mySwingWorker.cancel();

Having an ExecutorService is handy if you have a lot of things to do: you submit them all and that's all. The FixedThreadPool places all the pending tasks in an unbound queue. And then starts executing them in order. When one task finishes the thread is returned to the pool. If there are any pending tasks to be executed then the recently released thread starts executing one of those.

So, for instance, if we have N SwingWorkers to be executed stored in an ArrayList we can submit them all for execution with the following code:

ArrayList<SwingWorker> mySwingWorkers = ...
// This loops over all N workers to execute...
for( SwingWorker worker : mySwingWorkers )
  threadPool.submit( worker );

The Singleton Pattern

It may be interesting to have a reference to that ExecutorService handy in our application. We can achieve this by using the Singleton pattern. Something like this:

import ...;
public final class SwingWorkerExecutor
{
  /\*\* Number of threads in the pool. \*/
  private static final int M = 3;

  /\*\* The ExecutorService. \*/
  private static ExecutorService threadPool
    = Executors.newFixedThreadPool(M);

  /\*\*
   \* Schedules a SwingWorker for execution.
   \* @param aWorker a SwingWorker to execute.
   \*/
  public static <T,V> void execute( SwingWorker<T,V> aWorker )
  {
    threadPool.submit( aWorker );
  }
}

So we can say something like:

  SwingWorkerExecutor.submit( mySwingWorker );

What else? Monitoring?

We can also take advantage of this SingletonPattern in some other interesting ways. Monitoring, for instance. This is what I'm thinking of currently:

  • Make the SwingWorkerExecutor keep a ListModel with the list of the currently running SwingWorkers.
  • Or, even better, make the SwingWorkerExecutor keep a TableModel with the list of the currently running SwingWorkers, and their current progress.
  • Keep statistics inside the SwingWorkerExecutor about the average wait time (in the unbounded queue) and the average run time. This may be interesting to fine-tune an appropriate value for M (the number of threads) for different scenarios.
  • Build a mechanism for allowing time-out on execution of SwingWorkers

So I was wondering what you think. Do you see any other uses for such an hypothetical "SwingWorkerExecutor"? Any ideas or suggestions?

Cheers,
Antonio

UPDATE:

As Vaidya points out the SwingWorker implementation in JDK 6 has a pool of 10 threads, so it won't ever fire 200 threads.

martes dic 06, 2005

Cooler, faster SwingWorkers

Do you remember our experiment with Amdahl's Law? Do you remember Jeffrey's table with the results of running a Swing, multi-threaded, application in a dual core system? (He measured a reduction in running time in those tables in his dual core box). Do you remember when I talked about Herb Sutter (of C++ world fame) urging people to go multithread?.

Well, the fact is that the time has come for us to be able to run up to 32 simultaneous threads within a single chip. It's not dual core, but dual-dual-core. This is, it contains four cores. It's not hyper-threaded, but hyper-hyper-hyper-hyper threaded. Each one of those four cores can run eight threads. That means that you can run 1+1+1+1+ 1+1+1+1+ 1+1+1+1+ 1+1+1+1+ 1+1+1+1+ 1+1+1+1+ 1+1+1+1+ 1+1+1+1 threads at the same time.

That-is-impressive. I'm delighted. But wait, there's more. For those of use worried about Global Warming news are even better. The beast runs with a low power consumption. Since high frequency signals don't have to travel long distances out of the chip, there's little radiation overhead (well, there're much more technical reasons for low power consumption, of course). I've got a bulb around consuming 60 Watts. The chip consumes just 73 Watts. Probably less than this Intel Pentium IV I'm running now. (By the way, I've been trying to find about Pentium processor power consumption without success, has anyone got any details on this? A comment would be greatly appreciated).

I think today is a great day for Sun and for us enjoying multithreading. (And well, of course, for all Sun customers wishing to save some bucks in the elecricity bills and in real state bills. There's even a savings calculator here.)

Now I'll start saving those $3000 to buy one of those boxes. Or, even better, I'll try to think about a good idea for the coolthreads project at java.net, so that I can earn some cash and get one of those flipping servers. What about reading 32 RSS feeds at once and caching them? (Oh, well, that's just what bloglines does).

Any ideas, anyone?
Thanks,
Antonio

martes sep 13, 2005

SwingWorker: summary of recommendations

So as promised I'm posting some sort of summary with the suggestions (idioms?) for working out Swing with SwingWorkers. Some sort of collection of previous postings. After this I'll move to other things. I am thinking of building a NetBeans module too, because that's very interesting to me. So here we go with the collection:

Happy Swinging,
Antonio

lunes sep 05, 2005

Mandelbrot: Update with new fonts

First of all I would like to thank all people posting on the performance improvements of the Mandelbrot Explorer on multicore systems. Jeffrey Olson's tables are indeed impressive (and appreciated. Thanks Jeffrey!!). Regarding the hyperthreading question I assume hyperthreading gains are observable if one of the threads is waiting (for instance in an I/O operation) so the other thread can benefit of the CPU core. Dual core systems are more performant, of course, and true multi-processor ones may be even more performant.

Now, as promised, the newest release of the Mandelbrot explorer...

I think little people know that Java allows you to use custom fonts in your GUIs. Well, most of the time you'd better use the JVM's built-in fonts because you're sure those are UTF-8 fonts. So by using the built-in fonts you are safe with foreign languages and weird symbols.

Anyway there're some exceptions to the rule and Mandelbrot explorer is one of these. I've downloaded a freeware font from the Internet and bundled it in the jar files of the Mandelbrot explorer. A little magic:

URL url = InfoPane.class.getResource("fonts/LCD-U___.TTF");
if ( url != null )
{
try
{
  InputStream input = new BufferedInputStream( url.openStream(), 4\*1024 );
  LCDFont = Font.createFont( Font.TRUETYPE_FONT, input ).deriveFont(34.0f);
  input.close();
}
catch( Exception e )
{
  LCDFont = new java.awt.Font("DialogInput", Font.BOLD, 24);
}
}
loads the font for me and I'm all set.

In this new release of the Mandelbrot explorer I've solved some bugs (regarding progress computations), so I think it's safe to release the source code too. It's a little bit more documented than the previous one too. Any questions are welcome.

source and JDK 5.0 Java WebStart demo available as well.

Happy Swinging,
Antonio

miércoles ago 31, 2005

SwingWorker: All for one OR one for all?

So is it "all for one" or "one for all"? Well, it depends. It depends on how many processors/cores you have, and it depends on the operating system you have. Let me explain myself.

As you know (Sun's) Java schedules threads in as many processors as you own, at least on most operating systems that take advantage of multi-processor systems:

A major advantage of using native OS threads and scheduling is the ability to take advantage of native OS multiprocessing support transparently. Because the Java HotSpot VM is designed to be insensitive to race conditions caused by preemption and/or multiprocessing while executing Java programming language code, the Java programming language threads will automatically take advantage of whatever scheduling and processor allocation policies the native OS provides.

So, if you have different processors and run different Java threads you automagically take advantage of all those processors.

One for all case

You are running on a multiprocessor system, and one thread may be scheduled in any of those processors; this is the "one for all" case.

All for one case

You run on a single processor system, and thus we are in the "All for one" situation, all threads are scheduled in the same processor.

Mandelbrot and Amdahl's law

So what has all this well-known facts have to do with Mandelbrot? What about Amdahl?

The Mandelbrot fractal is highly parallelizable. Computing the Mandelbrot fractal means basically examining each dot in a complex plane; and each one of those computations for each point in the complex plane has no relation with the rest of the complex plane. So, why not schedule different threads for different regions in the plane? That would allow us to take advantage of multi-processor (multi-core) systems and make things faster, right?

Assume that computing the Mandelbrot fractal takes 95% of time and that rendering it (on the single Swing thread, which is not parallelizable) requires 5% of the time. (These are ball park estimates, obviously, Swing is probably faster than that). According to Amdahl's law we can speed up the Mandelbrot explorer by a factor of 1.9 in a system with 2 processors, a factor of 2.7 in a system with 3, and a (maximum) factor of 20 with lots of processors. That's quite impressive, right?

Obviously the Mandelbrot speed-up is observable in the "one for all" case, where we are running in multiprocessor systems.

But anyway we can also see some speedup in the "all for one" case, when we run on a single processor. Why you ask? Well, because there're regions of the complex plane that are faster to compute and require less iterations, so those will be finished earlier and will be rendered earlier. So the "apparent performance" of the Mandelbrot Explorer will be better. Of course spanning different threads will some more JVM work (synchronization, scheduling), but I think this little overhead is worth the effort.

You can take advantage, too

So, to summarize, you can take advantage of scheduling SwingWorkers even on single-processor boxes. An application that runs different queries against a database (J2EE server, web-service server, RSS feed server, whatever) will greatly benefit of parallelization.

An example

So I have modified the Mandelbrot Explorer program to span different SwingWorkers to compute the Mandelbrot fractal. You can choose between 1x threads, 4x threads or 16x threads to compute the fractal (Choose "Configuration/Configure" in the menu). If things are too fast to notice you may try selecting a different detail level as well. Launch the JDK 5.0 demo here. It's still somewhat buggy but I won't be able to post until next week and I prefer posting it today for you to enjoy . Source code will be available by next week, as soon as I correct some little bugs.

If you own a dual-core system or may run this on a multiprocessor system I would greatly appreciate feedback on performance. I would also like to know if all your cores show activity when exploring the Mandelbrot fractal and, if so, which operating system (and JVM) you're running on. Thanks!

Happy Swing threading,
Antonio

lunes ago 22, 2005

Inform the user... and get out of the way.

So the other day I was watching TV and noticed those funny computers they use in TV. This was about a guy that had nanobots inside (wow!?). Well, the fact is that he was using one of those computers where the information appears slowly, letter by letter, on a green on black screen, with a blinking block cursor after it. And doing some weird noise when displaying each letter. I wonder why a guy with nanobots inside uses those old, slow, noisy computers.

So I decided to build one of those with Swing. I thought it was a nice way to inform the user and don't bother him by asking him to click or press buttons afterwards. Just send a message and then fade away until the message dissappears. Inform the user... and get out of the way.

You can watch the result starting this demo with JavaWebStart (JDK 5.0 required). The demo is signed (with a dummy signature) because it allows you to export screenshots to disk as PNG files (and so needs disk access). Source code here under the CDDL license. I recommend NetBeans 4.1 for easier access to visual designs.

All the magic with this is performed in the "InfoPane.java" panel. A timer to slowly write each letter with a blinking block cursor afterwards (looking at a Unicode character map I found the appropriate character constant). And a fading afterwards to dismiss the message by using alpha colors in the label.

I'm satisfied with the result. A slowly updating message pane that fades away after the message is displayed. Cool! (Music/noise is missing at the moment, maybe next release includes some weird sound too ;-) ).

And, since it is released under the CDDL license, I'll be able to re-use just that panel in other projects (CDDL is a file based license, after all.); and I won't have to reuse the whole project.

So can you, of course ;-).

I think I should blog about how I work with the JLayeredPane in the demo. JLayeredPane and transparency are good companions. Of course you can work with Z-order after JDK5.0; but I think JLayeredPane is completely forgotten nowadays. And it shouldn't. It's a good JComponent after all, and behaves extremely well!

Happy Swinging meanwhile,
Antonio

viernes ago 19, 2005

SwingWorker: the importance of being concurrent

So why all this posts about SwingWorker, you ask? Well, because "Concurrency is the next major revolution in how we write software". That's what Herb Sutter says. And Herb Sutter has been one of my favourite gurus since I first met C++ around 1990. Herb kept the C++ FAQ on those years, and posted weekly exercises for those of us suffering C++. (Yeah, I know he works for Microsoft, nobody is perfect ;-) ).

But, of course, it's not only Herb. Our Chief Technology Officer, Greg Papadopoulos is also aware (PDF) of that.

(Yes, we have lots of innovators on board. Congratulations to the Solaris 10 team that has won the InfoWorld's 2005 Innovation Award !!).

Well, the fact is that most Java Virtual Machines may be able to run different Threads on different processors (or on the same processor but on a different core, or on the same processor but on a different chip thread, or both). You don't have to do special stuff to do that. It just happens. You span a Thread and it runs on whatever processor is idle. That easy. The JVM and the Operating System do that for us.

(See an example with a dual G5 system, for instance)

So there comes my fifth suggestion for SwingWorkers:

Swingworker suggestion V: Use SwingWorkers in your Java applications By using SwingWorkers (or any other threading mechanism of your liking) your application will run (much) faster in multi-core systems, multi-processor systems or processors with chip-multithreading (as Intel's hyperthreading) or a combination of those (as Sun's Niagara). Threading is the way to fully exploit future processors.

Or, as Herb says:

"If you haven’t done so already, now is the time to take a hard look at the design of your application, determine what operations are CPU-sensitive now or are likely to become so soon, and identify how those places could benefit from concurrency. Now is also the time for you and your team to grok concurrent programming’s requirements, pitfalls, styles, and idioms." (Herb Sutter)

Happy Threading,
Antonio

jueves ago 18, 2005

SwingWorker: clean up the mess before leaving

Of course you add a PropertyChangeListener to a SwingWorker's list of listeners to be informed of advances in the progress of computations and to notice changes in SwingWorker's status.

But, when do you remove those listeners? Of course if you don't remove listeners then you keep references from the SwingWorker to the listeners and that may lead to memory leaks.

Of course the safest place to remove listeners is when the SwingWorker is done. Something like this:

public void propertyChange(java.beans.PropertyChangeEvent propertyChangeEvent)
{
  SwingWorker source = (SwingWorker) propertyChangeEvent.getSource();
  if ( "state".equals( propertyChangeEvent.getPropertyName() )
       && (source.isDone() || source.isCancelled() ) )
  {
    source.removePropertyChangeListener( this );
  }
}
So the listener removes itself when the SwingWorker is done. Easy and clean. So here we go with another suggestion for SwingWorkers:

SwingWorker suggestion IV: Clean up listeners when done Each PropertyChangeListener should remove itself from the list of listeners whenever the SwingWorker is done.
Enough for today. Keep swinging,
Antonio

miércoles ago 17, 2005

SwingWorker: timing-out slow ones

So you have a background task that takes too long to finish. You want to set a timeout value, after which the task is automagically cancelled.

Of course there're lots of solutions for handling this. The very first one could think of is keeping track of time in any background loop (if any) and deciding if it's time to timeout the task. Something like this:

public String doInBackground() throws Exception
{
  long startTime = System.currentTimeMillis();
  for( ... )
  {
    // Check if we've been running for more than 100 ms...
    if ( System.currentTimeMillis() - startTime > 100L )
      cancel();
    ... keep on doing things
  }
  ...
That's a naive solution that fails if any of those operations in the loop is a blocking operation, since then the "if" is not evaluated and the operation is not cancelled.

So what's a better solution that works on all cases? Well, the solution requires using ... SwingWorkers!!

Imagine you've got a SwingWorker with your stuff. Let's call this the "worker" SwingWorker.

Now we can build another SwingWorker, a wrapper one, let's call it "Timer". What this "Timer" SwingWorker does is just executing the other SwingWorker and invoking "get(long,TimeUnit)" to wait for the result of the "worker" SwingWorker. If the result is not available within that timeout the "Timer" SwingWorker will cancel the "worker" SwingWorker. Let's see the code...

  @Override
  protected R doInBackground()
    throws TimeoutException, InterruptedException, ExecutionException
  {
    worker.execute();
    try
    {
      return worker.get( timeoutValue, timeUnit );
    }
    catch( TimeoutException timeoutException )
    {
      worker.cancel(true);
      throw timeoutException;
    }
  }
That's easy, right? Well, the drawback is that we need two SwingWorkers. The benefits are that timing is accurate and that the worker SwingWorker is cancelled even with blocking operations.

Obviously you can enhance that as you want. Instead of cancelling the "worker" you could just pop-up a progress dialog, informing the user of the advances of the "worker" SwingWorker and presenting an optional Cancel button, if you like. I'll post examples for this in the future.

Enjoy Swing meanwhile,
Antonio

martes ago 16, 2005

SwingWorker: for SWT too!

It seems that somebody at Javalobby has realized that SWT long running tasks freeze the GUI as well.

Oh, sorry, just thought SWT didn't suffer those problems? Well, the fact is that it does. All GUI frameworks do.

Good news is that SwingWorker may help you there too. Go download the code and change SwingUtilities.invokeLater (or AWTEventQueue.invokeLater) by Display.getCurrent().asyncExec() and you're probably done.

Now, isn't it ironic that Sun's SwingWorker solves IBM's SWT threading issues? ;-)

SwingWorker: shoot just once, please.

Many GUI users don't understand double-clicks. My father, for instance, usually clicks twice on buttons. That's a consequence, I imagine, of experience with bad user interfaces. Windows and Linux users, for instance, have to double-click on icons on the desktop, but click just once on buttons. Weird. Experienced users, like me, are just used to this weirdness and just "behave" as the GUI programmers expect (;-)). People with an innovative, creative mind, like the recently deceased genius Jef Raskin (see movies), just build new user interfaces.

So it's important to consider novice/inexpert users when building GUIs. When using SwingWorkers (or any other threading framework of your liking) it's important to fire Threads just once, although the user double-clicks on buttons.

Otherwise you can fire SwingWorkers twice. And that's probably not what you want. You can see this behaviour in my previous JDBC example. If you click the search button twice quickly then the SwingWorker is fired twice, and the model (the table) contains twice the entries it should.

Single shots: handling multiple clicks

The first thing to deal with is avoiding the user to press the button multiple times, quickly. Well, that's not a big deal with Swing. After JDK1.4 we have a AbstractButton.setMultiClickThreshhold(long) that may be of help. By setting that to, say, 200L (200 milliseconds) we avoid the JButton to generate ActionEvents less than 0.2 seconds apart. So if the user clicks the JButton three times within 150 milliseconds only one ActionEvent is generated.

Now, if you don't use an AbstractButton or derivative you can easily solve the thing yourself. Using timestamps. For instance, for an Action you may use something like this:

public class MyAction
  extends Action
{
  ...
  private long lastTimestamp = 0L;
  public void actionPerformed( ActionEvent anActionEvent )
  {
    long currentTimestamp = System.currentTimeMillis();
    // Threshhold of 200 milliseconds
    if ( (currentTimestamp-lastTimestamp) > 200L )
    {
       lastTimestamp = currentTimestamp;
       ... proceed
    }
  }
}

Single shots: disabling input until done

The second thing to deal with is avoiding the user to press the button until our operation is either done or cancelled.

Meanwhile we may block all user input by using a GlassPane that blocks all user input (but a Cancel option, probably) until the operation is done.

Or we may just disable the action that generated the event, by invoking setEnabled(false) on that JButton (or Action).

Note that, in both cases, it's desirable to include a "Cancel" option that actually cancels the operation and allows the user to try again. If the operation fails, or if it generates a runtime exception not handled by the application, the user may continue working instead of being kept locked. This is critical for the solution with GlassPanes, since GlassPanes will block all user input (you should consider using "finally" clauses that remove the GlassPane whether the operation suceeds or not).

It's also a best practice to inform the user that the operation is in progress meanwhile. Otherwise the user may think the application has hung. Doing that with a GlassPane is a piece of cake (the example above uses Java2D to visually show progress), but if we use the second option (disabling just an action) we should include either a status bar or even a progress bar informing of the progress of the operation. Even better we can update the UI with intermediate results (by publish()-ing and process()-ing them with the SwingWorker) so that the user does not get nervous while waiting.

Example

The Mandelbrot example contains these features. All buttons have a MultiClickThreshold() setting. The appropriate buttons and entries are disabled and enabled (see MandelbrotViewController.java#setWorkingState(boolean) ) depending on the working state, and a blocking ProgressPane is shown to the user while the operation is in progress (allowing as well to cancel the operation).

So here it comes my third suggestion for SwingWorkers (that can be applied to other threading frameworks around).

SwingWorker Suggestion III: Handle multi-clicks, show progress, allow cancel

Be aware of multi-clicks by using timestamps or setting MultiClickThreshold in buttons and then disabling user input where appropriate. While the operations are working visually show progress to the user. Allow the user to cancel the operation if he whishes.

Enough for today. Next time I'll talk about automagically timing-out tasks that have been running for a long time.

Keep swinging,
Antonio

lunes ago 15, 2005

SwingWorker: don't stop that train!

So imagine you're comfortably sleeping at home. Suddenly you wake up and you find out you're sitting in your car, driving at 65 MPH in the highway. That's a dangerous situation, right?

Now imagine you're a JDBC driver and you're comfortably performing a query against a server. You're talking, say, OCI to an Oracle server. Suddenly someone "Thread.interrupt()"s you and then asks you to "close()" the JDBC connection. You may be able to successfully close the connection. Or you may not. Maybe you can close the connection, but the server is left in a weird state, thinking you're still asking for the query.

What I mean is that Thread.interrupt() (or SwingWorker.cancel()) certain operations may not be a very good idea. By interrupting threads you may be interrupting network protocols, for instance, and that may be asking for trouble with some JDBC drivers. You can build a SQL application and interrupt and close the connections perfectly well with a given driver, and completely fail with another driver.

What I really mean is that if you want to suddenly cancel a running process (whatever that is) you should try to do that in the safest, cleanest way. An idea is to use a "boolean wantsToRun=true" flag somewhere in your SwingWorker constructors and keep the things rolling in the "doInBackground()" method as long as "wantsToRun" is true.

So there comes our SwingWorker suggestion number II:

SwingWorker suggestion II: try to cancel() as cleanly as possible Use a flag in your "doInBackground()" loops to check if your SwingWorker is still expected to be run. Since you cannot override "cancel(boolean)" in your SwingWorker (it's final :-( ) then write a method to gracefully ask for cancellation. If you use SwingWorker.cancel(true) to stop your running process doublecheck that everything is correctly cancelled by thoroughly testing your application.
Note that this suggestion applies to SwingWorkers and to any Thread you build.

So, to summarize, think it twice before suddenly stopping trains Threads. Results might be quite different from what you expect.

Happy Swinging,
Antonio

viernes ago 12, 2005

SwingWorker: stop that train!!

Or how to cancel SwingWorkers.

Imagine you have a SwingWorker that does some computations in the "doInBackground()" method. You want to cancel execution and you invoke "SwingWorker.cancel()". But, suprisingly, the job being performed is not cancelled. Your SwingWorker just keeps on doing the job!!

Why, you ask? Well, to understand why we need to go a little bit deep on how Threads are interrupt()ed (because SwingWorker.cancel() is just a hidden call to Thread.interrupt()). When you invoke "Thread.interrupt()" you're just setting a flag on the Thread, the "interrupt status" mentioned in the API, but you're not really interrupting the Thread, you're just setting a status flag.

Case I: If the operation you're performing on the "doInBackground()" method is not doing any I/O or is not wait()ing or sleep()ing (or doing those things mentioned in the API) then the Thread just keeps on running, executing its "run()" method (or "doInBackground()" in our case). No "InterruptedException" is thrown in this case.

Case II: On the other hand, if the operations in your SwingWorker.doInBackground() are performing I/O operations (such as reading data with JDBC or writing to disk) then the interrupted status is detected and an InterruptedException is thrown and, of course, the Thread just stops whatever it's doing.

So, if your "doInBackground()" fits in the "Case I" category then you need to take that into account when building your "doInBackground()" method. A good idea is to test if "Thread.currentThread().isInterrupted()" is true inside any loop you may have in your method. Another option is to invoke "Thread.currentThread().sleep()" (or Thread.sleep()) for some time in your loop. By doing so you allow "SwingWorker.cancel()" to really cancel whatever you're doing.

So here it comes my first suggestion for SwingWorkers:

SwingWorker suggestion I: make your doInBackground() cancellable
If your "doInBackground()" does not perform any I/O then either periodically sleep() from a little bit of time or check if Thread.currentThread().isInterrupted().

(Note, by the way, that this applies to any Thread you extend, not just SwingWorkers!).

If you see the source code of MandelbrotSwingWorker.java you'll notice that I'm sleeping for 1 millisecond each 5% of work done. Exact location is here:

120  if ( 0 == progress%5 ) 
121  {
122    setProgress( progress  );
123    // This is important!! I'll comment on this later.
124    Thread.sleep(1L);
125  }

By doing so I allow a MandelbrotSwingWorker (a SwingWorker that does not have any I/O operations) to be cancelled (and, well, slow things a little bit too, MandelbrotSwingWorker is too fast on my Pentium IV ;-).

I think this is good enough for today. Next week I'll talk about why cancelling SwingWorkers may not be a very good idea.

Happy Swinging,
Antonio

jueves ago 11, 2005

Mandelbrot on fire

So I have been experimenting with SwingWorker, as promised, and this:

is the result.

source code and binaries provided. To run the demo you'll need SwingWorker.jar and a JDK5.0 JVM. If you have JavaWebStart (JDK 5.0) installed then press here

Then run the stuff as this:

java -cp MANDELBROT.jar:SwingWorker.jar -jar MANDELBROT.jar
Once upon a time Mandelbrot fractals where fashionable. I remember programming them a long time ago, and I also remember how slow to compute they were. I thought that math would be a nice thing to do in a SwingWorker. The fact is that I can navigate the Mandelbrot fractal (and zoom and pan it) quite fast in my current computer (a Pentium IV)!!. Things have improved a lot since then!!

The examples

By building this simple demo I've seen some do's and don'ts with SwingWorker. I'll post on this later this week.

Keep swinging,
Antonio

UPDATE:
The correct command line is
java -cp swing-worker.jar:MANDELBROT.jar org.jdesktop.swingworker.demos.mandelbrot.MandelbrotViewController

About

swinger

Search

Archives
« abril 2014
lunmarmiéjueviesábdom
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
    
       
Hoy