Actors in Scala(Scala中的Actor)(预打印版) 第五章 Event-Based Programming (B)

Actors in Scala(Scala中的Actor)(预打印版) 第五章 Event-Based Programming (B)

张贵宾

guibin.beijing@gmail.com


2011.10.29

注:翻译这些英文书籍资料纯属个人爱好,如有不恰当之处敬请指正。

5.3 Event-based futures(基于事件的Future)

在第四章,我们展示了如何为注重结果(result-bearing)的消息使用future。Future中一些等待结果的方法在底层都依赖基于线程的receive方法。在等待结果时,这些方法都独占了底层的线程。然而,也可以在future等待结果时使用react的基于事件的方式(,这样就不会独占底层的线程了)。


比如,假设我们要从一个指定的URL上为所有的图片渲染一个概览。我们可以在图片下载完毕后,再依次渲染每张单独的图片。为了增加程序的吞吐量,可以让每个loader用自己的actor去单独下载,既然每个执行下载的actor都执行的是注重结果的任务,那么就可以很方便的使用future来跟踪期望的结果。下面的代码就展示了用这种方式渲染图片。

def renderImages(url: String) {
  val imageInfos = scanForImageInfo(url)
  val dataFurures = for(info <- imageInfos) yield {
    val loader = actor {
      react {
        case Download(info) =>
          reply(info.downloadImage())
      }
    }
    loader !! Download(info)
  }

  for (i <- 0 until imageInfos.size) {
    dataFuture(i)() match {
      case data@ImageData(_) =>
        renderImage(data)
    }
  }
  println("OK, all images rendered")
}

首先,URL作为扫描图片信息的输入参数。对每一张图片,我们都启动了一个专门的actor去下载,下载完毕后把图片内容作为响应。我们使用两个惊叹号 !! 来获得发送消息之后的future。一旦所有的future都被收集到了dataFutures中,当前的actor通过依次调用future的apply无参数方法来等待结果。

例子:使用react和future的图片渲染器

我们刚才描述的实现方式在等待future的结果时将会阻塞底层的线程。然而,我们也可以使用react以非阻塞的、基于消息的方式等待future。这样做的关键是与每个future相关联的InputChannel,这个channel是用来把future的结果传输给创建future的actor。使用基于线程的receive,调用future的apply方法会等待收取这个channel上的结果。然而我们也能在future的Channel上够使用react用基于事件的方式等待结果。

def renderImages(url: String) {
  val imageInfos = scanForImageInfo(url)
  val dataFutures = for(info <- imageInfos) yield {
    val loader = actor {
      react {
        case Download(info) =>
          reply(info.downloadImage())
      }
    }
    loader !! Download(info)
  }

  var i = 0
  loopWhile(i < imageInfos.size) {
    i += 1
    dataFutures(i - 1).inputChannel.react{
      case data@ImageData(_) =>
        renderImage(data)
    }
  } andThen {println("OK, all images rendered.")}
}

上面的代码展示了刚才所说的实现。既然有必要依次调用react多次,那么就可以使用5.2节中介绍的控制流组合器。在这个例子中,我们使用loopWhile来模拟上一个实现版本的循环。其主要区别是:新版的代码声明了索引变量 i ,并且显示的自增,而且使用循环的终止条件代替了for循环的遍历。


你也可以建立一个客户化的控制流组合器来在for循环内部使用react。在下面的章节中我们将解释如何做到这一点的。

List5.9

def renderImages(url: String) {
  val imageInfos = scanForImageInfo(url)
  val dataFutures = for(info <- imageInfos) yield {
    val loader = actor {
        react {
         case Download(info) => reply(info.downloadImage()) 
       }
    }
    loader !! Download(info)
  }

  (for (ft <- ForEach(dataFutures))
    ft.inputChannel.react {
      case data@ImageData(_) => renderImage(data)
    }  andThen {
      println("OK, all images rendered.")
    }
  )
}


List 5.10
case class ForEach[T] (iter: Iterable[T]) {
  def foreach (fun: T => Unit): Unit = {
    val it = iter.elements // 在Scala2.9中elements方法已经废弃,使用iterator方法。 
    loopWhile(it.hasNext) {
      fun(t.next)
    }
  }
}

Building custom control-flow operators(构建客户化的控制流操作符)

有时候Actor对象提供的已经存在的控制流组合器不能很好的适应手头的任务,在这种情况下构建客户化的控制流操作符可以派上用场。在这节中,你将学到如何使用Actor对象提供的控制流组合器来构建能在for循环中使用react方法的客户化的操作符。


代码5.9展示了使用客户化的ForEach操作符遍历一个list,在遍历的同时调用每个元素的react方法。在这种情况下,我们希望遍历dataFutures中的future。我们使用ForEach把dataFutures list转换成一个类似复合for循环中的生成器对象,它生成了与dataFutures list中相同的值,也就是说生成了所有的list中的元素。然而,ForEach做到了即使在调用了for循环内部的react之后,还能够继续遍历。


代码5.10展示了ForEach的具体实现。把ForEach设计成case class主要是为了在创建实例时能够省略new关键字。ForEach的构造函数接收一个类型为Iterable[T]的参数--这个集合参数用于生成我们自己的迭代器。


ForEach类有一个唯一的方法foreach,foreach方法接收一个函数类型的参数 T => Unit,实现foreach方法使得ForEach类的实例能够在简单的for复合循环中被用作生成器,就像代码5.9中展示的那样。在复合for循环中被绑定到生成元素的变量对应于函数类型的参数fun,复合循环的循环体对应于函数类型参数fun的函数体。


在foreach方法内部,我们首先从Iterable中获得了迭代器it,然后我们使用it和loopWhile组合器遍历集合,在每一次迭代过程中,我们对当前的集合元素应用函数方法fun,既然我们在使用loopWhile,那么在fun方法中调用react就很安全。


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值