开发者

Suggestions for simple ways to do Asynchronous processing in Grails

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

class FooController {

  def index = {
     someVeryLongCompution() //e.g crawl a set of web page开发者_C百科s
     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?


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."
  }


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.


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'


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."
  }
}


Try Spring events plugin - It supports asynchronous event listeners.


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


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.


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");
    });
}
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜