Предложения по простым способам асинхронной обработки в 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");
});
}