如何在Play中将同步控制器重写为异步?

Vee*_*era 9 asynchronous playframework playframework-2.0

我正在使用Play framework 2.2作为我即将推出的Web应用程序之一.我已经以同步模式实现了我的控制器,有几个阻塞调用(主要是数据库).

例如,

同步版本:

public static Result index(){
  User user = db.getUser(email); // blocking
  User anotherUser = db.getUser(emailTwo); // blocking
  ...
  user.sendEmail(); // call to a webservice, blocking.
  return ok();
}
Run Code Online (Sandbox Code Playgroud)

因此,在优化代码的同时,决定利用Play的异步编程支持.通过文档,但这个想法对我来说仍然含糊不清,因为我对如何正确地将上述同步代码块转换为Async感到困惑.

所以,我提出了以下代码:

异步版本:

public static Promise<Result> index(){
  return Promise.promise(
    new Function0<Result>(){
      public Result apply(){
        User user = db.getUser(email); // blocking
        User anotherUser = db.getUser(emailTwo); // blocking
        ...
        user.sendEmail(); // call to a webservice, blocking.
        return ok();
      }
    }
  );
}
Run Code Online (Sandbox Code Playgroud)

所以,我只是将整个控制逻辑包装在一个promise块中.

  1. 我的方法是否正确?
  2. 我应该在控制器内部转换每个阻塞请求,如异步,还是在单个异步块内包含多个阻塞调用就足够了?

sti*_*kos 6

play框架本质上是异步的,它允许创建完全非阻塞的代码.但是为了无阻塞 - 带来所有好处 - 你不能只是包装你的阻止代码并期待魔法发生......

在理想情况下,您的完整应用程序是以非阻塞方式编写的.如果这是不可能的(无论出于何种原因),您可能希望在Akka actor中或在返回的异步接口后面抽象阻塞代码scala.concurrent.Future.这样,您可以在专用的执行上下文中同时执行阻止代码,而不会影响其他操作.毕竟,让所有操作共享相同的ExecutionContext意味着它们共享相同的线程池.因此,阻止线程的Action可能会在CPU未充分利用的同时对其他执行纯CPU的操作产生巨大影响!

在您的情况下,您可能希望从最低级别开始.看起来数据库调用是阻塞的,所以首先重构这些.您需要为正在使用的数据库找到异步驱动程序,或者如果只有阻塞驱动程序可用,您应该在将来使用特定于DB的执行上下文(使用与DB ConnectionPool).

抽象异步接口背后的数据库调用的另一个好处是,如果在将来某个时候,您切换到非阻塞驱动程序,您只需更改接口的实现,而无需更改控制器!

在您的重新激活的控制器中,您可以处理这些未来并与它们一起工作(当它们完成时).您可以在此处找到有关与期货合作的更多信息

以下是执行非阻塞调用的控制器方法的简化示例,然后在视图中组合结果,同时发送异步电子邮件:

public static Promise<Result> index(){
    scala.concurrent.Future<User> user = db.getUser(email); // non-blocking
    scala.concurrent.Future<User> anotherUser = db.getUser(emailTwo); // non-blocking

    List<scala.concurrent.Future<User>> listOfUserFutures = new ArrayList<>();
    listOfUserFutures.add(user);
    listOfUserFutures.add(anotherUser);
    final ExecutionContext dbExecutionContext = Akka.system().dispatchers().lookup("dbExecutionContext");
    scala.concurrent.Future<Iterable<User>> futureListOfUsers = akka.dispatch.Futures.sequence(listOfUserFutures, dbExecutionContext);  

    final ExecutionContext mailExecutionContext = Akka.system().dispatchers().lookup("mailExecutionContext");
    user.andThen(new OnComplete<User>() {
        public void onComplete(Throwable failure, User user) {
             user.sendEmail(); // call to a webservice, non-blocking.       
        }
    }, mailExecutionContext);

    return Promise.wrap(futureListOfUsers.flatMap(new Mapper<Iterable<User>, Future<Result>>() {
        public Future<Result> apply(final Iterable<User> users) {
            return Futures.future(new Callable<Result>() {
                public Result call() {              
                    return ok(...);
                }
            }, Akka.system().dispatcher());
        }
    }, ec));
}
Run Code Online (Sandbox Code Playgroud)

  • 使用play WS库的Web服务调用是非阻塞的.对于DB调用,如前面的帖子中所提到的,您必须查看数据库服务器是否具有非阻塞驱动程序.据我所知,只有MongoDB有一个非阻塞驱动程序[link](http://reactivemongo.org) (2认同)
  • 我认为EBean支持异步查询...请查看http://www.avaje.org/ebean/introquery_future.html (2认同)

Jam*_*ard 2

如果您没有任何不阻塞的东西,那么可能没有理由让您的控制器异步。这是 Play 的创建者之一写的关于此的好博客:http ://sadache.tumblr.com/post/42351000773/async-reactive-nonblocking-threads-futures-executioncont

  • 如果代码可以是非阻塞的,那么它必须是异步的。但如果它不能是非阻塞的,那么就没有理由异步。Play 已经在后台异步处理所有请求。 (2认同)