如何重写同步控制器是在播放异步? [英] How to rewrite synchronous controller to be asynchronous in Play?

查看:187
本文介绍了如何重写同步控制器是在播放异步?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我使用的播放框架2.2我即将Web应用程序之一。我已经实现了我在同步模式控制器,有几个阻塞调用(主要是数据库)。

例如,

同步版本:

 公共静态结果指数(){
  用户的用户= db.getUser(电子邮件); //阻塞
  用户anotherUser = db.getUser(emailTwo); //阻塞
  ...
  user.sendEmail(); //调用Web服务,阻塞。
  返回OK();
}

因此​​,同时优化了code,决定利用异步编程支持播放的。通过文档了,但这个想法仍然是模糊的我,因为我很困惑如何code以上的同步块正确地转换为异步。

所以,我想出了低于code:

异步版本:

 公共静态无极<结果>指数(){
  返回Promise.promise(
    新Function0<结果>(){
      公开结果适用(){
        用户的用户= db.getUser(电子邮件); //阻塞
        用户anotherUser = db.getUser(emailTwo); //阻塞
        ...
        user.sendEmail(); //调用Web服务,阻塞。
        返回OK();
      }
    }
  );
}

所以,我刚完成一个块内的整个控制逻辑。


  1. 是我的做法是否正确?

  2. 我应该转换每一个阻​​塞请求控制器内,如异步或几个包阻塞调用内部的单一异步块就够了吗?


解决方案

该剧框架在本质上是异步的,它可以让完全无阻塞code的创建。但为了非阻塞 - 与所有的好处 - 你不能只是包装你堵code和期待神奇的发生...

在一个理想的方案中,完整的应用程序被写入一个无阻塞方式。如果这是不可能的(无论何种原因),你可能想在阿卡演员抽象的阻塞code或后面的异步接口的它会返回 scala.concurrent.Future 的。这样你就可以执行你的阻塞code(同时)在一个专门的执行上下文,而不会影响其他操作。毕竟,让所有你的行动共享相同的执行上下文意味着它们共享同一个线程池。因此,一个动作,阻止线程可能大大影响其他操作,同时具有CPU做纯粹的CPU没有得到充分利用!

在你的情况,你可能想在最低水平开始。它看起来像数据库调用阻塞使通过重构这些第一次启动。你要么需要找到一个异步驱动程序的任何数据库,您正在使用或如果只有一个阻挡司机可用,您应该将它们包装在未来使用DB-具体执行上下文(用线程池这是大小相同的执行DB的ConnectionPool)。

抽象DB的另一个优点呼叫背后的一个的异步接口的是,如果在未来的某个时刻,您切换到无阻塞的驱动程序,你可以改变你的接口的实现而无需改变你的控制器!

在您重新活跃控制器,就可以处理这些期货和与他们的工作(当他们完成)。你可以找到更多关于这里期货工作

这是你的控制器方法做一个简单的例子非阻塞调用,然后在您的视图的结果相结合,同时发送电子邮件同步:

 公共静态无极<结果>指数(){
    scala.concurrent.Future<使用者>用户= db.getUser(电子邮件); //非阻塞
    scala.concurrent.Future<使用者> anotherUser = db.getUser(emailTwo); //非阻塞    清单< scala.concurrent.Future<使用者>> listOfUserFutures =新的ArrayList<>();
    listOfUserFutures.add(用户);
    listOfUserFutures.add(anotherUser);
    。最终的ExecutionContext dbExecutionContext = Akka.system()调度()查询(dbExecutionContext);
    scala.concurrent.Future<可迭代<使用者>> futureListOfUsers = akka.dispatch.Futures.sequence(listOfUserFutures,dbExecutionContext);    。最终的ExecutionContext mailExecutionContext = Akka.system()调度()查询(mailExecutionContext);
    user.andThen(新的onComplete<使用者>(){
        公共无效的onComplete(Throwable的失败,用户用户){
             user.sendEmail(); //调用Web服务,无阻塞。
        }
    },mailExecutionContext);    返回Promise.wrap(futureListOfUsers.flatMap(新映射器LT;可迭代<使用者>中未来<结果>>(){
        公共未来<结果>申请(最终可迭代<使用者>用户){
            返回Futures.future(新赎回<结果>(){
                公开结果呼叫(){
                    返回OK(...);
                }
            },Akka.system()调度());
        }
    },EC));
}

I'm using Play framework 2.2 for one of my upcoming web application. I have implemented my controllers in synchronous pattern, with several blocking calls (mainly, database).

For example,

Synchronous version:

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

So, while optimising the code, decided to make use of Asynchronous programming support of Play. Gone through the documentation, but the idea is still vague to me, as I'm confused about how to properly convert the above synchronous block of code to Async.

So, I came up with below code:

Asynchronous version:

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

So, I just wrapped the entire control logic inside a promise block.

  1. Is my approach correct?
  2. Should I convert each and every blocking request inside the controller, as Asynchronous, or wrapping several blocking calls inside single Async block is enough?

解决方案

The play framework is asynchronous by nature and it allows the creation of fully non-blocking code. But in order to be non-blocking - with all its benefits - you can't just wrap your blocking code and expect magic to happen...

In an ideal scenario, your complete application is written in a non-blocking manner. If this is not possible (for whatever reason), you might want to abstract your blocking code in Akka actors or behind async interfaces which return scala.concurrent.Future's. This way you can execute your blocking code (simultaneously) in a dedicated Execution Context, without impacting other actions. After all, having all your actions share the same ExecutionContext means they share the same Thread pool. So an Action that blocks Threads might drastically impact other Actions doing pure CPU while having CPU not fully utilized!

In your case, you probably want to start at the lowest level. It looks like the database calls are blocking so start by refactoring these first. You either need to find an asynchronous driver for whatever database you are using or if there is only a blocking driver available, you should wrap them in a future to execute using a DB-specific execution context (with a ThreadPool that's the same size as the DB ConnectionPool).

Another advantage of abstracting the DB calls behind an async interface is that, if at some point in the future, you switch to a non-blocking driver, you can just change the implementation of your interface without having to change your controllers!

In your re-active controller, you can then handle these futures and work with them (when they complete). You can find more about working with Futures here

Here's a simplified example of your controller method doing non-blocking calls, and then combining the results in your view, while sending an email asynchronous:

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

这篇关于如何重写同步控制器是在播放异步?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆