Планирование потоков Swingworker
-
06-09-2019 - |
Вопрос
У меня есть два процесса, которые нужно выполнить в моем приложении Swing: один для заполнения списка и один для выполнения операций над каждым элементом в списке.Я только что переместил два процесса в потоки Swingworker, чтобы предотвратить блокировку графического интерфейса во время выполнения задач, а также потому, что мне нужно будет выполнить этот набор операций с несколькими списками, поэтому параллелизм не был бы плохой идеей в первую очередь. место.Однако, когда я просто побежал
fillList.execute();
doStuffToList.execute();
поток doStuffToList запускался в пустом списке (ага...).Как мне сказать второму процессу дождаться завершения первого?Полагаю, я мог бы просто вложить второй процесс в конец первого, но я не знаю, это кажется плохой практикой.
Решение
Я думаю, что-то вроде этого подойдет?
boolean listIsFull=false;
class FillListWorker extends SwingWorker<Foo,Bar>
{
...
protected void done()
{
synchronized (listYouveBeenFilling)
{
listIsFull=true;
listYouveBeenFilling.notifyAll();
}
}
...
}
class DoStuffToListListWorker extends SwingWorker<Foo,Bar>
{
...
protected Foo doInBackground()
{
synchronized (listYouveBeenFilling)
{
while (!listIsFull)
{
try
{
listYouveBeenFilling.wait();
}
catch (InterruptedException ie)
{
// Don't worry, we'll just wait again
}
}
}
}
...
}
Другие советы
Как мне сказать второму процессу дождаться завершения первого?Полагаю, я мог бы просто вложить второй процесс в конец первого, но я не знаю, это кажется плохой практикой.
Рассматривали ли вы вместо этого использование callables и фьючерсов?Кажется, они хорошо подходят для такого рода вещей (позволяя doStuffToList работать с Future.get() вместо фактического списка, чтобы он был готов, когда будет вызван get), не говоря уже о всем бизнесе с Swingworker..(Считайте это скорее предложением, чем ответом)
У нас есть что-то вроде этого:
private SwingWorkerExecutor swingWorkerExecutor;
//...
protected void runChain(List<SwingWorker<Void>> chainWorkers,
final SwingWorkerExecutor.RunAfter<Void> runAfter,
final SwingWorkerExecutor.RunOnError runOnError)
{
final List<SwingWorker<Void>> remainingWorkers =
chainWorkers.subList(1, chainWorkers.size());
SwingWorkerExecutor.RunAfter<Void> chainRunAfter;
if (chainWorkers.size() > 1)
{
chainRunAfter = new SwingWorkerExecutor.RunAfter<Void>()
{
@Override
public void run(Void value)
{
runChain(remainingWorkers, runAfter, runOnError);
}
};
}
else
{
chainRunAfter = runAfter;
}
currentWorker = chainWorkers.get(0);
swingWorkerExecutor.execute(currentWorker, chainRunAfter, runOnError);
}
На мой взгляд, это довольно просто, потому что в нашем случае SwingWorkerExecutor на самом деле содержит все сложные для понимания вещи:
public class DefaultSwingWorkerExecutor implements SwingWorkerExecutor
{
@Override
public <T> void execute(SwingWorker<T, ?> worker, RunAfter<T> after,
RunOnError onError)
{
worker.addPropertyChangeListener(
new RunAfterHandler<T>(worker, after, onError));
worker.execute();
}
private static class RunAfterHandler<T> implements PropertyChangeListener
{
private final SwingWorker<T, ?> worker;
private final RunAfter<T> after;
private final RunAfter<Throwable> onError;
protected RunAfterHandler(SwingWorker<T, ?> worker, RunAfter<T> after,
RunOnError onError)
{
this.worker = worker;
this.after = after;
this.onError = onError;
}
@Override
public void propertyChange(PropertyChangeEvent evt)
{
if ("state".equals(evt.getPropertyName()) &&
evt.getNewValue() == SwingWorker.StateValue.DONE)
{
if (worker.isCancelled())
{
return;
}
try
{
after.run(worker.get());
}
catch (InterruptedException e)
{
Thread.currentThread().interrupt();
}
catch (ExecutionException e)
{
onError.run(e);
}
}
}
}
}
Есть несколько недостающих интерфейсов, которые можно было бы легко написать, не видя их здесь.
Наше реальное развертывание SwingWorkerExecutor выполняется с использованием внедренного ExecutorService вместо службы по умолчанию (это уменьшает количество пулов потоков, необходимых для одного приложения). Но настоящая причина, по которой мы представили SwingWorkerExecutor, заключалась в том, что он упрощает и стандартизирует обработку успехов и ошибок SwingWorker. условий, а также позволяет заменять логику модульных тестов (которые, как я уверен, вы знаете, намного проще, если они однопоточные.) Как вы можете видеть, существует куча шаблонов, которые обычно нужны для каждого отдельного SwingWorker. внутри Done(), поэтому вместо этого мы перемещаем работу Done() в обратный вызов.
Дополнительным преимуществом является то, что такие вещи, как запуск нескольких исполнителей Swing в цепочке, становится довольно легко реализовать.
Чтобы выполнить два процесса последовательно, традиционно вы просто вызываете один метод за другим (!).
fillList();
doStuffToList();
Или, возможно, что-то вроде:
doStuffToList(fillList());
Если вы обрабатываете по одному, вам могут понадобиться два потока с BlockingQueue
между.Вы можете пойти дальше, создав несколько потоков выполнения задач.
Что касается потока отправки событий AWT (EDT), он просто запускает действие без блокировки и получит уведомление позже.