Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Waiting for a cancelled future to actually finish

I have a SwingWorker which calls some code that does not check for thread interruption. After the call to worker.cancel(true), the worker.get() method will throw CancellationException immediately (as it is supposed to). However, since the background task's code never checks for its thread to be interrupted, it happily continues executing.

Is there a standard way to wait for the background task to actually finish? I'm looking to show a "Cancelling..." message or something of the sort and block until the task has terminated. (I'm sure I could always accomplish this with a flag in the worker class if necessary, just looking for any other solutions.)

like image 460
Paul Blessing Avatar asked Aug 19 '10 13:08

Paul Blessing


3 Answers

I played around with this a bit and here's what I came up with. I'm using a CountDownLatch and basically exposing its await() method as a method on my SwingWorker object. Still looking for any better solutions.

final class Worker extends SwingWorker<Void, Void> {

    private final CountDownLatch actuallyFinishedLatch = new CountDownLatch(1);

    @Override
    protected Void doInBackground() throws Exception {
        try {
            System.out.println("Long Task Started");

            /* Simulate long running method */
            for (int i = 0; i < 1000000000; i++) {
                double d = Math.sqrt(i);
            }

            return null;
        } finally {
            actuallyFinishedLatch.countDown();
        }
    }

    public void awaitActualCompletion() throws InterruptedException {
        actuallyFinishedLatch.await();
    }

    public static void main(String[] args) {
        Worker worker = new Worker();
        worker.execute();

        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {

        }

        System.out.println("Cancelling");
        worker.cancel(true);

        try {
            worker.get();
        } catch (CancellationException e) {
            System.out.println("CancellationException properly thrown");
        } catch (InterruptedException e) {

        } catch (ExecutionException e) {

        }

        System.out.println("Awaiting Actual Completion");
        try {
            worker.awaitActualCompletion();
            System.out.println("Done");
        } catch (InterruptedException e) {

        }
    }

}
like image 197
Paul Blessing Avatar answered Sep 29 '22 09:09

Paul Blessing


The closest thing to a standard, or ready-made way of doing this is the progress property and/or the publish/process method pair provided by SwingWorker. You can set this to a "I'm finished" value at the end of the method to indicate the background work is done. The thread waiting on the swing worker can put up a "Canceling..." message and periodically check the progress to see if it has reached completion. If the waiting thread is the swing EDT, then you will need to use a Timer to periodically check the progress property and clear the cancel message when done.

Here's some example code that runs a stubborn background thread, which is canceled, and then waits until the progress reaches 100.

@Test
public void testSwingWorker()
{
    SwingWorker worker = new SwingWorker() {

        @Override
        protected void process(List chunks)
        {
            for (Object chunk : chunks)
            {
                System.out.println("process: "+chunk.toString());
            }
        }

        @Override
        protected void done()
        {
            System.out.println("done");
        }

        @Override
        protected Object doInBackground() throws Exception
        {
            // simulate long running method
            for (int i=0; i<1000000000; i++)
            {
                double d = Math.sqrt(i);
            }
            System.err.println("finished");
            publish("finished");
            setProgress(100);
            return null;
        }
    };
    Thread t = new Thread(worker);
    t.start();

    try
    {
        worker.get(1, TimeUnit.SECONDS);
    }
    catch (InterruptedException e)        {
    }
    catch (ExecutionException e)        {
    }
    catch (TimeoutException e)        {
    }

    worker.cancel(true);

    // now wait for finish.
    int progress = 0;
    do
    {
        try
        {
            Thread.sleep(1000);
        }
        catch (InterruptedException e)
        {
        }
        progress = worker.getProgress();
        System.out.println(String.format("progress %d", progress));
    }
    while (progress<100);
}

An alternative approach is to use the publish\process method pairs to push a special value indicating that the background thread has finished into the EDT. Your process override method in SwingWorker then picks up this special value and hides the "Canceling..." message. The advantage with this is that no polling or timers are needed. The example code shows that although done is called as soon as the task is canceled, the publish/process method pairs still work even when the task is cancelled.

like image 31
mdma Avatar answered Sep 29 '22 11:09

mdma


Inspired by the solution by Paul Blessing I improved it a little to become a class, you can subclass to get the desired funcitonality:

class AwaitingWorker<T,V> extends SwingWorker<T, V> {

    private final CountDownLatch actuallyFinishedLatch = new CountDownLatch(1);

    /**
     * Override this to do something useful
     */
    protected abstract void performOperation();

    @Override
    protected final T doInBackground() throws Exception {
        try {
            return performOperation();
        } finally {
            actuallyFinishedLatch.countDown();
        }
    }

    public void awaitActualCompletion() throws InterruptedException {
        actuallyFinishedLatch.await();
    }

}
like image 29
Martin Modrák Avatar answered Sep 29 '22 11:09

Martin Modrák