您的位置:首页 > 其它

Play Framework Web开发教程(19): 任务–启动一些进程

2014-08-17 21:44 351 查看
有些时候,一个Web应用有需要在正常的请求-响应周期之外执行一些代码,比如一些常时间运行的后台任务,或者也是在请求-响应周期中执行,但无需用户交互。
比如我们回到之前的产品分类的例子,我们需要跟踪订单是否有人拣选,打包了和发货了。拣选货物涉及了某个人根据订单在仓库中查找订单中的物品,然后可以打包这些货品,交给物流发货。一个实现方法是生成新图所示的货品目录的物品拣选单(和HTML表单无关)。


在过去很长的一段时间内,系统构架都假定这些任务都在Web应用外实现,比如在一些旧系统中的批量任务生成。而今天的系统很多是为Web为中心的,或者是基本云服务的。使用这些架构意味着我们需要在Web应用中有能够调度和执行这些任务。
为了更好的说明问题,我们考虑这样一个系统,这个系统用来生成物品拣选单并把拣选单发给仓库管理员,我们假定我们需要使用批量处理,因为生成一个拣选任务比较费时,而且我们需要优化这些任务的顺序以使得访问不同仓库的时间最小。异步工作的任务
一个最简单的实现方法是在Web应用的某个地方定义如下的页面:



用户点击”Generate&SendPickList”按钮触发这个任务。拣选单中的每个项目都是一个从指定仓库中提取物品的准备单,我们可以使用如下View模板来显示这个拣选单,
文件app/views/pickList.scala.html
1
@
(warehouse
:
String,list
:
List[models.Preparation],
2
time
:
java.util.Date)
3
4
@
main(
"Warehouse"
+warehouse+
"picklistfor"
+time){
5
<table>
6
<tr>
7
<th>Order
#
</th>
8
<th>ProductEAN</th>
9
<th>Productdescription</th>
10
<th>Quantity</th>
11
<th>Location</th>
12
</tr>
13
@
for
((preparation,index)<-list.zipWithIndex){
14
<tr
@
(
if
(index
%
2
==
0
)
"class='odd'"
)>
15
<th>
@
preparation.orderNumber</th>
16
<th>
@
preparation.product.ean</th>
17
<th>
@
preparation.product.description</th>
18
<th>
@
preparation.quantity</th>
19
<th>
@
preparation.location</th>
20
</tr>}
21
</table>
22
}
通常显现这个View模板的情况是在一个Controller的某个Action中显示这个页面,比如,你可能会在浏览器中预览这个PickList:
文件:app/controllers/PickLists.scala
1
object
PickLists
extends
Controller{
2
def
preview(warehouse
:
String)
=
Action{
3
val
pickList
=
PickList.find(warehouse)
4
val
timestamp
=
new
java.util.Date
5
Ok(views.html.pickList(warehouse,pickList,timestamp))
6
7
}
8
}
然而,我们希望在另外的进程中生成,显示和发送PickList,从而使得这个过程和Controller中给浏览器发送响应消息的工作独立开来。
我们首先可以使用Scala的futures来异步执行一些代码
文件app/controllers/PickLists.scala
1
package
controllers
2
3
import
models.PickList
4
import
play.api.mvc.{Action,Controller}
5
6
import
scala.concurrent.{ExecutionContext,future}
7
8
object
PickLists
extends
Controller{
9
...
10
11
def
sendAsync(warehouse
:
String)
=
Action{
12
import
ExecutionContext.Implicits.global
13
future{
14
val
pickList
=
PickList.find(warehouse)
15
send(views.html.pickList(warehouse,pickList,
new
java.util.Date))
16
}
17
Redirect(routes.PickLists.index())
18
19
}
20
}
这里我们使用了scala.concurrent.future来封装了一些异步执行的代码,这意味着不管send执行需要多久的时间,这个Action会立马执行网页的重定向到routes.PickLists.index。这时send发生在另外的一个executioncontext中。任务调度
根据我们仓库如何工作的不同,可能自动每隔半小时自动生成picklist更为有效。为了实现这个功能,我们需要每隔半小时触发这个任务而无需人工干预。Play没有直接支持任务调度,但Play集成了Akka(参见Akka教程),因此我们可以使用Akka来周期调度这个任务,我们无需用户界面,而是在Play应用开始时创建和调度一个actor.
文件:app/Global.scala
1
import
play.api.GlobalSettings
2
import
akka.actor.{Actor,Props}
3
import
models.Warehouse
4
import
play.api.libs.concurrent.Akka
5
import
play.api.templates.Html
6
import
play.api.libs.concurrent.Execution.Implicits.defaultContext
7
8
object
Global
extends
GlobalSettings{
9
override
def
onStart(application
:
play.api.Application){
10
import
scala.concurrent.duration.
_
11
import
play.api.Play.current
12
13
for
(warehouse<-Warehouse.find()){
14
val
actor
=
Akka.system.actorOf(
15
Props(
new
PickListActor(warehouse))
16
)
17
18
Akka.system.scheduler.schedule(
0
.seconds,
30
.minutes,actor,
"send"
)
19
}
20
}
21
}
这段代码中应用启动时为每个仓库创建一个Actor,然后我们利用Akka的调度器API创建一个每半小时执行的任务。下面为PickListActor一个可能的实现:
1
class
PickListActor(warehouse
:
String)
extends
Actor{
2
def
receive
=
{
3
case
"send"
=
>{
4
val
pickList
=
PickList.find(warehouse)
5
val
html
=
views.html.pickList(warehouse,pickList,
new
Date)
6
send(html)
7
}
8
case
_
=
>play.api.Logger.warn(
"unsupportedmessagetype"
)
9
}
10
def
send(html
:
Html){
11
//...
12
}
13
}
其中send的具体实现无关紧要,重点是我们可以利用Akka函数库构建一个定时执行的任务。异步返回结果和暂停的请求
之前我们介绍了在另外一个线程中执行一个较长运行的任务,那是一个不需要返回结果的情况,但有时我们需要异步返回的结果。
比如,我们需要显示一个仪表盘来显示当前订单量的数目–也就是给定仓库的需要拣选和发货的订单数,这意味着检查所有订单并返回订单数目。
比如,我们使用如下一个函数返回所有的订单:
1
val
backlog
=
models.Order.backlog(warehouse)
假定这个函数执行的时间比较长。我们使用一个异步操作来获取订单,暂停HTTP请求,在等待结果的同时可以处理其它请求,下面是一个可能的实现:
文件:pp/controllers/Dashboard.scala
1
package
controllers
2
import
play.api.mvc.{Action,Controller}
3
import
concurrent.{ExecutionContext,Future}
4
object
Dashboard
extends
Controller{
5
def
backlog(warehouse
:
String)
=
Action{
6
import
ExecutionContext.Implicits.global
7
val
backlog
=
scala.concurrent.future{
8
models.Order.backlog(warehouse)
9
}
10
Async{
11
backlog.map(value
=
>Ok(value))
12
}
13
}
14
}
这里scala.concurrent.future返回一个promise来封装一个尚未有结果的对象,它的类型为Future[String],代表一个String类型的占位符。
关于Future它代表一个可能还没有结束的结束过程,我们在后面这详细介绍,而且本篇的例子不是个完整的例子,这里只需要了解Web编程中异步操作的基本概念就可以了。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐