Better control of what SwingWorker does for you

SwingWorker is a great tool and a part of the standard Swing in JDK now. It does crucial job for any decent Swing application – allows us to execute long running heavy tasks on a separate thread in a background and update all of our UI models and components on UI dispatch thread. Otherwise the only heart and pumping engine of Swing UI, event dispatch thread or EDT, will be busy with moving heavy stuff and poor users won’t get a change to enjoy your wonderful application, starring at frozen pressed menu items or buttons instead.

 

It’s easy to trust such a great tool and always carelessly call it’s execute() method, not worrying what’s going on behind the scenes, but this is multi-threading and you better control every aspect of it in your application. So, what’s happening when you do something like this?

SwingWorker<String, String> work = new SwingWorker<String, String>() {
        @Override
        protected String doInBackground() throws Exception {return "";
        }

        @Override
        protected void process(List<String> chunks) {}

        @Override
        protected void done() {}
    work.execute();

When you call execute(), your SwingWorker instance gets executed by itself, and uses internal Executor service, which is fixed size thread pool with 10 threads in current versions of JDK. So if you application is big, and accidentally you have more than 10 background tasks running, all of the subsequent tasks will wait for the next available thread, which may hit the user experience significantly. More than that, recently there was a bug in the JDK regarding SwingWorker not able to execute more than one task at a time.

 

So looking in the future and expecting your application to grow and even more important, willing to control multi-threading behavior which is crucial to have better user experience with Swing UI applications, it’s better to do the execution of all your background tasks yourself. We just need to create our own Executor service, somehow expose it to all UI modules making use of SwingWorker to do background tasks, and whenever needed, change its implementation or debug it if application shows unsatisfactory behavior running a lot of background tasks.

 

The easiest way would be providing a service created with a help of a factory, something similar to the following:

public interface BackgroundTaskService {
    /**
     * Executes SwingWorker task.
     * @param worker Worker to be executed.
     */
    void execute(SwingWorker<?,?> worker);
    /**
     * Executes Runnable on background thread, it's up to
     * Runnable's code to call invokeLater() to update the UI. Do
     * not update or work with UI from the Runnable.
     * 
     * @param work Code to execute on background thread.
     */
    void execute(Runnable work);
}

This interface describes the responsibilities of a service, and we can create concrete implementations, for example, based on cached thread pool which has unlimited number of threads, cached as they get created and re-used when they are freed:

public class CachedBackgroundTaskService implements BackgroundTaskService {

    private ExecutorService executorService = Executors.newCachedThreadPool();

    @Override
    public void execute(SwingWorker<?, ?> worker) {
        executorService.execute(worker);
    }

    @Override
    public void execute(Runnable work) {
        executorService.execute(work);
    }
}

We can of course create other implementations, and even test how an application would work without background tasks by creating special implementation which just calls run() method of the SwingWorker or Runnable. That would definitely demonstrate a nightmare of running complex Swing application all in one event dispatch thread.

 

The different issue is how to provide all of your UI modules with BackgroundTaskService implementation. That’s a well known pattern of Service Locator (e.g. <url> http://martinfowler.com/articles/injection.html#UsingAServiceLocator</url>). The simplest way of course is to use singleton factory, which allows you to create a shared instance of service or multiple instances if you’d like. You then have a couple of static methods to get a current instance and set a new instance if you want to replace implementation with test stub or different approach.

package com.porty.swing;

import com.porty.swing.service.BackgroundTaskService;
import com.porty.swing.service.CachedBackgroundTaskService;

/**
 * Factory which provides access to current implementation of background task service.
 * It's instance could be substituted in order to change default library implementation.
 * 
 * @author iportyankin 
 */
public class BackgroundTaskServiceFactory {
    private static BackgroundTaskServiceFactory instance;
    // we use this implementation by default
    private CachedBackgroundTaskService taskService = new CachedBackgroundTaskService();

    public static BackgroundTaskServiceFactory getInstance() {
        if ( instance == null ) {
            instance = new BackgroundTaskServiceFactory();
        }
        return instance;
    }

    public static void setInstance(BackgroundTaskServiceFactory instance) {
        BackgroundTaskServiceFactory.instance = instance;
    }

    /**
     * Obtains currently used implementation of background task service.
     * @return Background task service
     */
    public BackgroundTaskService getBackgroundTaskService() {
        return taskService;
    }
}

The other approach would be using some sort of dependency management which is a good idea in general for programming Java Swing UI to be able to alter its behavior quickly. One of the candidates could be Google Guice, easy to use and bind to different implementations.

You can ask what’s the need in that additional abstraction level of background executor if we could just share an instance of ExecutorService between all of the UI modules in the application and make sure they all use it to execute their tasks. The good thing is that we can also provide some additional functionality in the new service related only to UI applications, still keeping the execution done by powerful Java Concurrency library. Most of the time we want the progress to be shown to the user, either indeterminate or determinate, depending on how much information is available to the task. Instead of coding the sequence of showing the progress bars and guarantee to hide them when task is done or failed or cancelled we can introduce a handy calls for that and increase simplicity and elegancy when executing UI tasks.

As an example, let’s start with indeterminate progress message, non-interruptible by the user, a bit simpler than well-defined determinate progress bar, and definitely simpler than interruption-aware automated task support. Let’s add a new method into our background execution interface:

/**
     * Executes runnable showing the progress bar with the message until the task is complete.
     * @param work Work to be executed.
     * @param message Message to be shown
     * @param components Components to be disabled while task is executing
     */
    void executeIndeterminate(Runnable work, String message, Component... components);

The purpose of this method will be executing the given task in the background thread, under defined execution policy, and while the task is executed, showing the indeterminate progress message of some form, automatically hiding it when the background activity is over. This happens very frequently in Swing/any other single-threaded (or thread-confined) UI toolkit applications. Additionally, we’ll allow client programmer to disable set of components when task starts and enabled them back when it ends – pretty frequent activity as well.

Since the method itself doesn’t define how final task is executed and only builds common infrastructure, we can implement it in a base abstract class, having concrete implementations to only define how to execute tasks and re-use the complex UI-related framework from it.

First, we need to create a component which will show the indeterminate progress bar somehow. Since we don’t know which window or layout to rely on and stay generic, let’s use simplistic blocking modal dialog with an indeterminate progress bar and a label with the message. It’s part of the Swing Tools project and located in com.porty.swing.dialog package.

/**
 * Abstract implementation of the background service with some UI work prepared.
 * Has no actual execution code.
 * 
 * @author iportyankin 
 */
public abstract class AbstractBackgroundTaskService implements BackgroundTaskService {
    protected Component createProgressComponent(String message) {
        BlockingProgressDialog dialog = new BlockingProgressDialog(null, message);
        dialog.setVisible(true);
        WindowUtils.centerWindow(dialog);
        return dialog;
    }

    protected void destroyProgressComponent(Component c) {
        ((BlockingProgressDialog) c).dispose();
    }
}

These two methods are protected so you can override them and provide progress implementation more elegant and suitable for your application needs.

Then we need to wrap the background call with UI setup and tear down code and execute the final sequence. It sounds simple, i.e. create and show component before running task and dispose it after it’s done. Naive implementation could look like that:

@Override
    public void executeIndeterminate(final Runnable work, final String message, final Component... components) {
        final List<Component> uiProgressComponent = new ArrayList<Component>(1);
        // disable the components
        SwingUtilities.invokeLater(new Runnable() {

            @Override
            public void run() {
                if (null != components) {
                    // disable components
                    for (Component next : components) {
                        next.setEnabled(false);
                    }
                    // create a progress component
                    uiProgressComponent.add(createProgressComponent(message));
                }
            }
        });
        Runnable proxy = new Runnable() {

            @Override
            public void run() {
                try {
                    work.run();
                } finally {
                    // enable the components back
                    SwingUtilities.invokeLater(new Runnable() {

                        @Override
                        public void run() {
                            if (null != components) {
                                // disable components
                                for (Component next : components) {
                                    next.setEnabled(true);
                                }
                            }
                            // dispose progress component
                            destroyProgressComponent(uiProgressComponent.get(0));
                        }
                    });
                }
            }
        };
        // execute proxy with actual work
        execute(proxy);
    }

What we do here is schedule UI activities to be run later when all pending events are processed, including disabling the components and creating generic progress component, and then immediately run the task itself (using execute() method which will be defined by concrete execution strategy). To be able to catch end of the task, regardless of anything that happens with it, we create a proxy Runnable on top of it and use finally section to do post-mortem activities, again scheduling UI task for later, to re-enable the components and destroy progress bar component. We don’t need any sophisticated concurrency tools here since UI thread and invokeLater() guarantee that first UI task is executed before the second one, so the component will be created and available in the exchange list (we have to use final list – restriction of inner classes access to outer variables).

The beauty of the new method is extreme simplicity and handling of many activities behind the scenes for you – to execute some work with progress and disabling a button while it’s executing is one line of code:

 BackgroundTaskServiceFactory.getInstance().
getBackgroundTaskService().executeIndeterminate(worker, "Working slow...", b);

You can see some of the Swing FEST automated tests in test package for the services for standard UI.

Moving forward, it makes sense to add determinate progress with a callback which allows updating the progress, and cancellable implementation which honors the interruption policy and supplies a standard interrupt button in the UI which does it.

Remember, you can find all of the articles code and tools, as well as latest additions and changes at project code site,http://code.google.com/p/swingtools/.

This entry was posted in Uncategorized. Bookmark the permalink.

27 Responses to Better control of what SwingWorker does for you

  1. Janesa says:

    Kudos to you! I hadn’t tohhgut of that!

  2. Kevlyn says:

    You have more useful info than the British had clonoies pre-WWII.

  3. Thanks for the share!
    Hellen

  4. Gerrie says:

    Heck yeah this is exactly what I neeedd.

  5. puttbctuwdu says:

    LYr1zl , [url=http://wodsscvrxzxw.com/]wodsscvrxzxw[/url], [link=http://hsuqiggmhafn.com/]hsuqiggmhafn[/link], http://qrddrweuvilv.com/

  6. 4473633 says:

    4473633 beers on the wall. sck was here

  7. I’m not sure exactly why but this weblog is loading very slow for me. Is anyone else having this problem or is it a issue on my end? I’ll check back later on and see if the problem still exists. Baufinanzierungsrechner Online

  8. Admiring the dedication you put into your site and in depth information you present. It’s nice to come across a blog every once in a while that isn’t the same outdated rehashed information. Excellent read! I’ve saved your site and I’m including your RSS feeds to my Google account. Krankenkassen Preisvergleich

  9. Do you mind if I quote a few of your posts as long as I provide credit and sources back to your website? My website is in the exact same niche as yours and my users would certainly benefit from a lot of the information you provide here. Please let me know if this okay with you. Thanks a lot!
    Kredite Sofort Schufa

  10. Have you ever considered creating an e-book or guest authoring on other websites? I have a blog centered on the same ideas you discuss and would love to have you share some stories/information. I know my audience would appreciate your work. If you’re even remotely interested, feel free to send me an e-mail.
    Sofortkredite Online Kredite Schufa

  11. Most powerful&cost effective SEO and website traffic service in world get up to 100

  12. a2667984 says:

    I’ve said that least 2667984 times. The problem this like that is they are just too compilcated for the average bird, if you know what I mean

  13. After I originally commented I clicked the -Notify me when new comments are added- checkbox and now every time a remark is added I get four emails with the identical comment. Is there any approach you may remove me from that service? Thanks! best watch store

  14. LuzTarvis says:

    Aw, this was a really nice post. In concept I wish to put in writing like this additionally

  15. I don’t know whether it’s just me or if perhaps everyone else experiencing issues with your blog. It appears like some of the text within your content are running off the screen. Can somebody else please provide feedback and let me know if this is happening to them as well? This may be a problem with my web browser because I’ve had this happen before. Kudos Kredit ohne Schufa

  16. Hello there! This is my first visit to your blog! We are a team of volunteers and starting a new project in a community in the same niche. Your blog provided us useful information to work on. You have done a wonderful job! Kredite ohne Schufa

  17. Hi, I can’t understand how to add your site in my rss reader. Can you Help me, please

  18. Hi! I’m at work surfing around your blog from my new iphone 3gs! Just wanted to say I love reading your blog and look forward to all your posts! Keep up the outstanding work! gunstige haftpflichtversicherung

  19. When I originally commented I clicked the “Notify me when new comments are added” checkbox and now each time a comment is added I get three e-mails with the same comment. Is there any way you can remove me from that service? Thank you! Kredit ohne Schufa

  20. Hi, Fantastic post Thanks!

  21. Hi there! Do you use Twitter? I’d like to follow you if that would be ok. I’m definitely enjoying your blog and look forward to new updates. Sofortkredit ohne Schufa Online

  22. Hey, I think your blog might be having browser compatibility issues. When I look at your blog in Safari, it looks fine but when opening in Internet Explorer, it has some overlapping. I just wanted to give you a quick heads up! Other then that, excellent blog! Sofortkredite ohne Schufa

  23. With havin so much written content do you ever run into any issues of plagorism or copyright violation? My site has a lot of completely unique content I’ve either written myself or outsourced but it looks like a lot of it is popping it up all over the web without my agreement. Do you know any ways to help stop content from being stolen? I’d really appreciate it. Sofortkredit Schufa

  24. Awesome blog! Do you have any hints for aspiring writers? I’m hoping to start my own website soon but I’m a little lost on everything. Would you suggest starting with a free platform like WordPress or go for a paid option? There are so many options out there that I’m totally overwhelmed .. Any ideas? Many thanks! haftpflichtversicherung vergleich

  25. My coder is trying to persuade me to move to .net from PHP. I have always disliked the idea because of the expenses. But he’s tryiong none the less. I’ve been using Movable-type on numerous websites for about a year and am concerned about switching to another platform. I have heard great things about blogengine.net. Is there a way I can import all my wordpress posts into it? Any kind of help would be really appreciated! haftpflichtversicherung vergleich

  26. a4822950 says:

    I’ve said that least 4822950 times. The problem this like that is they are just too compilcated for the average bird, if you know what I mean