Печать результатов фьючерсов в 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()
}