关于在 Grails 中进行异步处理的简单方法的建议

发布于 2024-11-10 16:53:00 字数 424 浏览 5 评论 0原文

假设我有一个像这样的简单控制器:

class FooController {

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

当调用索引操作时,我希望该方法立即返回给用户,同时异步运行长计算。

我知道最可靠的方法是在架构中使用消息代理,但我想知道是否有更简单的方法。

我尝试了 Executor 插件,但是它会阻止 http 请求返回,直到长计算完成为止。

我尝试了 Quartz 插件,但这似乎对于周期性任务很有用(除非有办法只运行一次作业?)

你们是如何在 Grails 中处理此类请求的?

Let's say I have a simple controller like this:

class FooController {

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

When the index action is invoked, I want the method to return immediately to the user while running the long computation asynchronously.

I understand the most robust way to do this would be to use a message broker in the architecture, but I was wondering if there is a simpler way to do it.

I tried the Executor plugin but that blocks the http request from returning until the long computation is done.

I tried the Quartz plugin, but that seems to be good for periodic tasks (unless there is a way to run a job just once?)

How are you guys handling such requests in Grails?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(8

聽兲甴掵 2024-11-17 16:53:00

您想在同一个 Grails 服务器上还是不同的服务器上处理veryLongComputation()?

如果是同一台服务器,则不需要 JMS,另一种选择是创建一个新线程并异步处理计算。

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

     render "Long computation was launched."
  }

Where do you want to process veryLongComputation(), on the same Grails server, or a different server?

If the same server, you don't need JMS, another option is to just create a new thread and process the computation asynchronously.

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

     render "Long computation was launched."
  }
套路撩心 2024-11-17 16:53:00

如果您在 Grails Quartz 中使用简单触发器并将重复计数设置为 0,则作业将仅运行一次。但是,它与用户请求分开运行,因此您需要找到某种方式在完成后与用户进行通信。

If you use a simple trigger in Grails Quartz and set the repeatCount to 0, the job will only run once. It runs separate from user requests, however, so you'd need to figure out some way to communicate to user when it completed.

迷爱 2024-11-17 16:53:00

我知道这是一个非常老的问题,只是想给出更新的答案。

从 grails 2.3 开始,框架支持使用 Servlet 3.0 异步请求处理的异步调用(当然,必须使用 servlet 3.0 容器,并且配置中的 servlet 版本应为 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 异步方法:

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

小注释 - grails 方法使用 Promise,这是一个重大的(异步)飞跃。任何 Promise 都可以链接到进一步的 Promise,对成功和失败进行回调等。

I know this is a very old question, just wanted to give an updated answer.

Since grails 2.3, the framework supports async calls using Servlet 3.0 async request handling (of course, a servlet 3.0 container must be used and the servlet version should be 3.0 in the configuration, which it is per default)

It is documented here : http://grails.org/doc/latest/guide/async.html

In general, there are two ways achieving what you asked for:

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

or the Servlet Async way:

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

Small note - The grails method is using promises, which is a major (async) leap forward. Any Promise can be chained to further promised, have a callback on success and fail etc'

橘味果▽酱 2024-11-17 16:53:00

您是否尝试过 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."
  }
}

Have you tried Grails Promisses API? It should be as simple as

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."
  }
}
猥︴琐丶欲为 2024-11-17 16:53:00

尝试 Spring 事件插件 - 它支持异步事件侦听器。

Try Spring events plugin - It supports asynchronous event listeners.

雪若未夕 2024-11-17 16:53:00

如果您想使用 Quartz 插件(就像我们一直做的那样),您可以这样做。它对我们来说效果很好:

DEFINE A JOB(没有定时触发器)

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

CALL.triggerNow()以异步模式手动执行作业。

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

专业提示 #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. 
    }
}

专业提示 #2

执行方法总是在应用程序启动时被调用,但是命名参数参数为空。

文档: http://grails.org/version/Quartz%20plugin/24 #Dynamic%20Jobs%20Scheduling

If you'd like to use the Quartz plugin (as we always do), you can do it like this. It works well for us:

DEFINE A JOB (with no timed triggers)

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

CALL <job>.triggerNow() to manually execute the job in asynchronous mode.

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

Pro Tip #1

To get the named parameters out the other side...

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

The execute method always gets called just as the application starts up, but the named parameters come through as null.

Documentation: http://grails.org/version/Quartz%20plugin/24#Dynamic%20Jobs%20Scheduling

毁我热情 2024-11-17 16:53:00

解决此类问题的最佳解决方案是通过 JMS 插件 使用 JMS。

对于更简单的实现,不需要外部服务器/服务,您可以尝试 Spring Events 插件。

The best solution for this kind of problem is to use JMS, via the JMS plugin.

For a simpler implementation, that doesn't requires an externel server/service, you can try the Spring Events plugin.

沩ん囻菔务 2024-11-17 16:53:00

Grails 2.2.1 解决方案 我有一个额外的要求,即报告完成后必须自动弹出一个窗口。所以我选择了上面的 servlet 方式。我用 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");
    });
}

Grails 2.2.1 Solution I had an additional requirement that said the report had to auto pop a window when it was complete. So I chose the servlet way from above with a twist. I replaced the render a view with a json formatted string return it looked like this.
Additionally my client side is not a gsp view, it is ExtJS 4.1.1 (a HTML5 product)

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");
    });
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文