我想做这样的事情:
对于集合中的每个项目,在等待某个超时间隔的响应时询问一个actor并阻塞,如果抛出超时异常,我想继续下一个项目。
这是代码模式:
createUser
演员本身正在召唤其他演员可能需要比我3秒钟更长的时间。
这并没有按预期工作:在第一个项目超时后,整个事情崩溃并停止。有一些死信通知,我想这是因为当我的演员调用的演员完成时,原始发件人无效(花了3秒多)。所以我的问题是如何告诉它忘记超时项目并继续其余的事情,好像什么也没发生?
答案 0 :(得分:1)
@stefanobaghino是对的。如果未来包含异常,请参阅文档中的here,然后 Await.result 抛出以便可以正确处理。
这里你匹配未来的失败案例,但你没有从中恢复。更好的方法就像跟随 -
collection.foreach { item =>
val future = (actor ? Request(msg = item)).mapTo[Response]
future.recover {
case ex: Exception =>
// log ex
Response(ex.message) // some other object of type Response
}
val response = Await.result(future, 3 seconds)
// use response here
}
在阅读@Dimitri的回答之后,我尝试以毫秒为单位记录时间戳,以查看它在整个过程中导致滞后的原因,并且我发现了相当奇怪的行为。我观察到,只要有死信,即使开始处理下一条消息给演员,也会有很大的滞后。不知道为什么会这样。下面是我试图检查它的代码 -
package com.lightbend.akka.sample
import akka.actor.{ Actor, ActorLogging, ActorRef, ActorSystem, Props }
import akka.pattern.{ ask, pipe, AskTimeoutException }
import scala.concurrent.Await
import scala.concurrent.duration._
import scala.io.StdIn
import scala.util.{ Try, Success, Failure }
import scala.concurrent.ExecutionContext.Implicits.global
import java.util.concurrent.TimeoutException
object AkkaQuickStart {
class NumberActor extends Actor {
override def receive: Receive = {
case (num: Int, startAt: Long) =>
println("B " + num.toString + " : " + System.currentTimeMillis().toString + " : " + (System.currentTimeMillis() - startAt).toString)
Thread.sleep(500 * num)
sender() ! "OK"
}
}
def main(args: Array[String]): Unit = {
implicit val timeout: akka.util.Timeout = 1 seconds
val numActor = ActorSystem("system").actorOf(Props(new NumberActor()))
val range = (1 to 5) ++ (4 to 1 by -1)
println(range)
def lag(implicit startAt: Long): String = (System.currentTimeMillis() - startAt).toString
range.map { r =>
implicit val startAt = System.currentTimeMillis()
println("A " + r.toString + " : " + System.currentTimeMillis().toString + " : " + lag)
val future = (numActor ? (r, startAt))
.recover {
case ex: AskTimeoutException =>
println("E " + r.toString + " : " + System.currentTimeMillis().toString + " : " + lag)
"Ask timeout"
}
.mapTo[String]
future.onComplete{
case Success(reply) =>
println("C " + r.toString + " : " + System.currentTimeMillis().toString + " : " + lag + " : success " + reply)
case Failure(reply) =>
println("C " + r.toString + " : " + System.currentTimeMillis().toString + " : " + lag + " : failure")
}
Try(Await.result(future, 1 seconds)) match {
case Success(reply) =>
println("D " + r.toString + " : " + System.currentTimeMillis().toString + " : " + lag + " : " + reply)
case Failure(ex) =>
println("D " + r.toString + " : " + System.currentTimeMillis().toString + " : " + lag + " : Await timeout ")
}
}
}
}
我尝试了Ask超时和Await超时的不同组合,发现在迭代结束时发送的actor消息的开始处理方面存在滞后 -
Ask timeout = 1 Await Timeout = 1 => 3000 - 4500毫秒导致死信
Ask timeout = 1 Await Timeout = 3 => 3000 - 4500毫秒导致死信
询问超时= 3等待超时= 1 => 3000 - 4500毫秒导致死信
询问超时= 3等待超时= 3 => 0 - 500 ms不会导致死信
我不确定但猜测是调度员在处理死信时需要时间,因此无法开始处理我们的Actor的消息。可能是一些更有经验的人可以解释它。
答案 1 :(得分:0)
@stefanobaghino @Tarun感谢您的帮助,我想我现在明白了。
所以事情是有2次超时可能导致异常:
如果我们必须等待演员响应的时间,那么Ask(?)超时会引发akka.pattern.AskTimeoutException
。
如果我们不等待足够长的时间来完成,Await.result
会引发java.util.concurrent.TimeoutException
。
这两个都可能导致整个事情崩溃。对于第一个,正如您所提到的,我们可以添加recover来返回一些默认值。对于第二个,我们也应该捕获并处理异常。
在以下示例中更改两个超时并删除recover
/ Try
时,您可以看到不同的行为:
object Example {
class NumberActor extends Actor {
override def receive: Receive = {
case num: Int =>
Thread.sleep(250 * num)
sender() ! "OK"
}
}
def main(): Unit = {
implicit val timeout: akka.util.Timeout = 1 seconds
val numActor = ActorSystem("system").actorOf(Props(new NumberActor()))
val range = (1 to 5) ++ (4 to 1 by -1)
println(range)
range.map { r =>
val future = (numActor ? r)
.recover { case ex: TimeoutException => "FAIL" }
.mapTo[String]
Try(Await.result(future, 1 seconds)) match {
case Success(reply) => println(reply)
case Failure(ex) => println(ex)
}
}
}
}