2

ScheduledThreadPoolExecutor (which implements ScheduledExecutorService) seems to be only running the SwingWorker class once when using the ScheduleAtFixedRate method. The original code is kinda long, so I made a new code that produces the same results below.

import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import javax.swing.SwingUtilities;
import javax.swing.SwingWorker;

public class ScheduledThreadPoolExecutorTest extends SwingWorker<Void, Void>{
    @Override
    protected Void doInBackground() {
        System.out.println("Yay!");
        return null;
    }

    @Override
    protected void done() {
        try {
            get();
        } catch(Exception e) {
            e.printStackTrace();
        }
        System.out.println("Woohoo!");
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(1);
                executor.scheduleAtFixedRate(new ScheduledThreadPoolExecutorTest(), 0, 30, TimeUnit.MILLISECONDS);
            }
        });
    }
}

This yields the results:

Yay!
Woohoo!

Why is ScheduledThreadPoolExecutor running SwingWorker only once? And what can I do to make the SwingWorker run every 30 milliseconds as indicated in the code?

ICanCYou
  • 545
  • 1
  • 5
  • 8

2 Answers2

3

While SwingWorker does implement the Runnable interface, per its API section on the doInBackground() method:

Note that this method is executed only once.

So while its inner run() method may repeatedly run, the doInBackground() will only run once. Not only that, but the run() method is marked final within SwingWorker, and so you can't override it to call doInBackground multiple times.

A better solution is not use a SwingWorker at all but rather a simpler Runnable-derived class.

Hovercraft Full Of Eels
  • 283,665
  • 25
  • 256
  • 373
  • So `doInBackground()` is only called once per class and not once per instance of that class? And also, what if you absolutely need to use SwingWorker because you're using Swing objects and need to call `repaint()` (not shown here, of course, but in the original code)? – ICanCYou Aug 21 '16 at 21:54
  • @ICanCYou: it's called once per instance -- you're only creating one instance though. – Hovercraft Full Of Eels Aug 21 '16 at 21:57
  • @ICanCYou: also you can create multiple SwingWorkers if need be or can queue Swing calls onto the event thread using SwingUtilieis. Note that `repaint()` does not need to be called on the event thread. – Hovercraft Full Of Eels Aug 21 '16 at 21:57
  • @Hovercraft Full Of Eels Is it possible for the executor to schedule a new instance of a SwingWorker every 30 milliseconds using the `scheduleAtFixedRate()` method? – ICanCYou Aug 21 '16 at 22:01
  • @ICanCYou: you could create a SwingWorker within the Runnable that the executor service calls, but this seems redundant. Why not simply queue up Swing code as needed. – Hovercraft Full Of Eels Aug 21 '16 at 22:06
  • @ICanCYou: or you can do the old fashioned `while (true)` loop with `Thread.sleep(...)` within the SwingWorker's doInBackground method itself – Hovercraft Full Of Eels Aug 21 '16 at 22:10
  • I ended up using a `Runnable` instead of a `SwingWorker` as you suggested in your answer. My code works fine now. Thank you. – ICanCYou Aug 21 '16 at 23:12
  • runs forever ([if is there created Top Level Container](http://stackoverflow.com/a/6174462/714968)) – mKorbel Aug 22 '16 at 06:48
2

SwingWorker extends Runnable, however, it uses FutureTask to run its computation.

From the javadoc:

A cancellable asynchronous computation.  This class provides a base
implementation of {@link Future}, with methods to start and cancel
a computation, query to see if the computation is complete, and
retrieve the result of the computation.  The result can only be
retrieved when the computation has completed; the {@code get}
methods will block if the computation has not yet completed.  Once
the computation has completed, the computation cannot be restarted
or cancelled (unless the computation is invoked using
{@link #runAndReset}).

That is, the FutureTask will run only once, if you try to run it again, it will simply return.

public void run() {
    if (state != NEW ||
        !UNSAFE.compareAndSwapObject(this, runnerOffset,
                                     null, Thread.currentThread()))
        return;
    try {
        Callable<V> c = callable;
        if (c != null && state == NEW) {
            V result;
            boolean ran;
            try {
                result = c.call();
                ran = true;
            } catch (Throwable ex) {
                result = null;
                ran = false;
                setException(ex);
            }
            if (ran)
                set(result);
        }
    } finally {
        // runner must be non-null until state is settled to
        // prevent concurrent calls to run()
        runner = null;
        // state must be re-read after nulling runner to prevent
        // leaked interrupts
        int s = state;
        if (s >= INTERRUPTING)
            handlePossibleCancellationInterrupt(s);
    }
}