Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 15 additions & 15 deletions ch02/ScalaAsync.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,23 @@

## 构建异步controller

Play框架本身就是异步的。对每一个请求而言,Play都会使用异步,非阻塞的方式来处理。
Play 框架本身就是异步的。对每一个请求而言,Play 都会使用异步,非阻塞的方式来处理。

默认配置下controller即是异步的了。换言之,应用应该避免在controller中做阻塞操作,例如,让controller等待某一个操作完成。类似的阻塞操作还有:调用JDBC,流处理(streaming)API,HTTP请求和长时间的计算任务
默认配置下 controller 即是异步的了。换言之,应用应该避免在controller中做阻塞操作,例如,让 controller 等待某一个操作完成。此类阻塞操作的常见例子还有:调用JDBC,流处理(streaming)API,HTTP 请求和长时间的计算任务

虽然不可能通过增加默认执行环境(execution context)中的线程数来提升阻塞controller的并发请求处理能力,但使用异步controller可以使应用更易于扩展,在负载较大的情况下依然能够保持响应。
尽管可以通过增加默认执行环境(execution context)中的线程数来提升阻塞 controller 的并发请求处理能力,但建议使用异步 controller 可以使应用更易于扩展,在负载较大的情况下依然能够保持响应。

## 创建非阻塞action

由于Play的异步工作方式,action应该做到尽可能的快,例如非阻塞。那么,在还没有得到返回结果的情况下,应该返回什么作为结果呢?答案是future结果!
由于 Play 的异步工作方式,action 应该做到尽可能的快,例如非阻塞。那么,在还没有得到返回结果的情况下,应该返回什么作为结果呢?答案是future结果!

一个`Future[Result]`最终会被替换成一个类型为`Result`的值。通过提供`Future[Result]`而非`Result`,我们能够在非阻塞的情况下快速生成结果。一旦从promise中得到了最终结果,Play就会应用该结果
一个 `Future[Result]` 最终会被替换成一个类型为 `Result` 的值。通过提供`Future[Result]` 而非 `Result`,我们能够在非阻塞的情况下快速生成结果。一旦从promise 中得到了最终结果,Play 就会返回该结果

Web客户端在等待响应时依然是阻塞的,但在服务器端没有任何操作被阻塞,而且服务器资源依然可以服务其他客户端。
Web 客户端在等待响应时是阻塞的,但服务器端没有发生阻塞,而且服务器资源依然可以服务其他客户端。

## 如何创建`Future[Result]`

在创建一个`Future[Result]`之前,我们需要先创建另一个future:这个future会返回一个真实的值,以便我们依此生成结果:
在创建一个 `Future[Result]` 之前,我们需要先创建另一个 future :这个 future 会返回一个真实的值,以便我们依此生成结果:

```scala
import play.api.libs.concurrent.Execution.Implicits.defaultContext
Expand All @@ -29,7 +29,7 @@ val futureResult: Future[Result] = futurePIValue.map { pi =>
}
```

所有的Play异步API调用都会返回一个`Future`。不管你是在调用外部web服务,如`play.api.libs.WS`API,或是用Akka调度异步任务,亦或是使用`play.api.libs.Akka`与actor进行通讯
Play 所有的异步 API 调用都会返回一个 `Future`。例如,不管你是用`play.api.libs.WS` API 在调用外部 web 服务,或是用 Akka 分配异步任务,亦或是使用 `play.api.libs.Akka` 与 actor 进行通讯

以下是一个异步调用并获得一个`Future`的简单例子:

Expand All @@ -41,15 +41,15 @@ val futureInt: Future[Int] = scala.concurrent.Future {
}
```

注意:理解哪个线程运行了future非常重要,以上的两段代码都导入了Play的默认执行环境(execution context)。这是一个隐式(implicit)的参数,会被传入所有接受回调的future API方法中。执行环境(execution context)通常等价于线程池,但这并不是一定的。
注意:理解哪个线程运行了 future 非常重要,以上的两段代码都导入了 Play 的默认执行环境(execution context)。这是一个隐式(implicit)的参数,会被传入所有接受回调的 future API 方法中。执行环境(execution context)通常等价于线程池,但这并不是一定的。

简单的把同步IO封装入`Future`并不能将其转换为异步的。如果你不能通过改变应用架构来避免阻塞操作,那么该操作总会在某一时刻被执行的,而相应的线程则会被阻塞。所以,除了将操作封装于`Future`中,还必须让它运行在配置了足够线程来处理可预计并发的独立执行环境(execution context)中。更多信息请见[理解Play线程池](https://www.playframework.com/documentation/2.3.x/ThreadPools)
简单的把同步IO封装入 `Future` 并不能将其转换为异步的。如果你不能通过改变应用架构来避免阻塞操作,那么该操作总会在某一时刻被执行的,而相应的线程则会被阻塞。所以,除了将操作封装于 `Future` 中,还需要让它运行在配置了足够线程来处理可预计并发的独立执行环境(execution context)中。更多信息请见[理解Play线程池](https://www.playframework.com/documentation/2.3.x/ThreadPools)

这对于使用Actor来阻塞操作也非常有用。Actor提供了一种非常简洁的模型来处理超时和失败,设置阻塞执行环境(execution context),并且管理了该服务的一切状态。Actor还提供了像`ScatterGatherFirstCompletedRouter`这样的模式来处理同时缓存和数据库请求,并且能够远程执行于后端服务器集群中。但使用actor可能有些多余,主要还是取决你想要的是什么。
这对于使用 Actor 来阻塞操作也非常有用。Actor 提供了一种非常简洁的模型来处理超时和失败,设置阻塞执行环境(execution context),并且管理了该服务的一切状态。Actor还提供了像 `ScatterGatherFirstCompletedRouter` 这样的模式来处理同时缓存和数据库请求,并且能够远程执行于后端服务器集群中。但使用 actor 可能有些多余,主要还是取决你想要的是什么。

## 返回future

迄今为止,我们一直都在调用`Action.apply`方法来构建action,为了发出一个异步的结果,我们需要调用`Action.async`:
迄今为止,我们一直都在调用 `Action.apply` 方法来构建 action,为了发出一个异步的结果,我们需要调用`Action.async`:

```scala
import play.api.libs.concurrent.Execution.Implicits.defaultContext
Expand All @@ -62,19 +62,19 @@ def index = Action.async {

## Action默认即是异步的

Play的[action](../ch1/ScalaActions.md)默认即是异步的。比如说,在下面这个controller中,`{ Ok(...) }`这部分代码并不是controller的方法体。这其实是一个传入`Action`对象`apply`方法的匿名函数,用来创建一个`Action`对象。运行时,你写的这个匿名函数会被调用并返回一个`Future`结果。
Play的[action](../ch1/ScalaActions.md)默认即是异步的。比如说,在下面这个controller中,`{ Ok(...) }` 这部分代码并不是 controller 的方法体。这其实是一个传入 `Action` 对象 `apply` 方法的匿名函数,用来创建一个`Action`对象。运行时,你写的这个匿名函数会被调用并返回一个`Future`结果。

```scala
val echo = Action { request =>
Ok("Got request [" + request + "]")
}
```

注意:`Action.apply``Action.async`创建的`Action`对象在Play内部会以同样的方式处理。他们都是异步的`Action`,而不是一个同步一个异步。`.async`构造器只是用来简化创建基于API并返回`Future`的action,让非阻塞的代码更加容易写。
注意:`Action.apply``Action.async` 创建的 `Action` 对象在Play内部会以同样的方式处理。他们都是异步的 `Action`,而不是一个同步一个异步。`.async` 构造器只是用来简化创建基于API并返回`Future`的 action,让非阻塞的代码更加容易写。

## 处理超时

能够合理的处理超时通常很有用,当出现问题时可以避免浏览器无谓的阻塞和等待。简单的通过调用promise超时来构造另一个promise便能够处理这些情况
能够合理的处理超时通常很有用,当出现问题时可以避免浏览器无谓的阻塞和等待。简单的通过调用 promise 超时来构造另一个 promise 便能够处理这些情况

```scala
import play.api.libs.concurrent.Execution.Implicits.defaultContext
Expand Down
4 changes: 2 additions & 2 deletions ch02/ScalaStream.md
Original file line number Diff line number Diff line change
Expand Up @@ -112,11 +112,11 @@ def index = Action {

## 分块响应

到现在为止,流处理文件内容工作的非常好,主要是因为能够在流处理之前算出文本长度。但是如果是动态计算,还没有得出长度的内容呢
到现在为止,流处理文件内容工作的非常好,主要是因为能够在流处理之前算出文本长度。但是如果是动态文本,无法确定文本大小呢

对于这样的响应,我们需要使用 **分块传输编码** (Chunked transfer encoding)。

**分块传输编码**(Chunked transfer encoding)是一种定义在 Hypertext Transfer Protocol(HTTP)1.1 版本中的数据传输机制,其中 web 服务器会将文本分块处理。它使用了 `Transfer-Encoding` HTTP 响应报头而非 `Content-Length` 报头,如果你没有使用 `Content-Length` 的话,则必须使用这个报头。由于没有 `Content-Length`,服务器无需在开始传输响应到客户端(通常是 web 客户端)之前就知道内容的长度。 Web 服务器在知道内容总长前就能够传输动态生成的内容响应。
**分块传输编码**(Chunked transfer encoding)是一种定义在 Hypertext Transfer Protocol(HTTP)1.1 版本中的数据传输机制,其中 web 服务器会将文本连续分块处理。它使用了 `Transfer-Encoding` HTTP 响应报头而非 `Content-Length` 报头,如果你没有使用 `Content-Length` 的话,则必须使用这个报头。由于没有 `Content-Length`,服务器无需在开始传输响应到客户端(通常是 web 客户端)之前就知道内容的长度。 Web 服务器在知道内容总长前就能够传输动态生成的内容响应。

在发送每个数据块前,都会先发送块的大小,客户端能够依此判断是否已完整接收该数据块。数据传输会在收到一个长度为零的数据块后终结。

Expand Down
2 changes: 1 addition & 1 deletion ch02/ScalaWebSockets.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ class MyWebSocketActor(out: ActorRef) extends Actor {

### 检测 WebSocket 何时关闭

如果 WebSocket 已经关闭,Play 会自动停掉 actor。这意味着你可以通过实现 `postStop` 方法来清理 WebSocket 可能使用的资源。例如:
如果 WebSocket 已经关闭,Play 会自动停掉 actor。这意味着你可以通过实现 `postStop` 方法来清理 WebSocket 可能已经使用的资源。例如:

```scala
override def postStop() = {
Expand Down