web-dev-qa-db-ja.com

タイムアウトのある未来Scala

ブロッキング割り込み可能操作を呼び出す関数があるとします。タイムアウトで非同期に実行したいと思います。つまり、タイムアウトになったときに機能を中断したいのですが。

だから私はそのようなことをやろうとしています:

 import scala.util.Try 
 import scala.concurrent.Future 
 
 def launch(f:()=> Unit、timeout:Int):Future [ Try [Unit]] = {
 
 val aref = new Java.util.concurrent.atomic.AtomicReference [Thread]()
 
 import ExecutionContext.Implicits。グローバル
将来{Thread.sleep(timeout); aref.get()。interrupt} // 1 
将来{aref.set(Thread.currentThread); Try(f())} // 2 
} 

問題は、(2)がまだ現在のスレッドに設定していないため、(1)のarefがnullになる可能性があることです。この場合、arefが設定されるまで待ちます。それを行う最良の方法は何ですか?

22
Michael

CountDownLatchを追加すると、必要な動作を実現できます。 (大量のawaitsでブロッキング(つまり、Futureでスタックする)が発生すると、スレッドプールが不足する可能性があることに注意してください。)

import scala.util.Try
import scala.concurrent.Future

def launch(f: () => Unit, timeout: Int): Future[Try[Unit]] = {

  val aref = new Java.util.concurrent.atomic.AtomicReference[Thread]()
  val cdl = new Java.util.concurrent.CountDownLatch(1)

  import ExecutionContext.Implicits.global
  Future {Thread.sleep(timeout); cdl.await(); aref.get().interrupt}   // 1
  Future {aref.set(Thread.currentThread); cdl.countDown(); Try(f())}  // 2
}
5
Rex Kerr

Await を使用すると、少し簡単なアプローチをとることができます。 Await.resultメソッドは、タイムアウト期間を2番目のパラメーターとして受け取り、タイムアウト時にTimeoutExceptionをスローします。

try {
  import scala.concurrent.duration._
  Await.result(aref, 10 seconds);
} catch {
    case e: TimeoutException => // whatever you want to do.
}
15
flavian

私も同じ動作が必要だったので、これが私がそれを解決した方法です。基本的には、タイマーを作成するオブジェクトを作成し、指定された期間にフューチャーが完了しなかった場合にTimeoutExceptionでプロミスを失敗させます。

package mypackage

import scala.concurrent.{Promise, Future}
import scala.concurrent.duration.FiniteDuration
import akka.actor.ActorSystem
import scala.concurrent.ExecutionContext.Implicits.global

object TimeoutFuture {

  val actorSystem = ActorSystem("myActorSystem")
  def apply[A](timeout: FiniteDuration)(block: => A): Future[A] = {
    val promise = Promise[A]()
    actorSystem.scheduler.scheduleOnce(timeout) {
      promise tryFailure new Java.util.concurrent.TimeoutException
    }

    Future {
      try {
        promise success block
      }
      catch {
        case e:Throwable => promise failure e
      } 
    }

    promise.future
  }
}
6
anshumans

タイムアウトを処理するために追加のスレッドをブロックしてそれを達成する方法についてはすでにいくつかの回答を得ていますが、Rex Kerrがすでに与えた理由により、別の方法を試すことをお勧めします。 f()で何をしているのかは正確にはわかりませんが、I/Oバウンドの場合は、代わりに非同期I/Oライブラリを使用することをお勧めします。ある種のループの場合は、タイムアウト値をその関数に直接渡して、タイムアウトを超えた場合にTimeoutExceptionをスローすることができます。例:

import scala.concurrent.duration._
import Java.util.concurrent.TimeoutException

def doSth(timeout: Deadline) = {
  for {
    i <- 0 to 10
  } yield {
    Thread.sleep(1000)
    if (timeout.isOverdue)
      throw new TimeoutException("Operation timed out.")

    i
  }
}

scala> future { doSth(12.seconds.fromNow) }
res3: scala.concurrent.Future[scala.collection.immutable.IndexedSeq[Int]] = 
  scala.concurrent.impl.Promise$DefaultPromise@3d104456

scala> Await.result(res3, Duration.Inf)
res6: scala.collection.immutable.IndexedSeq[Int] =
  Vector(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10)

scala> future { doSth(2.seconds.fromNow) }
res7: scala.concurrent.Future[scala.collection.immutable.IndexedSeq[Int]] = 
  scala.concurrent.impl.Promise$DefaultPromise@f7dd680

scala> Await.result(res7, Duration.Inf)
Java.util.concurrent.TimeoutException: Operation timed out.
    at $anonfun$doSth$1.apply$mcII$sp(<console>:17)
    at $anonfun$doSth$1.apply(<console>:13)
    at $anonfun$doSth$1.apply(<console>:13)
    at scala.collection.TraversableLike$$anonfun$map$1.apply(TraversableLike.scala:244)
    ...

scala> res7.value
res10: Option[scala.util.Try[scala.collection.immutable.IndexedSeq[Int]]] =
  Some(Failure(Java.util.concurrent.TimeoutException: Operation timed out.))

これは1つのスレッドのみを使用し、タイムアウト+シングルステップの実行時間の後に終了します。

3
drexin

次のようにCountDownLatchを使用することもできます。

_def launch(f: () => Unit, timeout: Int): Future[Try[Unit]] = {

  val aref = new Java.util.concurrent.atomic.AtomicReference[Thread]() 

  import ExecutionContext.Implicits.global
  val latch = new CountDownLatch(1)
  Future {
    latch.await()
    aref.get().interrupt
  }

  Future {
    aref.set(Thread.currentThread) 
    latch.countDown()
    Try(f())
  }
}
_

今、私はlatch.await()への私の呼び出しで永遠に待っていますが、あなたは確かにそれを次のように変更できます:

_latch.await(1, TimeUnit.SECONDS)
_

そして、それをTryでラップして、いつ/いつタイムアウトしたかを処理します。

1
cmbaxter