Печать результатов фьючерсов в Scala Рабочий лист
Я беру курс реактивного программирования на Coursera, и, выполняя одно из заданий, я наткнулся на что-то странное. В любом случае я добавил несколько методов объекту 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 )
}
}
i написал это на Scala WorkSheet в Eclipse
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
//| [email protected], scala.concurrent.impl.Promise$Default
//| [email protected], [email protected])
val crapFut = Future.all(anotherFuLs) //> crapFut : scala.concurrent.Future[List[Boolean]] = scala.concurrent.impl.Pr
//| [email protected]
crapFut.isCompleted //> res3: Boolean = false
tempPr.success(false) //> res4: nodescala.TestSheet.tempPr.type = scala.concurrent.impl.Promise$Defaul
//| [email protected]
crapFut.isCompleted //> res5: Boolean = true
crapFut onComplete {
case Success(ls) => println( ls )
case Failure(e) => println( "Failed with Exception " + e )
}
}
независимо от того, что я не могу получить Scala Work Sheet, чтобы распечатать значения результирующего списка. Однако, когда я пишу unit test и запускаю тест Scala, у меня нет проблем с сравнением конечного результирующего списка. Является ли это ошибкой в рабочем листе Scala при работе с асинхронным материалом?
Это unit test
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)
}
Ответы
Ответ 1
Похоже, проблема в том, что основной поток, который запускает ваш код рабочего листа, заканчивается до того, как обработчик onComplete
запускается.
Scala default ExecutionContext
- это, по сути, пул потоков, полный потоков демона. "Демон" в этом контексте означает, что даже если этот поток занят чем-то занятым, это не помешает JVM отключиться, когда закончится нить не-демона. В вашем случае основной поток, вероятно, является единственным не-демоном в программе.
Вызов onComplete
в Будущем сделает так, чтобы неявно предоставленный ExecutionContext выполнил ваш обработчик, когда будет завершено будущее. Это означает, что обработчик работает по потоку демона. Поскольку onComplete
- это последнее, что вы делаете в своем основном методе, JVM просто заканчивается до того, как ExecutionContext обойдет обработчик.
Обычно это не имеет большого значения. В сценарии, таком как веб-сервер, ваша JVM будет работать и работать в течение длительного времени. Для вашего варианта использования я бы рекомендовал завершить блокировку для Будущего, используя один из методов в scala.concurrent.Await
. Таким образом, вы можете запустить логику завершения как часть основного потока в основном методе.
Ответ 2
У 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()
не дотягивает до конца потоков daemon.
Вот конкретный пример (также вдохновленный реактивным курсом):
тестирование пакетов
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 не печатается.
Однако, если код запускается напрямую, результат:
[email protected]:~/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 мс, чтобы позволить самому медленному будущему завершено), но каким-то образом этот исходный println имеет побочный эффект, заключающийся в том, что окончательная работа Await работает.
Кто-то намного умнее, чем мне придется объяснить эту последнюю пьесу...
Ответ 3
Поскольку мы не можем напечатать результат Future на листе, я предлагаю записать результат в файл. Затем вы можете использовать writeFile вместо println на вашем листе.
def writeFile(text : String) = {
val fw = new FileWriter("result.txt", true)
try {
fw.write(text)
fw.write("\n")
}
finally fw.close()
}