如何实现将结果返回给事件调度线程的方法?

问题描述

我有以下方法

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

一些耗时的计算我放在一个单独的方法中:

private Object performlongCalculations() {
    ...
}

问题是它返回了一些计算结果。这些计算在 EDT 中执行并导致冻结 UI

我尝试通过以下方式解决它:

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;
}

但是线程在调用 future.get(); 时被阻塞,直到计算完成。而且我认为它也在 EDT 中运行。

接下来我尝试使用 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?
}

这里我需要返回结果,但它在与 EDT 并行运行的线程结束之前返回。

解决方法

您的问题本质上是:

如何通过从后台线程中调用的长时间运行代码获取解决方案的方法将值直接返回到我的 Swing GUI 中?

简而言之,答案是:你没有。

尝试以任何方式、形状或方式执行此操作将意味着强制后台线程阻塞 GUI 事件线程,直到后台线程完成其任务,并且如果该任务花费任何可观的时间,那么这将始终导致GUI冻结。相反,您必须在后台线程完成时提取信息,而不是从方法本身获取结果。这通常使用某种回调机制来完成。

例如,在这段代码中:

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)
}

您会将结果提供给位于位置 (A) 的 GUI,而不是作为方法的返回值,位置 (B)

或者,您可以将 PropertyChangeListener 附加到 SwingWorker,侦听 Worker 的状态属性何时更改为 SwingWorker.StateValue.DONE,然后在 Worker 上调用 .get() 并将返回的值推送到 GUI。这是我的首选方式,因为它通常允许较低的代码耦合。