在Scala工作表中打印Futures的结果

时间:2013-12-29 01:59:14

标签: scala monads scala-ide

我正在参加Coursera的Reactive编程课程,当我完成其中一项任务时,我遇到了一些奇怪的事情。无论如何,我通过此扩展

向Future Companion对象添加了一些方法
implicit class FutureCompanionOps[T](val f: Future.type) extends AnyVal {

    /** Returns a future that is always completed with `value`.
     */
    def always[T](value: T): Future[T] = Future(value)

    /** Returns a future that is never completed.
     *
     *  This future may be useful when testing if timeout logic works correctly.
     */
    def never[T]: Future[T] = Promise().future


    /** Given a list of futures `fs`, returns the future holding the list of values of all the futures from `fs`.
     *  The returned future is completed only once all of the futures in `fs` have been completed.
     *  The values in the list are in the same order as corresponding futures `fs`.
     *  If any of the futures `fs` fails, the resulting future also fails.
     */
    def all[T](fs: List[Future[T]]): Future[List[T]] = {
      val resPr = Promise[List[T]]()
      def function( in: List[Future[T]], fxs:Future[List[T]] ): Future[List[T]] =
      {
        if(in.isEmpty) fxs 
        else
        function( in.tail, for { i <- in.head ; xs <- fxs } yield { i :: xs } ) 
      }
      function( fs, resPr.success(Nil).future )
    }
}

然后我在Eclipse的Scala WorkSheet上写了这个

object TestSheet {

val tempPr = Promise[Boolean]()      
val anotherFuLs = List( Future.always(true), Future.always(false), tempPr.future )
                                                  //> anotherFuLs  : List[scala.concurrent.Future[Boolean]] = List(scala.concurren
                                                  //| t.impl.Promise$DefaultPromise@a19b1de, scala.concurrent.impl.Promise$Default
                                                  //| Promise@1cec6b00, scala.concurrent.impl.Promise$DefaultPromise@625dcec6)
  val crapFut = Future.all(anotherFuLs)           //> crapFut  : scala.concurrent.Future[List[Boolean]] = scala.concurrent.impl.Pr
                                                  //| omise$DefaultPromise@6564dbd5
  crapFut.isCompleted                             //> res3: Boolean = false
  tempPr.success(false)                           //> res4: nodescala.TestSheet.tempPr.type = scala.concurrent.impl.Promise$Defaul
                                                  //| tPromise@625dcec6
  crapFut.isCompleted                             //> res5: Boolean = true
  crapFut onComplete {
    case Success(ls) => println( ls )
    case Failure(e) => println( "Failed with Exception " + e )
  }
} 

无论我怎样都无法让Scala工作表打印出结果列表的值。但是,当我编写单元测试并运行scala测试时,我在比较最终结果列表时没有任何问题。在使用异步内容时,这是scala工作表中的错误吗?

这是单元测试

test("A composed future with all should complete when all futures complete") {
    val tempPr = Promise[Boolean]()
    val lsFu = List( Future.always(true), Future.always(false), tempPr.future );
    val fuL = Future.all( lsFu )
    fuL onComplete { case Success(ls) => println( "This got done" ); assert( ls === List( true, false, true ), "I should get back the expected List" ) 
                     case Failure(ex) => assert( false, "Failed with Exception " + ex ) } 
    assert( fuL.isCompleted === false, "The resulting Future should not be complete when the depending futures are still pending" )    
    tempPr.success(true)

  }

3 个答案:

答案 0 :(得分:8)

看起来问题是运行工作表代码的主线程在onComplete处理程序运行之前结束。

Scala的默认ExecutionContext本质上是一个充满守护程序线程的线程池。在此上下文中的“守护进程”意味着即使该线程忙于执行某些操作,也不会阻止JVM在所有非守护程序线程完成时关闭。在您的情况下,主线程可能是程序中唯一的非守护程序线程。

在Future上调用onComplete将使得隐式提供的ExecutionContext将在Future完成时执行您的处理程序。这意味着处理程序在守护程序线程上运行。由于onComplete是你在main方法中做的最后一件事,因此JVM只是在ExecutionContext运行处理程序之前完成。

通常情况下,这不是什么大问题。在像Web服务器这样的场景中,您的JVM将启动并运行很长时间。对于您的用例,我建议使用scala.concurrent.Await中的一种方法阻止Future完成。这样,您可以在main方法中将完成逻辑作为主线程的一部分运行。

答案 1 :(得分:4)

Intellij IDEA有类似的问题,它既适用于工作表,也适用于从IDEA内部运行应用程序。

关键是运行代码时类路径上的库。 scala命令变成了类似的东西:

execCommand /opt/jdk1.7.0_45/bin/java -Xmx256M -Xms32M -Xbootclasspath/a:
/opt/scala-2.10.3/lib/akka-actors.jar:
/opt/scala-2.10.3/lib/diffutils.jar:
/opt/scala-2.10.3/lib/jline.jar:
/opt/scala-2.10.3/lib/scala-actors.jar:
/opt/scala-2.10.3/lib/scala-actors-migration.jar:
/opt/scala-2.10.3/lib/scala-compiler.jar:
/opt/scala-2.10.3/lib/scala-library.jar:
/opt/scala-2.10.3/lib/scala-partest.jar:
/opt/scala-2.10.3/lib/scalap.jar:
/opt/scala-2.10.3/lib/scala-reflect.jar:
/opt/scala-2.10.3/lib/scala-swing.jar:
/opt/scala-2.10.3/lib/typesafe-config.jar 
-classpath "" -Dscala.home=/opt/scala-2.10.3 -Dscala.usejavacp=true scala.tools.nsc.MainGenericRunner 
-cp out/production/Sheets testing.SequenceMain 400

问题是IDE没有做与scala命令相同的操作,最终结果是Await.result()不等待守护程序线程完成。

这是一个具体的例子(也启发了Reactive课程): 包测试

import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.{Await, Promise, Future}
import scala.concurrent.duration.Duration

object SequenceMain {

  def main(args: Array[String]) {
    val delay = if (args.length > 0) args(0).toInt else 100
    if (args.length > 1) {
      println("Delays are: " + (List(1.0, 1.2, 0.8) map {df => (delay * df).toInt} mkString ", "))
    }

    val start = System.currentTimeMillis()
    var last = start

    def stamp(s: String) {
      val tn = Thread.currentThread().getName
      val now = System.currentTimeMillis()
      println(s"$tn: ${now-start} / ${now - last}: $s")
      last = now
    }

    def sequence[T](fs: List[Future[T]]): Future[List[T]] = {
      val pr = Promise[List[T]]()
      pr.success(Nil)
      val r: Future[List[T]] = fs.foldRight(pr.future) {
        (ft: Future[T], z: Future[List[T]]) =>
          val result: Future[List[T]] = for (t <- ft; ts <- z) yield {
            t :: ts
          }
          result
      }
      r
    }

    stamp("Making sequence of futures.")
    val fts: List[Future[String]] = List(
      Future[String] {
        Thread.sleep((delay * 1.0).toInt)
        "Future 0"
      },
      Future[String] {
        Thread.sleep((delay * 1.2).toInt)
        if (false) throw new Exception("Blew up")
        else "Future 1"
      },
      Future[String] {
        Thread.sleep((delay * 0.8).toInt)
        "Future 2"
      }
    )


    stamp("Making Future sequence.")
    val a1: Future[List[String]] = sequence(fts)

    stamp("Extracting sequence from future.")
    a1 foreach {
      (z: List[String]) => println("And the result is : " + z)
    }

    stamp("Await result.")
    Await.result(a1, Duration(10, "seconds"))
    stamp("Awaited result.")

  }
}

在IDEA中运行此应用程序会产生:

/opt/jdk1.7.0_45/bin/java -Didea.launcher.port=7541 -Didea.launcher.bin.path=/opt/idea/idea-IU-134.1160/bin -Dfile.encoding=UTF-8 -classpath /home/mkh/IdeaProjects/Sheets/out/production/Sheets:/opt/scala-2.10.3/lib/scala-library.jar:/opt/jdk1.7.0_45/jre/lib/rt.jar:/opt/idea/idea-IU-134.1160/lib/idea_rt.jar com.intellij.rt.execution.application.AppMain testing.SequenceMain 400
main: 0 / 0: Making sequence of futures.
main: 87 / 87: Making Future sequence.
main: 90 / 3: Extracting sequence from future.
main: 90 / 0: Await result.
main: 562 / 472: Awaited result.

Process finished with exit code 0

请注意,“并且结果是”println不会被打印。

但是如果直接运行代码,结果是:

mkh@rock:~/IdeaProjects/Sheets$ scala -cp out/production/Sheets:/opt/scala-2.10.3/lib/scala-library.jar testing.SequenceMain 400 
main: 1 / 1: Making sequence of futures.
main: 9 / 8: Making Future sequence.
main: 10 / 1: Extracting sequence from future.
main: 10 / 0: Await result.
main: 491 / 481: Awaited result.
And the result is : List(Future 0, Future 1, Future 2)

请注意,等待的时间稍长,println实际上已完成。

更奇怪的是,即使不使用println命令,看似无关的scala也可以使这个例子有效:

/opt/jdk1.7.0_45/bin/java -Didea.launcher.port=7539 -Didea.launcher.bin.path=/opt/idea/idea-IU-134.1160/bin -Dfile.encoding=UTF-8 -classpath /home/mkh/IdeaProjects/Sheets/out/production/Sheets:/opt/scala-2.10.3/lib/scala-library.jar:/opt/jdk1.7.0_45/jre/lib/rt.jar:/opt/idea/idea-IU-134.1160/lib/idea_rt.jar com.intellij.rt.execution.application.AppMain testing.SequenceMain 400 1
Delays are: 400, 480, 320
main: 1 / 1: Making sequence of futures.
main: 59 / 58: Making Future sequence.
main: 62 / 3: Extracting sequence from future.
main: 62 / 0: Await result.
And the result is : List(Future 0, Future 1, Future 2)
main: 543 / 481: Awaited result.

Process finished with exit code 0

该示例现在打印延迟作为其第一个动作(确认需要480毫秒来让最慢的Future完成),但不知何故,这个初始println具有使最终Await工作的副作用。

比我最后一段要解释的人要聪明得多......

答案 2 :(得分:0)

因为我们无法在工作表上打印Future的结果,所以我建议将结果写入文件。然后,您可以在工作表上使用writeFile而不是println。

 def writeFile(text : String) =  {
  val fw = new FileWriter("result.txt", true)
  try {
    fw.write(text)
    fw.write("\n")
  }
  finally fw.close()
}