Waiting for a cancelled future to actually finish

前端 未结 3 1098
臣服心动
臣服心动 2021-01-12 17:09

I have a SwingWorker which calls some code that does not check for thread interruption. After the call to worker.cancel(true), the worker.ge

相关标签:
3条回答
  • 2021-01-12 17:17

    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) {
    
            }
        }
    
    }
    
    0 讨论(0)
  • 2021-01-12 17:29

    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.

    0 讨论(0)
  • 2021-01-12 17:43

    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();
        }
    
    }
    
    0 讨论(0)
提交回复
热议问题