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

时间:2014-03-15 08:12:37

标签: asynchronous playframework playframework-2.0

我正在将Play框架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();
}

因此,在优化代码的同时,决定利用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();
      }
    }
  );
}

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

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

2 个答案:

答案 0 :(得分:6)

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

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

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

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

在您的重新激活的控制器中,您可以处理这些未来并与它们一起工作(当它们完成时)。您可以找到有关使用期货here

的更多信息

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

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

答案 1 :(得分:2)

你没有任何东西可以阻止,然后可能没有理由让你的控制器异步。这是一个关于Play的创建者之一的好博客:http://sadache.tumblr.com/post/42351000773/async-reactive-nonblocking-threads-futures-executioncont