ブロッキング割り込み可能操作を呼び出す関数があるとします。タイムアウトで非同期に実行したいと思います。つまり、タイムアウトになったときに機能を中断したいのですが。
だから私はそのようなことをやろうとしています:
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
が設定されるまで待ちます。それを行う最良の方法は何ですか?
CountDownLatch
を追加すると、必要な動作を実現できます。 (大量のawait
sでブロッキング(つまり、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
}
Await を使用すると、少し簡単なアプローチをとることができます。 Await.result
メソッドは、タイムアウト期間を2番目のパラメーターとして受け取り、タイムアウト時にTimeoutException
をスローします。
try {
import scala.concurrent.duration._
Await.result(aref, 10 seconds);
} catch {
case e: TimeoutException => // whatever you want to do.
}
私も同じ動作が必要だったので、これが私がそれを解決した方法です。基本的には、タイマーを作成するオブジェクトを作成し、指定された期間にフューチャーが完了しなかった場合に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
}
}
タイムアウトを処理するために追加のスレッドをブロックしてそれを達成する方法についてはすでにいくつかの回答を得ていますが、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つのスレッドのみを使用し、タイムアウト+シングルステップの実行時間の後に終了します。
次のように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
でラップして、いつ/いつタイムアウトしたかを処理します。