如何在没有 java.util.Timer 的情况下实现延迟的未来?
Posted
技术标签:
【中文标题】如何在没有 java.util.Timer 的情况下实现延迟的未来?【英文标题】:How to implement a delayed future without java.util.Timer? 【发布时间】:2021-06-21 00:30:36 【问题描述】:我正在编写一个简单的函数来返回一个在给定延迟后完成的Future[Unit]
。
def delayedFuture(delay: FiniteDuration): Future[Unit] =
val promise = Promise[Unit]()
val timerTask = new java.util.TimerTask
override def run(): Unit = promise.complete(Success(()))
new java.util.Timer().schedule(timerTask, delay.toMillis)
promise.future
这个实现可能可以工作,但我不喜欢为每次调用创建一个新的Timer
,因为每个Timer
实例都会创建一个线程。我可以将Timer
实例作为参数传递给delayedFuture
,但我不希望客户端代码知道Timer
。所以我想我根本不能使用java.util.Timer
。
我想使用ExecutionContext
进行任务调度,也许可以像这样定义delayedFuture
:
def delayedFuture(delay: FiniteDuration)
(implicit ec: ExecutoionContext): Future[Unit] = ???
在没有java.util.Timer
的情况下像这样实现delayedFuture
的最简单方法是什么?
【问题讨论】:
确实有点复杂——说到定时器,有一些重要的因素会影响系统运行时的性能。支持什么精度?支持什么分辨率?计时器的可扩展性如何(即一次可以轻松处理多少个任务)?定时器的生命周期是如何管理的? (即如果计时器关闭,计划的事情不会被执行吗?谁关闭了它?) 哇!这是很多问题。不确定我能回答他们... 我知道 :) 这也是为什么我还没有为 Scala Futures 添加内置 Timer 工具的原因。这是一个困难的问题空间——一般地解决。 确实——将事物变成全局变量会使它们的测试变得极其棘手。一个选项是使实现可实例化以进行测试并在其上执行测试。但这意味着将计时器与使用全局计时器的逻辑分开测试。除非在测试期间使用一些巧妙的技巧来用模拟(反射?)替换全局。 不要使用java.util.Timer
。它早已被ScheduledExecutorService
取代。您可以创建一个实例,例如通过Executors.newScheduledThreadPool(int)
或Executors.newSingleThreadScheduledExecutor()
。
【参考方案1】:
您不需要为每次调用创建一个新的Timer
。只需制作一个全球性的。
object Delayed
private val timer = new Timer
def apply[T](delay: Duration)(task: => T): Future[T] =
val promise = Promise[T]()
val tt = new TimerTask
override def run(): Unit = promise.success(task)
timer.schedule(tt, delay.toMillis)
promise.future
def unit(delay: Duration) = apply[Unit](delay) ()
然后,Delayed.unit(10 seconds)
会为您提供一个在 10 秒内满足的未来单元。
【讨论】:
嗯...我担心这段代码是不可测试的:((因为java.util.Timer
使用系统时钟...
@Michael 是的,这是一个示例,用于说明如何在每次需要计时器时不创建计时器实例。为了使其可测试,需要付出一些额外的努力(使其成为一个类,以timer
作为参数),然后让对象扩展它,传递系统计时器用于产品,并在测试中使用模拟计时器创建实例。相当标准的东西,没有火箭科学。
你是对的。它不应该很复杂。谢谢。【参考方案2】:
您可以使用 Java 的ScheduledExecutorService
s 之一,例如创建一个单线程池并仅用于调度:
val svc = new ScheduledThreadPoolExecutor(1, (r: Runnable) =>
val t = new Thread(r)
t.setName("future-delayer")
t.setDaemon(true) // don't hog the app with this thread existing
t
)
def delayedFuture(d: FiniteDuration) =
val p = Promise[Unit]()
svc.schedule(() => p.success(()), d.length, d.unit)
p.future
【讨论】:
这个回复回答了这个问题,但我猜delayedFuture
应该接收ScheduledExecutorService
作为参数,但我不希望客户端代码知道ScheduledExecutorService
。我应该澄清这个问题......
我建议您将一个作为私有 val 隐藏在具有 delayedFuture
的对象中。你不能单独使用ExecutionContext
。在monix async/effect 库中,我们推出了自己的Scheduler
类型,因此我们处理延迟的方式与我在这里发布的非常相似——我们将EC 与可以进行调度的东西结合起来,例如一个线程调度的执行器。
另外值得注意的是,futures 将快速切换到在下一次.map
/.flatMap
调用时提供的 ExecutionContext,这样除了调度之外不会占用一个线程。
如果我有一个具有delayedFuture
的对象,我想我会使用java.util.Timer
。
据我了解,您建议创建object DelayedFutureFactory private val svc: ScheduledThreadPoolExecutor = ???; def delayedFuture(delay: FiniteDuration): Future[Unit] = ???
好的。也许这就是解决方案...以上是关于如何在没有 java.util.Timer 的情况下实现延迟的未来?的主要内容,如果未能解决你的问题,请参考以下文章
定时任务,客户页面操作,使用java.util.timer(要求时间精确度不高的定时任务)