我很难理解如何编写封装在一个单一结构中的并发异步代码。
我不知道如何准确地解释这个问题,所以我试着用一个例子来解释。
假设我有一个UdpServer结构。此结构有多个与其行为相关的方法(例如,handle_datagram、deserialize_datagram等)
如果我想使代码并发,我将生成tokio任务,它要求提供给它的闭包是静态的,这意味着我不能从这个任务中调用&self,只要&self不是静态的,这意味着我不能调用self.serialize_datagram()。
我理解这个问题(不能保证结构会超过线程),但是找不到解决它的正确方法。我知道将函数移出impl是可能的,但在我看来,这并不是一个很好的解决方案。
此外,即使我们假设有一段时间我可以将&self作为静态代码,但由于某些原因(我想还不够生硬),这段代码在我看来仍然不太正确。
另一个“解决方案”是采用self: Arc<Self>而不是&self,但这感觉更糟。
所以我假设有一些模式我不知道。有人能解释一下我该怎么重构整件事吗?
示例代码:
struct UdpServer {}
impl UdpServer {
pub async fn run(&self) {
let socket = UdpSocket::bind(self.addr).await.unwrap();
loop {
let mut buf: &mut [u8] = &mut [];
let (_, _) = socket.recv_from(&mut buf).await.unwrap();
// I spawn tokio task to enable concurrency
tokio::spawn(async move {
// But i can't use &self in here because it's not static.
let datagram = self.deserialize_datagram(buf).await;
self.handle_datagram(()).await;
});
}
}
pub async fn deserialize_datagram(&self, buf: &mut [u8]) -> Datagram {
unimplemented!()
}
pub async fn handle_datagram(&self, datagram: Datagram) {
unimplemented!()
}
}发布于 2021-11-14 14:23:18
目前,唯一的方法是通过使用self使Arc最后任意长。因为run()是UdpServer上的一个方法,所以需要对Arc<Self>进行更改,您曾经考虑过这个更改,但由于感觉更糟而拒绝了。不过,这就是做这件事的方法:
pub async fn run(self: Arc<Self>) {
let socket = UdpSocket::bind(&self.addr).await.unwrap();
loop {
let mut buf: &mut [u8] = &mut [];
let (_, _) = socket.recv_from(&mut buf).await.unwrap();
tokio::spawn({
let me = Arc::clone(&self);
async move {
let datagram = me.deserialize_datagram(buf).await;
me.handle_datagram(datagram).await;
}
});
}
}有趣的是,smol异步运行时可能实际上提供了您想要的东西,因为它的执行器具有终生的功能。该生存期与调用方环境中的值相关联,执行器上产生的未来值可能会引用它。例如,它编译:
use futures_lite::future;
use smol::{Executor, net::UdpSocket};
struct Datagram;
struct UdpServer {
addr: String,
}
impl UdpServer {
pub async fn run<'a>(&'a self, ex: &Executor<'a>) {
let socket = UdpSocket::bind(&self.addr).await.unwrap();
loop {
let mut buf: &mut [u8] = &mut [];
let (_, _) = socket.recv_from(&mut buf).await.unwrap();
ex.spawn({
async move {
let datagram = self.deserialize_datagram(buf).await;
self.handle_datagram(datagram).await;
}
}).detach();
}
}
pub async fn deserialize_datagram(&self, _buf: &mut [u8]) -> Datagram {
unimplemented!()
}
pub async fn handle_datagram(&self, _datagram: Datagram) {
unimplemented!()
}
}
fn main() {
let server = UdpServer { addr: "127.0.0.1:8080".to_string() };
let ex = Executor::new();
future::block_on(server.run(&ex));
}发布于 2022-03-11 10:14:00
你说的完全正确。在东京教程中,提到了这个解决方案:
如果必须同时从多个任务访问单个数据,则必须使用同步原语(如弧形)共享数据。
https://stackoverflow.com/questions/69955340
复制相似问题