5.5.6. Фоновые задачи

Механизм фоновых задач предназначен для асинхронного выполнения длительных операций на клиентском уровне без заморозки пользовательского интерфейса.

Использование фоновых задач:

  1. Задача описывается как наследник абстрактного класса BackgroundTask. В конструктор задачи необходимо передать ссылку на контроллер экрана, с которым будет связана задача, и значение таймаута ее выполнения.

    Если экран указан, то при его закрытии пользователем активная задача будет прервана. Кроме того, задача будет автоматически прервана по истечении указанного таймаута.

    Собственно действия, выполняемые задачей, реализуются в методе run().

  2. Создается объект управления задачей − BackgroundTaskHandler. Для этого экземпляр задачи необходимо передать методу handle() бина BackgroundWorker. Ссылку на BackgroundWorker можно получить инжекцией в контроллер экрана, либо статическим методом класса AppBeans.

  3. Выполняется запуск задачи.

Warning

Метод run() класса BackgroundTask нельзя использовать для чтения/изменения состояния визуальных компонентов или источников данных: вместо этого используйте методы done(), progress() и canceled(). При попытке установить значение для компонента UI из фонового потока будет выброшено исключение IllegalConcurrentAccessException.

Пример:

@Inject
protected BackgroundWorker backgroundWorker;

@Override
public void init(Map<String, Object> params) {
    // Create task with 10 sec timeout and this screen as owner
    BackgroundTask<Integer, Void> task = new BackgroundTask<Integer, Void>(10, this) {
        @Override
        public Void run(TaskLifeCycle<Integer> taskLifeCycle) throws Exception {
            // Do something in background thread
            for (int i = 0; i < 5; i++) {
                TimeUnit.SECONDS.sleep(1); // time consuming computations
                taskLifeCycle.publish(i);  // publish current progress to show it in progress() method
            }
            return null;
        }

        @Override
        public void canceled() {
            // Do something in UI thread if the task is canceled
        }

        @Override
        public void done(Void result) {
            // Do something in UI thread when the task is done
        }

        @Override
        public void progress(List<Integer> changes) {
            // Show current progress in UI thread
        }
    };
    // Get task handler object and run the task
    BackgroundTaskHandler taskHandler = backgroundWorker.handle(task);
    taskHandler.execute();
}

Подробная информация о назначении методов приведена в JavaDocs классов BackgroundTask, TaskLifeCycle, BackgroundTaskHandler.

Ниже приведены моменты, на которые следует обратить внимание:

  • BackgroundTask<T, V> − параметризованный класс:

    • T − тип объектов, показывающих прогресс задачи. Объекты этого типа передаются в метод progress() задачи при вызове TaskLifeCycle.publish() в рабочем потоке.

    • V − тип результата задачи, он передается в метод done(). Его также можно получить вызовом метода BackgroundTaskHandler.getResult(), что приведет к ожиданию завершения задачи.

  • Метод canceled() вызывается только в случае управляемой отмены задачи, то есть при вызове cancel() у TaskHandler.

  • Метод handleTimeoutException() вызывается при истечении таймаута задачи. Если окно, в котором выполняется задача, закрывается, то задача останавливается без оповещения.

  • Метод run() задачи должен поддерживать возможность прерывания извне. Для этого в долгих процессах желательно периодически проверять флаг TaskLifeCycle.isInterrupted(), и соответственно завершать выполнение. Кроме того, нельзя тихо проглатывать исключение InterruptedException (или вообще все исключения). Вместо этого нужно либо вообще не перехватывать его, либо выполнять корректный выход из метода.

    • Метод isCancelled() возвращает true, если задача была прервана вызовом метода cancel().

      public String run(TaskLifeCycle<Integer> taskLifeCycle) {
          for (int i = 0; i < 9_000_000; i++) {
              if (taskLifeCycle.isCancelled()) {
                  log.info(" >>> Task was cancelled");
                  break;
              } else {
                  log.info(" >>> Task is working: iteration #" + i);
              }
          }
          return "Done";
      }
  • Объекты BackgroundTask не имеют состояния. Если при реализации конкретного класса задачи не заводить полей для хранения промежуточных данных, то можно запускать несколько параллельно работающих процессов, используя единственный экземпляр задачи.

  • Объект BackgroundHandler можно запускать (т.е. вызывать его метод execute()) всего один раз. Если требуется частый перезапуск задачи, то используйте класс BackgroundTaskWrapper.

  • Для показа пользователю модального окна с прогрессом и кнопкой Отмена используйте классы BackgroundWorkWindow или BackgroundWorkProgressWindow с набором статических методов.Для окна можно задать режим отображения прогресса и разрешить или запретить отмену фоновой задачи.

  • Если внутри потока задачи необходимо использовать некоторые значения визуальных компонентов, то нужно реализовать их получение в методе getParams(), который выполняется в потоке UI один раз при запуске задачи. В методе run() эти параметры будут доступны через метод getParams() объекта TaskLifeCycle.

  • При возникновении исключительных ситуаций в потоке UI вызывается метод BackgroundTask.handleException(), в котором можно отобразить ошибку.

  • На выполнение фоновых задач влияют свойства приложения cuba.backgroundWorker.maxActiveTasksCount и cuba.backgroundWorker.timeoutCheckInterval.

Warning

В блоке Web Client фоновые задачи используют технологию HTTP push, предоставляемую фреймворком Vaadin. См. https://vaadin.com/wiki/-/wiki/Main/Working+around+push+issues для получения информации о настройке веб-серверов для использования данной технологии.

Tip

Если вы не используете фоновую задачу, но хотите изменять состояние UI-компонентов из не-UI потока, воспользуйтесь методами интерфейса UIAccessor. Получите ссылку на интерфейс UIAccessor методом BackgroundWorker.getUIAccessor() в UI-потоке, и после этого вы сможете вызывать его методы access() и accessSynchronously() из фонового потока для безопасного чтения и изменения состояния UI-компонентов.