Предложения по простым способам асинхронной обработки в Grails

Скажем, у меня такой простой контроллер:

class FooController {

  def index = {
     someVeryLongCompution() //e.g crawl a set of web pages
     render "Long computation was launched."
  }
}

При вызове действия индекса я хочу, чтобы метод немедленно возвращался к пользователю при длительном вычислении асинхронно.

Я понимаю, что самым надежным способом сделать это было бы использование брокера сообщений в архитектуре, но мне было интересно, есть ли более простой способ сделать это.

Я попробовал плагин Executor, но он блокирует запрос http, пока не завершится длинное вычисление.

Я попробовал плагин Quartz, но это похоже на периодические задачи (если только не существует способа запуска задания только один раз?)

Как вы, ребята, обрабатываете такие запросы в Grails?

Ответ 1

Где вы хотите обрабатывать veryLongComput(), на том же сервере Grails или на другом сервере?

Если на том же сервере вам не нужна JMS, другой вариант - просто создать новый поток и обработать вычисление асинхронно.

def index = {
     def asyncProcess = new Thread({
          someVeryLongComputation()
     } as Runnable )
     asyncProcess.start()

     render "Long computation was launched."
  }

Ответ 2

Если вы используете простой триггер в Quilz Grails и установите для параметра repeatCount значение 0, задание будет выполняться только один раз. Однако он выполняется отдельно от пользовательских запросов, поэтому вам нужно будет выяснить способ связи с пользователем, когда он будет завершен.

Ответ 3

Я знаю, что это очень старый вопрос, просто хотел дать обновленный ответ.

Начиная с grails 2.3, фреймворк поддерживает асинхронные вызовы с использованием обработки запросов async для сервлета 3.0 (конечно, должен использоваться контейнер сервлета 3.0, а версия сервлета должна быть 3.0 в конфигурации, которая по умолчанию)

Здесь описано: http://grails.org/doc/latest/guide/async.html

В общем, есть два способа достижения того, что вы просили:

import static grails.async.Promises.*
   def index() {
      tasks books: Book.async.list(),
            totalBooks: Book.async.count(),
            otherValue: {
              // do hard work
            }
   }

или метод Servlet Async:

def index() {
    def ctx = startAsync()
    ctx.start {
        new Book(title:"The Stand").save()
        render template:"books", model:[books:Book.list()]
        ctx.complete()
    }
}

Небольшое примечание. Метод grails использует promises, который является основным (асинхронным) скачком вперед. Любое обещание может быть привязано к дальнейшему обещанию, иметь обратный вызов успеха и неудачи и т.д.

Ответ 4

Вы пробовали Grails Promisses API? Он должен быть таким же простым, как

import static grails.async.Promise
import static grails.async.Promises

class FooController {

  def index = {
     Promise p = Promises.task {
         someVeryLongCompution() //e.g crawl a set of web pages
     }
     render "Long computation was launched."
  }
}

Ответ 5

Попробуйте Spring плагин событий - Он поддерживает асинхронные прослушиватели событий.

Ответ 6

Если вы хотите использовать плагин Quartz (как и всегда), вы можете сделать это так. Это хорошо работает для нас:

ОПРЕДЕЛИТЬ РАБОТУ (без таймеров)

static triggers =  {
    simple name:'simpleTrigger', startDelay:0, repeatInterval: 0, repeatCount: 0
}

CALL <job> .triggerNow() для выполнения задания вручную в асинхронном режиме.

MyJob.triggerNow([key1:value1, key2: value2]); 

Pro Tip # 1

Чтобы получить именованные параметры с другой стороны...

def execute(context) {
    def value1 = context.mergedJobDataMap.get('key1');
    def value2 = context.mergedJobDataMap.get('key2');
    ...
    if (value1 && value2) {
      // This was called by triggerNow(). We know this because we received the parameters.
    } else {
      // This was called when the application started up. There are no parameters. 
    }
}

Pro Tip # 2

Метод execute всегда вызывается так же, как приложение запускается, но именованные параметры передаются как null.

Документация: http://grails.org/version/Quartz%20plugin/24#Dynamic%20Jobs%20Scheduling

Ответ 7

Лучшим решением для такого рода проблем является использование JMS через плагин JMS.

Для более простой реализации, для которой не требуется сервер/служба externel, вы можете попробовать плагин Spring Events.

Ответ 8

Решение Grails 2.2.1 У меня было дополнительное требование, согласно которому отчет должен был автоматически открыть окно, когда оно было завершено. Поэтому я выбрал путь сервлета сверху с завихрением. Я заменил визуализацию в виде строки в форматированной строке json, и она выглядела так. Кроме того, моя клиентская сторона не является представлением gsp, это ExtJS 4.1.1 (продукт HTML5)

enter code here
def index() {
    def ctx = startAsync() 
    ctx.start ({

        Map retVar = [reportId: reportId, success: success];
        String jsonString = retVar as JSON;

        log.info("generateTwoDateParamReport before the render out String is: " + jsonString);

        ctx.getOriginalWebRequest().getCurrentResponse().setContentType("text/html");
        ctx.getOriginalWebRequest().getCurrentResponse().setCharacterEncoding("UTF-8");
        log.info("current contentType is: "ctx.getOriginalWebRequest().getCurrentResponse().contentType);
        try {
           ctx.getOriginalWebRequest().getCurrentResponse().getWriter().write(jsonString);
           ctx.getOriginalWebRequest().getCurrentResponse().getWriter().flush();
           ctx.getOriginalWebRequest().getCurrentResponse().setStatus(HttpServletResponse.SC_OK);
        }
        catch (IOException ioe)
        {
            log.error("generateTwoDateParamReport flush data to client failed.");
        }
        ctx.complete();
        log.info("generateNoUserParamsReport after complete");
    });
}