Scala - 最终超时和 Thread.sleep() 之间的区别

Scala - differents between eventually timeout and Thread.sleep()

我有一些异步 (ZIO) 代码,我需要对其进行测试。如果我使用 Thread.sleep() 创建一个测试部分,它工作正常并且我总是得到响应:

for {
 saved <- database.save(smth)
 result <- eventually {
   Thread.sleep(20000)
   database.search(...) 
 }
} yield result

但是,如果我使用 timeouteventually 中的 interval 进行相同的逻辑,那么它永远不会正常工作(我超时):

for {
   saved <- database.save(smth)
   result <- eventually(timeout(Span(20, Seconds)), interval(Span(20, Seconds))) {
     database.search(...) 
   }
} yield result

我不明白为什么 timeoutintervalThread.sleep 的工作方式不同。它应该做完全相同的事情。有人可以向我解释一下并告诉我应该如何更改此代码以不需要使用 Thread.sleep()?

假设 database.search(...) returns ZIO[] 对象。

eventually{database.search(...)} 很可能在第一次尝试后立即成功。

它成功创建了一个查询数据库的任务。 然后在没有任何重试逻辑的情况下查询数据库。

关于如何让它发挥作用:

val search: ZIO[Any, Throwable, String] = ???
val retried: ZIO[Any with Clock, Throwable, Option[String]] = search.retry(Schedule.spaced(Duration.fromMillis(1000))).timeout(Duration.fromMillis(20000))

类似的东西应该有用。但我相信存在更优雅的解决方案。

@simpadjo 的另一个回答非常简洁地解决了“什么”问题。我将添加一些额外的上下文来说明您为什么会看到这种行为。

for {
  saved <- database.save(smth)
  result <- eventually {
    Thread.sleep(20000)
    database.search(...) 
  }
} yield result

这里混合了三种不同的技术,这引起了一些混乱。

首先是 ZIO which is an asynchronous programming library that uses it's own custom runtime and execution model to perform tasks. The second is eventually which comes from ScalaTest,可用于通过有效轮询值的状态来检查异步计算。第三,Thread.sleep 是一个 Java api,它实际上是挂起当前线程并阻止任务继续进行,直到计时器到期。

eventually 使用一种简单的重试机制,该机制根据您使用的是普通值还是来自 scala 标准库的 Future 而有所不同。基本上它运行块中的代码,如果它抛出,则它使当前线程休眠,然后根据一些间隔配置重试它,最终超时。值得注意的是,在这种情况下,行为是完全同步的,这意味着只要 {} 中的值不引发异常,它就不会继续重试。

Thread.sleep 是一个重量级操作,在这种情况下,它有效地阻止了传递给 eventually 的函数进行 20 秒。这意味着在调用 database.search 时操作可能已经完成。

第二个变体不同,它会立即执行eventually块中的代码,如果抛出异常,则会根据您提供的interval/timeout逻辑再次尝试。在这种情况下,保存可能尚未完成(如果最终一致,则可能未传播)。因为你 returning 一个 ZIO 被设计 不是 抛出,并且 eventually 不理解 ZIO 它只会return search 尝试没有重试逻辑。

接受的答案:

val retried: ZIO[Any with Clock, Throwable, Option[String]] = search.retry(Schedule.spaced(Duration.fromMillis(1000))).timeout(Duration.fromMillis(20000))

之所以有效,是因为 retrytimeout 正在使用内置的 ZIO 运算符,do 了解如何实际 retrytimeout 一个 ZIO。这意味着如果搜索失败,retry 将处理它直到成功。