什么是Scala实现像这样的可重试调用的方式?

Mau*_*res 51 java functional-programming scala

仍然是Scala中的新手,我现在正在寻找一种方法来实现以下代码:

@Override
public void store(InputStream source, String destination, long size) {

    ObjectMetadata metadata = new ObjectMetadata();
    metadata.setContentLength(size);
    final PutObjectRequest request = new PutObjectRequest(
            this.configuration.getBucket(), destination, source, metadata);

    new RetryableService(3) {

        @Override
        public void call() throws Exception {
            getClient().putObject(request);
        }
    };

}
Run Code Online (Sandbox Code Playgroud)

在Scala中实现RetryableService实现的相同功能的最佳方法是什么?

它基本上调用了N次调用方法,如果所有这些都失败,则会引发异常,如果它们成功则继续运行.这个没有返回任何东西但是我有另一个版本允许返回一个值(所以,我有两个Java类)我相信我可以用Scala中的单个类/函数.

有任何想法吗?

编辑

java中的当前实现如下:

public abstract class RetryableService {

private static final JobsLogger log = JobsLogger
        .getLogger(RetryableService.class);

private int times;

public RetryableService() {
    this(3);
}

public RetryableService(int times) {
    this.times = times;
    this.run();
}

private void run() {

    RuntimeException lastExceptionParent = null;

    int x = 0;

    for (; x < this.times; x++) {

        try {
            this.call();
            lastExceptionParent = null;
            break;
        } catch (Exception e) {
            lastExceptionParent = new RuntimeException(e);
            log.errorWithoutNotice( e, "Try %d caused exception %s", x, e.getMessage() );

            try {
                Thread.sleep( 5000 );
            } catch (InterruptedException e1) {
                log.errorWithoutNotice( e1, "Sleep inside try %d caused exception %s", x, e1.getMessage() );
            }

        }

    }

    try {
        this.ensure();
    } catch (Exception e) {
        log.error(e, "Failed while ensure inside RetryableService");
    }

    if ( lastExceptionParent != null ) {
        throw new IllegalStateException( String.format( "Failed on try %d of %s", x, this ), lastExceptionParent);
    }   

}

public void ensure() throws Exception {
    // blank implementation
}

public abstract void call() throws Exception;

}
Run Code Online (Sandbox Code Playgroud)

lee*_*777 168

递归+ 第一类函数按名称参数==真棒.

def retry[T](n: Int)(fn: => T): T = {
  try {
    fn
  } catch {
    case e =>
      if (n > 1) retry(n - 1)(fn)
      else throw e
  }
}
Run Code Online (Sandbox Code Playgroud)

用法是这样的:

retry(3) {
  // insert code that may fail here
}
Run Code Online (Sandbox Code Playgroud)

编辑:受@themel的回答启发的微小变化.少一行代码:-)

def retry[T](n: Int)(fn: => T): T = {
  try {
    fn
  } catch {
    case e if n > 1 =>
      retry(n - 1)(fn)
  }
}
Run Code Online (Sandbox Code Playgroud)

再次编辑:递归困扰我,因为它添加了几次调用堆栈跟踪.由于某种原因,编译器无法在catch处理程序中优化尾递归.不过在catch处理程序中的尾递归,优化得很好:-)

@annotation.tailrec
def retry[T](n: Int)(fn: => T): T = {
  val r = try { Some(fn) } catch { case e: Exception if n > 1 => None }
  r match {
    case Some(x) => x
    case None => retry(n - 1)(fn)
  }
}
Run Code Online (Sandbox Code Playgroud)

再次编辑:显然我会把它作为一个爱好继续回来并添加这个答案的替代品.这是一个尾递归版本比使用更简单Option,但使用return短路函数不是惯用的Scala.

@annotation.tailrec
def retry[T](n: Int)(fn: => T): T = {
  try {
    return fn
  } catch {
    case e if n > 1 => // ignore
  }
  retry(n - 1)(fn)
}
Run Code Online (Sandbox Code Playgroud)

Scala 2.10更新.作为我的爱好,我偶尔会重温这个答案.Scala 2.10引入了Try,它提供了一种以尾递归方式实现重试的简洁方法.

// Returning T, throwing the exception on failure
@annotation.tailrec
def retry[T](n: Int)(fn: => T): T = {
  util.Try { fn } match {
    case util.Success(x) => x
    case _ if n > 1 => retry(n - 1)(fn)
    case util.Failure(e) => throw e
  }
}

// Returning a Try[T] wrapper
@annotation.tailrec
def retry[T](n: Int)(fn: => T): util.Try[T] = {
  util.Try { fn } match {
    case util.Failure(_) if n > 1 => retry(n - 1)(fn)
    case fn => fn
  }
}
Run Code Online (Sandbox Code Playgroud)

  • 我读了你所有的解决方案,你真的可以注意到每个解决方案如何改进以前 这就是斯卡拉是爱的原因.这就是scala很棒的原因.美丽.我正在调整最后一个并在每次重试之间添加升级超时.我会尽量保持美丽. (6认同)

Gar*_*ady 7

有一种方法scalaz.concurrent.Task[T]:http://docs.typelevel.org/api/scalaz/nightly/#scalaz.concurrent.Task

def retry(delays: Seq[Duration], p: (Throwable) ? Boolean = _.isInstanceOf[Exception]): Task[T]
Run Code Online (Sandbox Code Playgroud)

给定a Task[T],您可以创建一个新的Task[T],它将重试一定次数,其中重试之间的延迟由delays参数定义.例如:

// Task.delay will lazily execute the supplied function when run
val myTask: Task[String] =
  Task.delay(???)

// Retry four times if myTask throws java.lang.Exception when run
val retryTask: Task[String] =
  myTask.retry(Seq(20.millis, 50.millis, 100.millis, 5.seconds))

// Run the Task on the current thread to get the result
val result: String = retryTask.run
Run Code Online (Sandbox Code Playgroud)


ten*_*shi 6

这是一个可能的实现:

def retry[T](times: Int)(fn: => T) = 
    (1 to times).view flatMap (n => try Some(fn) catch {case e: Exception => None}) headOption
Run Code Online (Sandbox Code Playgroud)

你可以像这样使用它:

retry(3) {
    getClient.putObject(request)
}
Run Code Online (Sandbox Code Playgroud)

retrySome[T]如果正确处理了body并且Nonebody只是抛出异常,也会返回.


更新

如果你想填补最后一个异常,那么你可以采取非常类似的方法,但使用Either而不是Option:

def retry[T](times: Int)(fn: => T) = {
    val tries = (1 to times).toStream map (n => try Left(fn) catch {case e: Exception => Right(e)}) 

    tries find (_ isLeft) match {
        case Some(Left(result)) => result
        case _ => throw tries.reverse.head.right.get
    }
}
Run Code Online (Sandbox Code Playgroud)

另外,正如你所看到的那样,最后,我没有最后一个例外,而是拥有它们.所以AggregatingException如果你愿意,你也可以将它们包装成一些,然后扔掉.(为简单起见,我只抛出最后一个异常)