我正在参加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)
}
答案 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()
}