关于rust:如何使用Tokio产生许多可取消的计时器?

How do I spawn many cancellable timers using Tokio?

如何使用Tokio实现固定数量的计时器,这些计时器在线程之间定期重置和取消?当计时器到期时,将执行回调。

我想要的是类似于Go time.AfterFunc的API:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package main

import (
   "fmt"
   "time"
)

func main() {
    t := time.AfterFunc(time.Hour, func() {
        // happens every 2 seconds with 1 second delay
        fmt.Println("fired")
    })

    for {
        t.Reset(time.Second)
        time.Sleep(time.Second * 2)
    }
}

我发现唯一实现(足够)相似的API的板条箱是计时器,它通过产生2个线程以非常幼稚的方式实现。当经常重置计时器时,这很快变得令人望而却步。

显而易见的答案是使用Tokio,问题是如何优雅地做到这一点。

一种选择是,每当更新计时器时,都会生成一个新的绿色线程,并使用一个原子通过取消对该原子的回调执行条件来取消前一个计时器,例如伪锈:

1
2
3
4
5
6
7
8
9
10
11
12
13
tokio::run({
    // for every timer spawn with a new"cancel" atomic
    tokio::spawn({
        Delay::new(Instant::now() + Duration::from_millis(1000))
            .map_err(|e| panic!("timer failed; err={:?}", e))
            .and_then(|_| {
                if !cancelled.load(Ordering::Acquire) {
                    println!("fired");
                }
                Ok(())
            })
    })
})

问题是我为已经取消的计时器保持状态,可能要持续几分钟。另外,它看起来并不优雅。

除了tokio::time::Delaytokio::time::DelayQueue似乎也适用。特别是,通过使用从"插入"中返回的Key引用计时器来重置和取消计时器的能力。

尚不清楚如何在多线程应用程序中使用此库,即:

The return value represents the insertion and is used at an argument to remove and reset. Note that Key is token and is reused once value is removed from the queue either by calling poll after when is reached or by calling remove. At this point, the caller must take care to not use the returned Key again as it may reference a different item in the queue.

这将在任务通过其键取消计时器和使用DelayQueue流中的计时器事件的任务之间创建竞争条件,从而导致恐慌或取消无关的计时器。


您可以将futures-rs中的Select组合器与Tokio一起使用。 它返回第一个完成的Future的结果,然后忽略/停止轮询另一个。

作为第二个未来,我们可以使用来自oneshot::channel的接收器来创建一个信号,以完成组合器的未来。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
use futures::sync::oneshot;
use futures::*;
use std::thread;
use std::time::{Duration, Instant};
use tokio::timer::Delay;

fn main() {
    let (interrupter, interrupt_handler) = oneshot::channel::<()>();

    //signal to cancel delayed call
    thread::spawn(move || {
        thread::sleep(Duration::from_millis(500)); //increase this value more than 1000ms to see is delayed call is working or not.
        interrupter
            .send(())
            .expect("Not able to cancel delayed future");
    });

    let delayed = Delay::new(Instant::now() + Duration::from_millis(1000))
        .map_err(|e| panic!("timer failed; err={:?}", e))
        .and_then(|_| {
            println!("Delayed Call Executed!");

            Ok(())
        });

    tokio::run(delayed.select(interrupt_handler).then(|_| Ok(())));
}

操场