Why does Future::select choose the future with a longer sleep period first?

南楼画角 提交于 2019-11-26 10:03:33

问题


I\'m trying to understand Future::select: in this example, the future with a longer time delay is returned first.

When I read this article with its example, I get cognitive dissonance. The author writes:

The select function runs two (or more in case of select_all) futures and returns the first one coming to completion. This is useful for implementing timeouts.

It seems I don\'t understand the sense of select.

extern crate futures;
extern crate tokio_core;

use std::thread;
use std::time::Duration;
use futures::{Async, Future};
use tokio_core::reactor::Core;

struct Timeout {
    time: u32,
}

impl Timeout {
    fn new(period: u32) -> Timeout {
        Timeout { time: period }
    }
}

impl Future for Timeout {
    type Item = u32;
    type Error = String;

    fn poll(&mut self) -> Result<Async<u32>, Self::Error> {
        thread::sleep(Duration::from_secs(self.time as u64));
        println!(\"Timeout is done with time {}.\", self.time);
        Ok(Async::Ready(self.time))
    }
}

fn main() {
    let mut reactor = Core::new().unwrap();

    let time_out1 = Timeout::new(5);
    let time_out2 = Timeout::new(1);

    let task = time_out1.select(time_out2);

    let mut reactor = Core::new().unwrap();
    reactor.run(task);
}

I need to process the early future with the smaller time delay, and then work with the future with a longer delay. How can I do it?


回答1:


Use tokio::timer

If there's one thing to take away from this: never perform blocking or long-running operations inside of asynchronous operations.

If you want a timeout, use something from tokio::timer, such as Delay or Timeout:

use futures::prelude::*; // 0.1.26
use std::time::{Duration, Instant};
use tokio::timer::Delay; // 0.1.18

fn main() {
    let time_out1 = Delay::new(Instant::now() + Duration::from_secs(5));
    let time_out2 = Delay::new(Instant::now() + Duration::from_secs(1));

    let task = time_out1.select(time_out2);

    tokio::run(task.map(drop).map_err(drop));
}

What's the problem?

To understand why you get the behavior you do, you have to understand the implementation of futures at a high level.

When you call run, there's a loop that calls poll on the passed-in future. It loops until the future returns success or failure, otherwise the future isn't done yet.

Your implementation of poll "locks up" this loop for 5 seconds because nothing can break the call to sleep. By the time the sleep is done, the future is ready, thus that future is selected.

The implementation of an async timeout conceptually works by checking the clock every time it's polled, saying if enough time has passed or not.

The big difference is that when a future returns that it's not ready, another future can be checked. This is what select does!

A dramatic re-enactment:

sleep-based timer

core: Hey select, are you ready to go?

select: Hey future1, are you ready to go?

future1: Hold on a seconnnnnnnn [... 5 seconds pass ...] nnnnd. Yes!

async-based timer

core: Hey select, are you ready to go?

select: Hey future1, are you ready to go?

future1: Checks watch No.

select: Hey future2, are you ready to go?

future2: Checks watch No.

core: Hey select, are you ready to go?

[... 1 second passes ...]

core: Hey select, are you ready to go?

select: Hey future1, are you ready to go?

future1: Checks watch No.

select: Hey future2, are you ready to go?

future2: Checks watch Yes!

The generic solution

When you have have an operation that is blocking or long-running, then the appropriate thing to do is to move that work out of the async loop. See What is the best approach to encapsulate blocking I/O in future-rs? for details and examples.

However, using a threadpool to implement a timeout is highly inefficient; don't actually use this code!

use std::{thread, time::Duration};
use tokio::{prelude::*, runtime::Runtime}; // 0.1.18
use tokio_threadpool; // 0.1.13

fn delay_for(seconds: u64) -> impl Future<Item = u64, Error = tokio_threadpool::BlockingError> {
    future::poll_fn(move || {
        tokio_threadpool::blocking(|| {
            thread::sleep(Duration::from_secs(seconds));
            seconds
        })
    })
}

fn main() {
    let a = delay_for(3);
    let b = delay_for(1);
    let sum = a.join(b).map(|(a, b)| a + b);

    let mut runtime = Runtime::new().expect("Unable to start the runtime");
    let result = runtime.block_on(sum);
    println!("{:?}", result);
}


来源:https://stackoverflow.com/questions/48735952/why-does-futureselect-choose-the-future-with-a-longer-sleep-period-first

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!