我正在尝试编写一个通用的set_interval
函数助手:
pub fn set_interval<F, Fut>(mut f: F, dur: Duration)
where
F: Send + 'static + FnMut() -> Fut,
Fut: Future<Output = ()> + Send + 'static,
{
let mut interval = tokio::time::interval(dur);
tokio::spawn(async move {
// first tick is at 0ms
interval.tick().await;
loop {
interval.tick().await;
tokio::spawn(f());
}
});
}
这工作正常,直到从类内部调用它:
fn main() {}
struct Foo {}
impl Foo {
fn bar(&self) {
set_interval(|| self.task(), Duration::from_millis(1000));
}
async fn task(&self) {
}
}
self
不是'static
,我们不能因为tokio::task
而将生命周期参数限制为小于'static
的东西。
是否可以修改set_interval
实现,使其在这种情况下正常工作?
链接到游乐场
<小时 />附言
试图let instance = self.clone();
set_interval(move || instance.task(), Duration::from_millis(1000));
但我也收到一个错误:错误:捕获的变量无法FnMut
闭包正文中转义
修改set_interval实现,使其在这种情况下工作?
没有。尽管spawn
-ingf()
实际上也无济于事,因为它排除了简单的"回调拥有对象"解决方案(因为您需要回调和 future 来拥有对象,或者只是未来)。
我认为这留下了两个解决方案:
- 将所有内容转换为共享可变性
Arc
,回调拥有一个Arc
,然后在每次报价时克隆该克隆并将克隆移动到未来(task
方法)。 - 让未来(
task
)从某个外部源获取对象,而不是在一个外部源上调用,这样中间回调就不需要做任何事情。或者回调可以进行获取并将其移动到未来,相同的差异。
顺便说一下,在这一点上,直接创造未来是有意义的,但允许克隆它。因此,与其进行回调set_interval
不如采用可克隆的未来,它将spawn()
其存储未来的克隆,而不是重新创建它们。
正如@Masklinn所提到的,你可以克隆 Arc来允许这样做。请注意,克隆 Arc不会克隆底层数据,只会克隆指针,因此通常可以这样做,并且不会对性能产生重大影响。
下面是一个示例。以下代码将生成错误async block may outlive the current function, but it borrows data, which is owned by the current function
:
fn main() {
// Error: async block may outlive the current function, but it borrows data, which is owned by the current function
let data = Arc::new("Hello, World".to_string());
tokio::task::spawn(async {
println!("1: {}", data.len());
});
tokio::task::spawn(async {
println!("2: {}", data.len());
});
}
Rust 无助地建议将move
添加到两个异步块中,但这会导致借用错误,因为会有多个所有权。
为了解决这个问题,我们可以为每个任务克隆 Arc,然后将move
关键字添加到异步块中:
fn main() {
let data = Arc::new("Hello, World".to_string());
let data_for_task_1 = data.clone();
tokio::task::spawn(async move {
println!("1: {}", data_for_task_1.len());
});
let data_for_task_2 = data.clone();
tokio::task::spawn(async move {
println!("2: {}", data_for_task_2.len());
});
}