Scalaでスケジュールされたフューチャーを実装しようとしています。特定の時間待ってから本体を実行してほしい。これまでのところ、次の簡単なアプローチを試しました
val d = 5.seconds.fromNow
val f = future {Await.ready(Promise().future, d.timeLeft); 1}
val res = Await.result(f, Duration.Inf)
しかし、将来的にTimeoutExcpetionを取得しています。これは正しいアプローチですか、それともJavaのScheduledExecutorを使用するだけですか?
コードを次のように変更できます。
val d = 5.seconds.fromNow
val f = Future {delay(d); 1}
val res = Await.result(f, Duration.Inf)
def delay(dur:Deadline) = {
Try(Await.ready(Promise().future, dur.timeLeft))
}
しかし、私はそれをお勧めしません。そうすることで、あなたは将来ブロックすることになるでしょう(そのPromise
が完了することを待たないようにブロックすること)。そしてExecutionContext
でのブロックは大いに勧められません。私はあなたが述べたようにJavaスケジュールされたエグゼキュータを使用することを検討するか、@ alex23を推奨するようにAkkaを使用することを検討することができます。
Akkaにはakka.patternがあります:
def after[T](duration: FiniteDuration, using: Scheduler)(value: ⇒ Future[T])(implicit ec: ExecutionContext): Future[T]
「指定された期間の後、提供された値の成功または失敗で完了するscala.concurrent.Futureを返します。」
標準ライブラリだけを使用して、箱から出してそれを行うことは何もありません。ほとんどの単純な使用例では、次のような小さなヘルパーを使用できます。
object DelayedFuture {
import Java.util.{Timer, TimerTask}
import Java.util.Date
import scala.concurrent._
import scala.concurrent.duration.FiniteDuration
import scala.util.Try
private val timer = new Timer(true)
private def makeTask[T]( body: => T )( schedule: TimerTask => Unit )(implicit ctx: ExecutionContext): Future[T] = {
val prom = Promise[T]()
schedule(
new TimerTask{
def run() {
// IMPORTANT: The timer task just starts the execution on the passed
// ExecutionContext and is thus almost instantaneous (making it
// practical to use a single Timer - hence a single background thread).
ctx.execute(
new Runnable {
def run() {
prom.complete(Try(body))
}
}
)
}
}
)
prom.future
}
def apply[T]( delay: Long )( body: => T )(implicit ctx: ExecutionContext): Future[T] = {
makeTask( body )( timer.schedule( _, delay ) )
}
def apply[T]( date: Date )( body: => T )(implicit ctx: ExecutionContext): Future[T] = {
makeTask( body )( timer.schedule( _, date ) )
}
def apply[T]( delay: FiniteDuration )( body: => T )(implicit ctx: ExecutionContext): Future[T] = {
makeTask( body )( timer.schedule( _, delay.toMillis ) )
}
}
これは次のように使用できます。
import scala.concurrent.duration._
import scala.concurrent.ExecutionContext.Implicits._
DelayedFuture( 5 seconds )( println("Hello") )
Javaスケジュールされたフューチャーとは異なり、この実装ではフューチャーをキャンセルできないことに注意してください。
Akkaを使用せずに完了をスケジュールする場合は、通常のJavaタイマーを使用して、完了の約束をスケジュールします。
def delay[T](delay: Long)(block: => T): Future[T] = {
val promise = Promise[T]()
val t = new Timer()
t.schedule(new TimerTask {
override def run(): Unit = {
promise.complete(Try(block))
}
}, delay)
promise.future
}
私のソリューションはレギスのものとかなり似ていますが、Akkaを使用してスケジュールを設定します。
def delayedFuture[T](delay: FiniteDuration)(block: => T)(implicit executor : ExecutionContext): Future[T] = {
val promise = Promise[T]
Akka.system.scheduler.scheduleOnce(delay) {
try {
val result = block
promise.complete(Success(result))
} catch {
case t: Throwable => promise.failure(t)
}
}
promise.future
}
他のすべてのソリューションは、遅延タスクごとにakkaを使用するか、スレッドをブロックします。 (すでにakkaを使用していない限り)より良い解決策は、JavaのScheduledThreadPoolExecutorを使用することです。 scalaラッパーの例: