Вызывается в отдельном потоке против 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 должна быть где-то закрыта...

Другие вопросы по тегам