【发布时间】:2023-02-16 14:22:00
【问题描述】:
我正在使用 Abortable crate 来暂停 Future 的执行。假设我有一个失败的未来,其中异步函数本身等待其他异步函数。我的问题是,如果我中止根 Future,子 Futures 会同时立即中止,还是会悬空?
我阅读了Abortable的源代码,特别是try_poll的代码:
fn try_poll<I>(
mut self: Pin<&mut Self>,
cx: &mut Context<'_>,
poll: impl Fn(Pin<&mut T>, &mut Context<'_>) -> Poll<I>,
) -> Poll<Result<I, Aborted>> {
// Check if the task has been aborted
if self.is_aborted() {
return Poll::Ready(Err(Aborted));
}
// attempt to complete the task
if let Poll::Ready(x) = poll(self.as_mut().project().task, cx) {
return Poll::Ready(Ok(x));
}
// Register to receive a wakeup if the task is aborted in the future
self.inner.waker.register(cx.waker());
// Check to see if the task was aborted between the first check and
// registration.
// Checking with `is_aborted` which uses `Relaxed` is sufficient because
// `register` introduces an `AcqRel` barrier.
if self.is_aborted() {
return Poll::Ready(Err(Aborted));
}
Poll::Pending
}
我的理解是,一旦abort被调用,它就会传播到下游 Futures,即当根 Future 中止时,它将停止轮询其子 Future(因为 Poll::Ready(Err(Aborted)) 将被返回),这将依次停止轮询它的孩子。如果这个推理是正确的,那么调用 abort 的效果是立竿见影的。
另一个论点是,如果 Future 是基于拉取的,则应首先调用根节点,然后传播到子任务,直到叶节点被调用并中止(然后返回到根节点)。这意味着在调用 abort 方法与叶 Future 实际停止轮询之间存在延迟。可能是相关的,但是这个blogpost 提到了悬而未决的任务,我担心是这种情况。
例如,这是我写的一个玩具示例:
use futures::future::{AbortHandle, Abortable};
use tokio::{time::sleep};
use std::{time::{Duration, SystemTime}};
/*
* main
* \
* child
* | \
* | \
* leaf1 leaf2
*/
async fn leaf2() {
println!("This will not be printed")
}
async fn leaf1(s: String) {
println!("[{:?}] ====== in a ======", SystemTime::now());
for i in 0..100000 {
println!("[{:?}] before sleep i is {}", SystemTime::now(), i);
sleep(Duration::from_millis(1)).await;
println!("[{:?}] {}! i is {}", SystemTime::now(), s.clone(), i);
}
}
async fn child(s: String) {
println!("[{:?}] ====== in child ======", SystemTime::now());
leaf1(s.clone()).await;
leaf2().await
}
#[tokio::main]
async fn main() {
let (abort_handle, abort_registration) = AbortHandle::new_pair();
let result_fut = Abortable::new(child(String::from("Hello")), abort_registration);
tokio::spawn(async move {
println!("{:?} ^^^^^ before sleep ^^^^^", SystemTime::now());
sleep(Duration::from_millis(100)).await;
println!("{:?} ^^^^^ after sleep, about to abort ^^^^^", SystemTime::now());
abort_handle.abort();
println!("{:?} ***** operation aborted *****", SystemTime::now());
});
println!("{:?} ====== before main sleeps ======", SystemTime::now());
sleep(Duration::from_millis(5)).await;
println!("{:?} ====== after main wakes up from sleep and now getting results \
======", SystemTime::now());
result_fut.await.unwrap();
}
Rust playground
我个人更倾向于第一个论点,即根的流产和叶的流产之间没有延迟,因为叶不需要知道它需要中止(叶仅在根告诉它时才拉动)。上面的例子打印了child被执行的时间和root被中止的时间。 child 的执行总是在 root 被中止之前,但是我不确定这是否可以证明我的第一个论点是正确的,所以我想知道大家的想法!
【问题讨论】:
标签: rust async-await rust-tokio