我该如何编写一个异步函数来轮询资源,并在资源准备就绪或几秒钟后重试时返回?

时间:2019-08-12 18:30:24

标签: async-await rust

我想编写一个异步函数,该函数反复从Web轮询资源,并在就绪时返回。我正在使用future::poll_fn来实现它:

#![feature(async_await)]

/*
[dependencies]
rand = "0.7.0"
futures-preview = "=0.3.0-alpha.18"
*/

use futures::future;
use rand;
use std::task::Poll;

enum ResourceStatus {
    Ready,
    NotReady,
}
use ResourceStatus::*;

// Mocking the function requesting a web resource
fn poll_web_resource() -> ResourceStatus {
    if rand::random::<f32>() < 0.1 {
        Ready
    } else {
        NotReady
    }
}

async fn async_get_resource() {
    // do other works
    future::poll_fn(|ctx| match poll_web_resource() {
        Ready => Poll::Ready(()),
        NotReady => Poll::Pending,
    })
    .await
}

fn main() {
    futures::executor::block_on(async_get_resource());
}

它不起作用,因为当poll_web_resource()返回NotReady时,任务将永久停放。解决此问题的一种方法是,每次任务返回Pending时都要唤醒它:

future::poll_fn(|ctx| match poll_web_resource() {
    Ready => Poll::Ready(()),
    NotReady => {
        ctx.waker().wake_by_ref();
        Poll::Pending
    }
})
.await

这会创建大量不必要的请求。对于我的用例,理想的情况是在未准备好时每隔几秒钟请求一次资源。这是我当前的解决方法:

future::poll_fn(|ctx| match poll_web_resource() {
    Ready => Poll::Ready(()),
    NotReady => {
        let waker = ctx.waker().clone();
        thread::spawn(move || {
            thread::sleep(Duration.from_millis(5000));
            waker.wake();
        });
        Poll::Pending
    }
})
.await

这有效,但是它使用了一个额外的线程来跟踪超时。我认为应该有一个更好的方法。我该如何习惯地实现同一目标?

1 个答案:

答案 0 :(得分:1)

由于您使用的是async / await关键字,因此请编写一个循环,该循环在资源可用时退出,或者在资源不可用时等待。等待可以使用Tokio的Delay

#![feature(async_await)]

use futures; // 0.3.0-alpha.17
use rand; // 0.7.0
use std::time::Duration;
use tokio::timer; // 0.2.0-alpha.1

enum ResourceStatus {
    Ready,
    NotReady,
}
use ResourceStatus::*;

async fn async_get_resource() {
    const SLEEP_TIME: Duration = Duration::from_secs(1);

    loop {
        match poll_web_resource() {
            Ready => return,
            NotReady => {
                // Don't actually use println in production async code.
                println!("Waiting...");
                timer::Delay::new(tokio::clock::now() + SLEEP_TIME).await;
            }
        }
    }
}

fn poll_web_resource() -> ResourceStatus {
    if rand::random::<f32>() < 0.1 {
        Ready
    } else {
        NotReady
    }
}

fn main() {
    let runtime = tokio::runtime::Runtime::new().expect("Unable to create the runtime");
    let _resource = runtime.block_on(async_get_resource());
}