Вызывается в отдельном потоке против Runable и многозадачности
Извините за мой слегка "начинающий" вопрос, связанный с запуском вычислений в отдельном потоке, но я программист на C++.
Существует вычислительно дорогостоящая задача обработки больших изображений. Во время обработки я хотел бы иметь возможность работать с моим программным обеспечением (включая операции масштабирования).
На основании вашего совета (процедура возвращает данные - новое изображение) был использован интерфейс Callable:
public class B implements Callable<BufferedImage> {
private boolean c;
public B (boolean c) { this.c = c; }
public BufferedImage call() {
//Some operations
if (!c)
return null;
return new BufferedImage(100, 100, BufferedImage.TYPE_INT_ARGB);
}
}
Изначально служба исполнителя создается:
ExecutorService exe = Executors.newFixedThreadPool(2);
B b = new B(true);
Впоследствии будущее возвращается:
Future<BufferedImage> res = exe.submit(b);
Наконец, мы ждем данных:
BufferedImage img = res.get();
К сожалению, эта реализация не ведет себя так, как я ожидал. Хотя он работает в отдельном потоке, "ответ" не возвращается в главное окно, и я не могу правильно работать с программным обеспечением во время вычислений.
Поэтому я попытался изменить метод get() так, чтобы
try
{
BufferedImage img_proj = results.get(5, TimeUnit.MILLISECONDS);
}
catch (Exception e)
{
results.cancel(true);
e.printStackTrace();
}
Однако появляется TimeoutException. Переписать код обратно, используя интерфейс Runnable
public class B implements Runnable{
private boolean c;
private Runnable f;
public B (boolean c_, Runnable f_) { c = c_; f = f_;}
public BufferedImage process() {
//Some operations
BufferedImage output = null;
if (c) output = new BufferedImage(100, 100, BufferedImage.TYPE_INT_ARGB);
return output;
}
public void run() { process();}
}
вместе с
Thread t = new Thread(b);
t.start ();
и многозадачность работает как положено...
Поэтому мой вопрос: нужно ли дополнительно "настраивать" или настраивать интерфейс Callable, и если да, то как?
2 ответа
Я бы предложил воспользоваться услугой вашего исполнителя.
exe.submit(()->{
BufferedImage img = res.get();
uiRelatedMethod(img);
});
Таким образом, ваш поток графического интерфейса не блокируется, и он будет уведомлен, как только буферизованное изображение станет доступным. Конечно, вам придется использовать блок try/catch.
BufferedImage img = res.get();
Это блокирует весь поток, откуда он вызывается, пока изображение не будет вычислено. Я предполагаю, что вы вызываете это из вашего основного потока или потока рассылки сообщений пользовательского интерфейса, поэтому ваш пользовательский интерфейс заблокирован.
Есть несколько подходов для решения этой проблемы:
- Реализуйте механизм уведомлений, который уведомляет ваш пользовательский интерфейс, когда изображение полностью рассчитывается. Например, вы можете передать Listener конструктору B, сохранить его и уведомить об этом в конце вычисления.
- Регулярно проверяйте, готово ли ваше будущее (
isDone()
), а затем выполните действия. - Существуют также некоторые служебные библиотеки, обеспечивающие инфраструктуру уведомлений вокруг Java Concurrency.
РЕДАКТИРОВАТЬ: В качестве примера был запрошен:
Трудно привести хороший пример, не зная вашего полного приложения или хотя бы его технологического стека.
Чтобы избежать реализации собственного интерфейса (что я бы сделал в своем приложении), я буду повторно использовать интерфейс Java ChangeListener:
public void myButtonWasClicked() {
// all your stuff setting up executor...
// yes, this could be written much shorter with Java 8
ChangeListener myChangeListener = new ChangeListener() {
public void stateChanged(ChangeEvent evt) {
handleImageReady((BufferedImage)evt.getSource());
}
}
B b = new B(true, myChangeListener);
exe.submit(b);
}
Ваш класс B продлен:
public class B implements Callable<BufferedImage> {
private boolean c;
private ChangeListener listener;
public B (boolean c, ChangeListener listener) { this.c = c; this.listener = listener; }
public BufferedImage call() {
//Some operations
if (!c)
return null;
BufferedImage img = new BufferedImage(100, 100, BufferedImage.TYPE_INT_ARGB);
// pass finished image to listener who will handle in the UI
listener.stateChanged(new ChangeEvent(img));
return img; // as nobody will consume this any longer, you could as well switch back to Runnable instead of Callable...
}
}
Обратите внимание, что это действительно грубый пример для начинающего Java. Там будет много вещей, чтобы улучшить. Например, служба Executor должна быть где-то закрыта...