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

2022-08-31 15:15:28

仍然是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);
        }
    };

}

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

它基本上调用调用方法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;

}

答案 1

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

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

用法是这样的:

retry(3) {
  // insert code that may fail here
}

编辑:受到@themel答案的启发而略有不同。少一行代码:-)

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

再次编辑:递归困扰着我,因为它向堆栈跟踪添加了几个调用。由于某种原因,编译器无法在 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)
  }
}

再次编辑:显然,我将把它变成一种爱好,继续回来并为这个答案添加替代方案。这是一个尾递归版本,它比使用更简单,但用于短路函数不是惯用的Scala。Optionreturn

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

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
  }
}

答案 2

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

def retry(delays: Seq[Duration], p: (Throwable) ⇒ Boolean = _.isInstanceOf[Exception]): Task[T]

给定 一个 ,您可以创建一个新的,它将重试一定次数,其中重试之间的延迟由参数定义。例如: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