3

我实现了一个简单的作业处理器来处理期货中的子作业(scala.actors.Futures)。这些期货本身可以为处理子作业创建更多期货。现在,如果其中一个子作业引发异常,我希望作业处理器回复该作业的错误消息。我有一个发现失败子作业的解决方案,但我不确定这是否是最好的解决方案。基本上它是这样工作的:

sealed trait JobResult
case class SuccessResult(content: String) extends JobResult
case class FailedResult(message: String) extends JobResult

for(subjob <- subjobs) yield {
  future {
    try {
          SuccessResult(process(subjob))
    } catch {
      case e:Exception => FailedResult(e.getMessage)                              
    }
  }
}

顶层的结果是 JobResults 的 Lists of Lists... 的递归 List。我递归地在列表中搜索失败的结果,然后根据结果的类型返回错误或组合结果。那行得通,但我想知道是否有更优雅/更简单的解决方案来处理期货中的异常?

4

2 回答 2

5

你现在做的方式,本质上就是 scala.Either 的设计目的。见http://www.scala-lang.org/api/current/scala/Either.html

于 2011-04-19T14:03:44.780 回答
2

现代 scala 期货就像它们Either包含一个成功的结果或一个Throwable. 如果您在 scala 2.10 中重新访问此代码,我想您会发现这种情况非常愉快。

具体来说,scala.concurrent.Future[T]在技术上只是 "is-a" Awaitable[T],但_.onComplete两者Await.ready(_, timeout).value.get都将其结果呈现为scala.util.Try[T],这很像Either[Throwable, T]它是结果或异常。

奇怪的是,_.transform它需要两个映射函数,一个是 for T => U,一个是 for Throwable => Throwableand (除非我遗漏了什么)没有将未来映射为Try[T] => Try[U]. Future's.map将允许您通过简单地在映射函数中抛出异常来将成功变为失败,但它仅将其用于原始Future. 它.recover,同样可以化失败为成功。如果您希望能够将成功更改为失败,反之亦然,您需要自己构建一些东西,它是_.map_.recover_.onComplete用于链接到新的scala.concurrent.Promise[U]的组合,如下所示:

import scala.util.{Try, Success, Failure}
import scala.concurrent.{Future, Promise}
import scala.concurrent.ExecutionContext

def flexibleTransform[T,U](fut: Future[T])(f: Try[T] => Try[U])(implicit ec: ExecutionContext): Future[U] = {
  val p = Promise[U]
  fut.onComplete { res =>
    val transformed = f(res)
    p.complete(transformed)
  }
  p.future
}

这将像这样使用:

import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.Await
import scala.concurrent.duration.Duration.Inf

def doIt() {
  val a: Future[Integer] = Future {
    val r = scala.util.Random.nextInt
    if (r % 2 == 0) {
      throw new Exception("we don't like even numbers")
    } else if (r % 3 == 0) {
      throw new Exception("we don't like multiples of three")
    } else {
      r
    }
  }

  val b: Future[String] = flexibleTransform(a) {
    case Success(i) =>
      if (i < 0) {
        // turn negative successes into failures
        Failure(new Exception("we don't like negative numbers"))
      } else {
        Success(i.toString)
      }
    case Failure(ex) =>
      if (ex.getMessage.contains("three")) {
        // nevermind about multiples of three being a problem; just make them all a word.
        Success("three")
      } else {
        Failure(ex)
      }
  }

  val msg = try {
    "success: " + Await.result(b, Inf)
  } catch {
    case t: Throwable =>
      "failure: " + t
  }
  println(msg)
}

for { _ <- 1 to 10 } doIt()

这会给出这样的结果:

failure: java.lang.Exception: we don't like even numbers
failure: java.lang.Exception: we don't like negative numbers
failure: java.lang.Exception: we don't like negative numbers
success: three
success: 1756800103
failure: java.lang.Exception: we don't like even numbers
success: 1869926843
success: three
failure: java.lang.Exception: we don't like even numbers
success: three

(或者你可以用隐式的def “拉皮条”Future到 a中并创建一个成员函数,删除参数并简单地使用)RichFutureWithFlexibleTransformflexibleTransformfutthis

(更好的是获取Try[T] => Future[U]并调用它,flexibleFlatMap以便您可以在转换中执行异步操作)

于 2014-03-16T18:34:45.183 回答