Play Framework Web开发教程(19): 任务–启动一些进程
2014-08-17 21:44
351 查看
有些时候,一个Web应用有需要在正常的请求-响应周期之外执行一些代码,比如一些常时间运行的后台任务,或者也是在请求-响应周期中执行,但无需用户交互。
比如我们回到之前的产品分类的例子,我们需要跟踪订单是否有人拣选,打包了和发货了。拣选货物涉及了某个人根据订单在仓库中查找订单中的物品,然后可以打包这些货品,交给物流发货。一个实现方法是生成新图所示的货品目录的物品拣选单(和HTML表单无关)。
在过去很长的一段时间内,系统构架都假定这些任务都在Web应用外实现,比如在一些旧系统中的批量任务生成。而今天的系统很多是为Web为中心的,或者是基本云服务的。使用这些架构意味着我们需要在Web应用中有能够调度和执行这些任务。
为了更好的说明问题,我们考虑这样一个系统,这个系统用来生成物品拣选单并把拣选单发给仓库管理员,我们假定我们需要使用批量处理,因为生成一个拣选任务比较费时,而且我们需要优化这些任务的顺序以使得访问不同仓库的时间最小。异步工作的任务
一个最简单的实现方法是在Web应用的某个地方定义如下的页面:
用户点击”Generate&SendPickList”按钮触发这个任务。拣选单中的每个项目都是一个从指定仓库中提取物品的准备单,我们可以使用如下View模板来显示这个拣选单,
文件app/views/pickList.scala.html
通常显现这个View模板的情况是在一个Controller的某个Action中显示这个页面,比如,你可能会在浏览器中预览这个PickList:
文件:app/controllers/PickLists.scala
然而,我们希望在另外的进程中生成,显示和发送PickList,从而使得这个过程和Controller中给浏览器发送响应消息的工作独立开来。
我们首先可以使用Scala的futures来异步执行一些代码
文件app/controllers/PickLists.scala
这里我们使用了scala.concurrent.future来封装了一些异步执行的代码,这意味着不管send执行需要多久的时间,这个Action会立马执行网页的重定向到routes.PickLists.index。这时send发生在另外的一个executioncontext中。任务调度
根据我们仓库如何工作的不同,可能自动每隔半小时自动生成picklist更为有效。为了实现这个功能,我们需要每隔半小时触发这个任务而无需人工干预。Play没有直接支持任务调度,但Play集成了Akka(参见Akka教程),因此我们可以使用Akka来周期调度这个任务,我们无需用户界面,而是在Play应用开始时创建和调度一个actor.
文件:app/Global.scala
这段代码中应用启动时为每个仓库创建一个Actor,然后我们利用Akka的调度器API创建一个每半小时执行的任务。下面为PickListActor一个可能的实现:
其中send的具体实现无关紧要,重点是我们可以利用Akka函数库构建一个定时执行的任务。异步返回结果和暂停的请求
之前我们介绍了在另外一个线程中执行一个较长运行的任务,那是一个不需要返回结果的情况,但有时我们需要异步返回的结果。
比如,我们需要显示一个仪表盘来显示当前订单量的数目–也就是给定仓库的需要拣选和发货的订单数,这意味着检查所有订单并返回订单数目。
比如,我们使用如下一个函数返回所有的订单:
假定这个函数执行的时间比较长。我们使用一个异步操作来获取订单,暂停HTTP请求,在等待结果的同时可以处理其它请求,下面是一个可能的实现:
文件:pp/controllers/Dashboard.scala
这里scala.concurrent.future返回一个promise来封装一个尚未有结果的对象,它的类型为Future[String],代表一个String类型的占位符。
关于Future它代表一个可能还没有结束的结束过程,我们在后面这详细介绍,而且本篇的例子不是个完整的例子,这里只需要了解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 | } |
文件: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 | } |
我们首先可以使用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 | } |
根据我们仓库如何工作的不同,可能自动每隔半小时自动生成picklist更为有效。为了实现这个功能,我们需要每隔半小时触发这个任务而无需人工干预。Play没有直接支持任务调度,但Play集成了Akka(参见
文件: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 | } |
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 | } |
之前我们介绍了在另外一个线程中执行一个较长运行的任务,那是一个不需要返回结果的情况,但有时我们需要异步返回的结果。
比如,我们需要显示一个仪表盘来显示当前订单量的数目–也就是给定仓库的需要拣选和发货的订单数,这意味着检查所有订单并返回订单数目。
比如,我们使用如下一个函数返回所有的订单:
1 | val backlog = models.Order.backlog(warehouse) |
文件: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 | } |
关于Future它代表一个可能还没有结束的结束过程,我们在后面这详细介绍,而且本篇的例子不是个完整的例子,这里只需要了解Web编程中异步操作的基本概念就可以了。
相关文章推荐
- 在cron启动定时任务后总是会启动一个[sendmail] 进程的解决方法, 并且每次cron任务之后msmtp.log总是记录一条发送失败的日志
- shell 进度条及启动另一个进程处理任务
- Jenkins任务启动的后台进程被自动kill
- android教程学习第11讲:nit进程脚本如何解析启动脚本
- Android教程 -07 Activity的任务栈和启动模式
- Linux定时启动任务的一些命令...
- Android:启动模式与任务栈,进程的关系
- Spark 启动历史任务记录进程,报错 Logging directory must be specified解决
- Play Framework Web开发教程(33): 结构化页面-组合使用模板
- [李景山php]每天laravel[015]-laravel 中级任务--小白教程----实际操作-启动 laravel
- 如何获得JDK1.6上的jconsole启动时的正在运行的JAVA进程的一些信息
- RHCE学习<5>RHEL6进程管理、Cron任务计划和启动故障排除
- win7中一些我们应该禁用或者手动启动的系统服务进程
- 715的一些事:传京东启动IPO进程,中华网出售门户业务
- Play Framework Web开发教程(22): Controllers–HTTP和Scala
- Play Framework Web开发教程(16): 处理HTTP请求和响应
- Android应用程序模块详解(任务、启动模式、进程和线程、FLAG_ACTIVITY_NEW_TASK)
- Android应用程序模块详解(任务、启动模式、进程和线程、FLAG_ACTIVITY_NEW_TASK)
- Supervisor启动进程的一些例子
- [李景山php]每天laravel[015]-laravel 中级任务--小白教程----实际操作-修改你的启动页面