Search code examples
javamultithreadingswingconcurrencyswingworker

How can I implement a method that returns a result to Event Dispatch Thread?


I have the following method:

public Object someMethod(Object param) {
    return performLongCalculations();
}

Some time consuming calculations I placed in a separate method:

private Object performLongCalculations() {
    ...
}

The problem is that it returns some calculation result. These calculations are performed in the EDT and lead to freezing the UI.

I tried to solve it with the following way:

public Object someMethod(final Object param) {
    Object resultObject = new Object();
    ExecutorService executorService = Executors.newFixedThreadPool(1);
    Future<Object> future = executorService.submit(new Callable<Object>() {
        @Override
        public Object call() {
            return performLongCalculations(param);
        }
    });
    
    executorService.shutdown();

    try {
        resultObject = future.get();
    } catch (InterruptedException | ExecutionException  e) {
      // ...
    }
    return resultObject;
}

But the thread is blocked on the call to future.get(); until the calculations are completed. And I think it also runs in EDT.

Next I tried to use SwingWorker:

public Object someMethod(final Object param) {
    SwingWorker<Object, Void> worker = new SwingWorker<Object, Void>() {
        @Override
        protected Object doInBackground() {
            return performLongCalculations(param);
        }

        @Override
        protected void done() {
            try {
                get();
            } catch (InterruptedException e) {

            }
            catch (ExecutionException e) {

            }
        }
    };
    worker.execute();
    // what should I return here?
}

Here I need to return the result, but it returns before the end of the thread that runs in parallel with EDT.


Solution

  • Your question essentially is:

    How can I return a value directly into my Swing GUI from a method where the solution is obtained from long-running code called within a background thread?

    And the answer, succinctly, is: you don't.

    Trying to do this in any way, shape or fashion would mean forcing the background thread to block the GUI event thread until the background thread has completed its task, and if the task takes any appreciable time, then this will always cause the GUI to freeze. Instead, you must extract the information when the background thread has completed, and not get the result from the method itself. This is usually done using a call-back mechanism of some sort.

    For example, in this code:

    public void someMethod(final Object param) {
        SwingWorker<Object, Void> worker = new SwingWorker<Object, Void>() {
            @Override
            protected Object doInBackground() {
                return performLongCalculations(param);
            }
    
            @Override
            protected void done() {
                try {
                    Object something = get();
                    
                    // (A)
                    
                } catch (InterruptedException e) {
                    // do handle these exceptions!
                }
                catch (ExecutionException e) {
                    // do handle these exceptions!
                }
            }
        };
        worker.execute();
        
        // (B)
    }
    

    You would give the result to the GUI at location (A) not as a return value from the method, location (B)

    Alternatively, you could attach a PropertyChangeListener to the SwingWorker, listen for when Worker's state property changes to SwingWorker.StateValue.DONE, and then call .get() on the worker and push the value returned onto the GUI. This is my preferred way of doing this because it usually allows for lower code coupling.