为什么在等待点上持有非发送类型会导致非发送未来?

all*_*y87 2 asynchronous future rust

Sendtrait的文档中,有一个很好的例子说明了 Rc 之类的东西不是这样的Send,因为在两个不同的线程中克隆/删除会导致引用计数不同步。

然而,不太清楚的是,为什么在 a 中的Send一个await点上持有对非类型的绑定async fn会导致生成的未来也是非类型Send。当编译器在异步手册的变通方法一章中过于保守时,我能够找到变通方法,但它并没有回答我在这里提出的问题。

也许有人可以举例说明为什么在 a 中有一个非Send类型是Future可以的,但将它放在anawait上却不是?

Pet*_*all 6

当您.await在异步函数中使用时,编译器会在幕后构建一个状态机。每个都.await引入了一个新状态(当它等待某事时),中间的代码是状态转换(又名任务),这将根据一些外部事件(例如来自 IO 或计时器等)触发。

每个任务都被调度为由异步运行时执行,它可以选择使用与前一个任务不同的线程。如果状态转换在线程之间发送是不安全的,那么结果Future也不是Send这样,如果您尝试在多线程运行时中执行它,则会出现编译错误。

a Future not be完全可以Send,它只是意味着您只能在单线程运行时中执行它。


也许有人可以举例说明为什么在 a 中有一个非Send类型是Future可以的,但将它放在anawait上却不是?

考虑以下简单示例:

async fn add_votes(current: Rc<Cell<i32>>, post: Url) {
    let new_votes = get_votes(&post).await;
    *current += new_votes;
}
Run Code Online (Sandbox Code Playgroud)

编译器将构造一个这样的状态机(简化):

enum AddVotes {
    Initial {
        current: Rc<Cell<i32>>,
        post: Url,
    },
    WaitingForGetVotes { 
        current: Rc<Cell<i32>>,
        fut: GetVotesFut,
    },
}
Run Code Online (Sandbox Code Playgroud)
impl AddVotes {
    fn new(current: Rc<Cell<i32>>, post: Url) {
        AddVotes::Initial { current, post }
    }

    fn poll(&mut self) -> Poll {
        match self {
            AddVotes::Initial(state) => {
                let fut = get_votes(&state.post);
                *self = AddVotes::WaitingForGetVotes {
                     current: state.current,
                     fut
                }
                Poll::Pending
            }
            AddVotes::WaitingForGetVotes(state) => {
                if let Poll::Ready(votes) = state.fut.poll() {
                    *state.current += votes;
                    Poll::Ready(())
                } else {
                    Poll::Pending
                }
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

在多线程运行时,每次调用poll可能是从不同的线程,在这种情况下,运行时会移动AddVotes调用之前,其他线程poll就可以了。这将不起作用,因为Rc不能在线程之间发送。

但是,如果未来只是Rc同一个状态转换中使用 an ,那就没问题了,例如,如果votes只是一个i32

async fn add_votes(current: i32, post: Url) -> i32 {
    let new_votes = get_votes(&post).await;

    // use an Rc for some reason:
    let rc = Rc::new(1);
    println!("rc value: {:?}", rc);

    current + new_votes
}
Run Code Online (Sandbox Code Playgroud)

在这种情况下,状态机将如下所示:

enum AddVotes {
    Initial {
        current: i32,
        post: Url,
    },
    WaitingForGetVotes { 
        current: i32,
        fut: GetVotesFut,
    },
}
Run Code Online (Sandbox Code Playgroud)

Rc因为它是建立和状态变化(任务)中的下降并不在状态机拍摄的,所以整个状态机(又名Future)仍然是Send