为什么在等待点持有非发送类型会导致非发送未来?
Why does holding a non-Send type across an await point result in a non-Send Future?
在 Send
trait 的文档中,有一个很好的例子说明 Rc 之类的东西不是 Send
,因为两个不同线程中的 cloning/dropping 会导致引用计数失去同步。
然而,不太清楚的是,为什么在 async fn
中的 await
点持有对非 Send
类型的绑定会导致生成的未来也不是-Send
。当编译器在异步手册的 work-arounds chapter 中过于保守时,我能够找到解决方法,但它并没有回答我在这里提出的问题。
也许有人可以举例说明为什么在 Future
中输入非 Send
是可以的,但将它放在 await
上却不行?
当您在异步函数中使用 .await
时,编译器会在后台构建一个状态机。每个 .await
引入一个新状态(当它等待某事时)并且其间的代码是状态转换(又名任务),它将根据某些外部事件(例如来自 IO 或计时器等)触发。
每个任务都被安排为由异步运行时执行,可以选择使用与前一个任务不同的线程。如果在线程之间发送状态转换不安全,那么生成的 Future
也不是 Send
,因此如果您尝试在多线程运行时执行它,则会出现编译错误。
一个Future
不是是Send
是完全可以的,这只是意味着你只能在单线程运行时执行它。
Perhaps someone could shed some light on this with an example of why having a non-Send
type in a Future
is ok, but holding it across an await
is not?
考虑以下简单示例:
async fn add_votes(current: Rc<Cell<i32>>, post: Url) {
let new_votes = get_votes(&post).await;
*current += new_votes;
}
编译器将构造一个这样的状态机(简化):
enum AddVotes {
Initial {
current: Rc<Cell<i32>>,
post: Url,
},
WaitingForGetVotes {
current: Rc<Cell<i32>>,
fut: GetVotesFut,
},
}
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
}
}
}
}
}
在多线程运行时,对 poll
的每次调用都可能来自不同的线程,在这种情况下,运行时会 将 移动到 AddVotes
调用 poll
之前的其他线程。这不会起作用,因为 Rc
不能在线程之间发送。
但是,如果未来只是在相同的状态转换中使用Rc
,那就没问题了,例如如果 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
}
在这种情况下,状态机将如下所示:
enum AddVotes {
Initial {
current: i32,
post: Url,
},
WaitingForGetVotes {
current: i32,
fut: GetVotesFut,
},
}
Rc
未在状态机中捕获,因为它是在状态转换(任务)中创建和删除的,因此整个状态机(又名 Future
)仍然是 Send
.
在 Send
trait 的文档中,有一个很好的例子说明 Rc 之类的东西不是 Send
,因为两个不同线程中的 cloning/dropping 会导致引用计数失去同步。
然而,不太清楚的是,为什么在 async fn
中的 await
点持有对非 Send
类型的绑定会导致生成的未来也不是-Send
。当编译器在异步手册的 work-arounds chapter 中过于保守时,我能够找到解决方法,但它并没有回答我在这里提出的问题。
也许有人可以举例说明为什么在 Future
中输入非 Send
是可以的,但将它放在 await
上却不行?
当您在异步函数中使用 .await
时,编译器会在后台构建一个状态机。每个 .await
引入一个新状态(当它等待某事时)并且其间的代码是状态转换(又名任务),它将根据某些外部事件(例如来自 IO 或计时器等)触发。
每个任务都被安排为由异步运行时执行,可以选择使用与前一个任务不同的线程。如果在线程之间发送状态转换不安全,那么生成的 Future
也不是 Send
,因此如果您尝试在多线程运行时执行它,则会出现编译错误。
一个Future
不是是Send
是完全可以的,这只是意味着你只能在单线程运行时执行它。
Perhaps someone could shed some light on this with an example of why having a non-
Send
type in aFuture
is ok, but holding it across anawait
is not?
考虑以下简单示例:
async fn add_votes(current: Rc<Cell<i32>>, post: Url) {
let new_votes = get_votes(&post).await;
*current += new_votes;
}
编译器将构造一个这样的状态机(简化):
enum AddVotes {
Initial {
current: Rc<Cell<i32>>,
post: Url,
},
WaitingForGetVotes {
current: Rc<Cell<i32>>,
fut: GetVotesFut,
},
}
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
}
}
}
}
}
在多线程运行时,对 poll
的每次调用都可能来自不同的线程,在这种情况下,运行时会 将 移动到 AddVotes
调用 poll
之前的其他线程。这不会起作用,因为 Rc
不能在线程之间发送。
但是,如果未来只是在相同的状态转换中使用Rc
,那就没问题了,例如如果 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
}
在这种情况下,状态机将如下所示:
enum AddVotes {
Initial {
current: i32,
post: Url,
},
WaitingForGetVotes {
current: i32,
fut: GetVotesFut,
},
}
Rc
未在状态机中捕获,因为它是在状态转换(任务)中创建和删除的,因此整个状态机(又名 Future
)仍然是 Send
.