Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

cleaning up after using a SwingWorker

NB This is not about terminating a worker thread. It's about the aftermath of a normal termination.

I'm calling execute in the EDT, so this spawns a "worker thread".
I'm calling worker.get() in a Callable which I submit to an ExecutorService, specifically a ThreadPoolExecutor.

The task ends OK (i.e. worker.get() returns with a result, and the Callable also ends OK).

I call shutdown() on the Executor Service: method terminates normally.

The puzzle comes when I examine the threads after this. This is in the context of unit testing, and I basically want to kill off all the activity which has been started during the last test.

I'm listing all the threads, and in addition to the various ones which were present before the test started I see this:

# thread Thread[pool-2-thread-1,5,main], state WAITING, alive True, interrupted False, thread group main
# thread Thread[SwingWorker-pool-4-thread-1,5,main], state WAITING, alive True, interrupted False, thread group main

The trouble is, as the tests get run, the newly constructed "Application" object makes a new thread pool each time, and more and more SwingWorkers get created... and none of these threads appears to transition to the Thread.State "TERMINATED" (which presumably would mean they would then cease to be listed).

I then tried calling join on the "SwingWorker-pool-4-thread" Thread... but this causes my current thread to hang.

Can anyone explain what's going on here and how to transition these threads to "TERMINATED"?

later

Response to 2 helpful comments. 10 threads, right. My thinking is just that a test if it fails (or an exception occurs) this can leave things in a troublesome state: suppose, in a functional test, 3 SWs are involved, and all sorts of publish/process stuff is going on, not to mention various Runnables in the EDT. Before moving to the next test I want everything to be shut, closed, ended, killed, returned to a pristine, virginal state!

To ensure all Runnables have ended we have Robot.waitForIdle(). I've also thought up a strategy for ensuring the publish/process thing has ended: SwingWorker, done() is executed before process() calls are finished... . But in fact the latter, useful as it is, presupposes that the testing code knows which SWs to call the method on... I'm looking for a blunderbus, which will just kill em stone dead.

The toString for these Thread objects shows clearly which has been created since starting the latest test... but I don't know how to kill 'em off. Isn't it the case that "WAITING" means that, vampire-like, they might rise up again and do all sorts of bad things? And just leaving vast numbers of "waiting" Threads hanging around, potentially running into their hundreds, seems a bit... untidy!

even later

Ah yes, getting hold of the AppContext object is vital because you need to "reset" this in order to prevent RejectedExecutionException. See my answer for how to do in Jython.

I also note that in the SW API doc it says

Because SwingWorker implements Runnable, a SwingWorker can be submitted to an Executor for execution.

This presumably means that you don't have to use SW's default executor at all. Might be the way to go, but unless needed for another reason in the app code, it kind of violates the principle of not tailoring the app code for the convenience of the testing code...

like image 279
mike rodent Avatar asked Jan 02 '26 05:01

mike rodent


1 Answers

Disclaimer: I'm not sure I see the need to do this, but...

Having dug through the SwingWorker source code, the worker uses the private getWorkersExecutorService method to get the ExecutorService for the current JVM.

This uses the AppContext to store an instance of ExecutorService, from which all instances of SwingWorker (for the JVM) will draw threads from to execute their functionality.

So, if you do something like...

AppContext appContext = AppContext.getAppContext();
ExecutorService executorService = (ExecutorService) appContext.get(SwingWorker.class);
System.out.println(executorService);

will print out something like...

java.util.concurrent.ThreadPoolExecutor@4554617c[Running, pool size = 1, active threads = 1, queued tasks = 0, completed tasks = 0]

(this assumes you've previously started a SwingWorker otherwise it will return null)

But, what does this mean? Well, we can now gain direct access the ExecutorService for all the SwingWorkers (the apocalypse may now start)

This means you can shutdown the service and even remove it from the AppContext, for example...

SwingWorker worker = new SwingWorker() {
    @Override
    protected Object doInBackground() throws Exception {
        System.out.println("Starting");
        Thread.sleep(10000);
        System.out.println("Ending");
        return null;
    }
};
worker.execute();
synchronized (SwingWorker.class) {
    AppContext appContext = AppContext.getAppContext();
    ExecutorService executorService = (ExecutorService) appContext.get(SwingWorker.class);
    System.out.println(executorService);
    System.out.println("Shutting down");
    executorService.shutdownNow();
    try {
        System.out.println("Waiting");
        executorService.awaitTermination(Integer.MAX_VALUE, TimeUnit.DAYS);
        System.out.println("Done");
    } catch (InterruptedException ex) {
        ex.printStackTrace();
    }
    appContext.remove(SwingWorker.class);
}

In my testing, it outputs...

java.util.concurrent.ThreadPoolExecutor@4554617c[Running, pool size = 1, active threads = 1, queued tasks = 0, completed tasks = 0]
Shutting down
Waiting
Starting
Done

So the worker doesn't even get a chance to start. If I place a delay in (after calling execute), shutdownNow basically hammers it and won't wait for the worker to finish, but if I use shutdown it does, so, there you have it.

I should add, AFAIK WAITING means they are doing nothing. In the case of the SwingWorker, these threads are pooled, so, yes, the worker will try and re-use them if it can, BUT because in the "normal" operations of the SwingWorker, the worker needs to deal with the fact that the doInBackground method might explode horrible, it's already designed to deal with it (which is why get throws an Exception), so unless you have a particular use case, I'm not really sure you need to go to this much trouble, just saying

like image 189
MadProgrammer Avatar answered Jan 03 '26 21:01

MadProgrammer



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!