How async Action api works in play framework 2.2.x

2019-03-01 09:00发布

I was trying to create async api. But the response shows sequential execution. Steps done: Open the url in two tabs of chrome. And hit them one after other quickly. url ex:- localhost:9000/getStar.

But the execution log is like :-

    [info] play - Listening for HTTP on /0:0:0:0:0:0:0:0:9000

(Server started, use Ctrl+D to stop and go back to the console...)

[success] Compiled in 107ms
[info] application - Application has started
[info] play - Application started (Dev)
[info] application - Async started ************************** :tarun
[info] application - Success Async call  :1
[info] application - Success Async call  :2
[info] application - Success Async call  :3
[info] application - Success Async call  :4
[info] application - Success Async call  :5
[info] application - Success Async call  :6
[info] application - Success Async call  :7
[info] application - Success Async call  :8
[info] application - Success Async call  :9
[info] application - Async finished ************************** :tarun
[info] application - Async started ************************** :tarun1
[info] application - Success Async call  :1
[info] application - Success Async call  :2
[info] application - Success Async call  :3
[info] application - Success Async call  :4
[info] application - Success Async call  :5
[info] application - Success Async call  :6
[info] application - Success Async call  :7
[info] application - Success Async call  :8
[info] application - Success Async call  :9
[info] application - Async finished ************************** :tarun1

The code for this is :

package controllers

import play.Logger
import play.api.libs.json.Json
import play.api.mvc._


import scala.concurrent.Future

object StarController extends Controller {
  import play.api.libs.concurrent.Execution.Implicits.defaultContext


  def getStarAsync(name : String) = Action.async{
    val futureResult = Future{
      Logger.info("Async started ************************** :" + name)
      val a = 0;
      for( a <- 1 until 10) {
        Thread.sleep(1000)
        Logger.info("Success Async call  :" + a.toString)
      }
      Logger.info("Async finished ************************** :" + name)
      Map("success" -> Json.toJson(true), "msg" -> Json.toJson("Success Async by :" + name), "code" -> Json.toJson(200))
    }

    futureResult.map{ result =>
      Ok(Json.toJson(result))
    }
  }

}

Can anyone please help me understand , why the execution of the was sequential even with async call ?

3条回答
叛逆
2楼-- · 2019-03-01 09:42

Action.async doesn't magically make the controller method asynchronous. The only thing it is different about it is that it expects a Future[Result] instead of a Result. That's it. Controllers are otherwise asynchronous as they can be by nature (i.e. a normal Action gets wrapped in a Future anyway). The thing here is that Thread.sleep(1000) blocks it's thread, and is not the least bit asynchronous.

The other thing is that in dev mode (i.e. activator run), the play server uses a single thread to serve requests, so it can properly handle reload/compile, evolutions, etc. So what's happening is that you're just blocking that thread with synchronous calls. You should see different results using activator start, but even so, there's no point in using Action.async here unless you're going to delegate that blocking to a different thread pool.

Further reading.

查看更多
唯我独甜
3楼-- · 2019-03-01 09:43

I did a lot of experiments and found one thing. Maybe it sounds crazy, but Play handles simultaneous requests sequentially only if they are made from the same browser to the same route. If I make requests via curl or from different browsers or even from one browser but to different routes, then they are handled asynchronously. Not sure what kind of protection Play does in such a way, but this protection exists and it's a fact.

查看更多
Bombasti
4楼-- · 2019-03-01 09:49

Just to clarify m-z's answer. This is example how you could handle some asynchronous collections in your code

def getStarAsyncOld(name: String) = Action.async {
  val futureResult = Future {
    Logger.info("Async started ************************** :" + name)
  } flatMap (_ => Future.sequence(for (a <- 1 until 10) yield Future {
    Thread.sleep(1000)
    Logger.info("Success Async call  :" + a.toString)
  })) map { _ =>
    Logger.info("Async finished ************************** :" + name)
    Map("success" -> Json.toJson(true), "msg" -> Json.toJson("Success Async by :" + name), "code" -> Json.toJson(200))
  }

  futureResult.map { result =>
    Ok(Json.toJson(result))
  }
}

or absolutely the same using for:

def getStarAsync(name: String) = Action.async {
  for {
    _ <- Future(Logger.info("Async started ************************** :" + name))
    _ <- Future.sequence(for (a <- 1 until 10) yield Future {
      Thread.sleep(1000)
      Logger.info("Success Async call  :" + a.toString)
    })
    _ = Logger.info("Async finished ************************** :" + name)
    result = Map("success" -> Json.toJson(true), "msg" -> Json.toJson("Success Async by :" + name), "code" -> Json.toJson(200))
  } yield Ok(Json.toJson(result))
}
查看更多
登录 后发表回答