从闭包调用异步函数

时间:2021-01-24 14:14:04

标签: asynchronous rust async-await future

我想在迭代器中使用的闭包内 await 一个 async 函数。需要闭包的函数在结构体实现中被调用。我不知道该怎么做。

这段代码模拟了我正在尝试做的事情:

struct MyType {}

impl MyType {
    async fn foo(&self) {
        println!("foo");

        (0..2).for_each(|v| {
            self.bar(v).await;
        });
    }

    async fn bar(&self, v: usize) {
        println!("bar: {}", v);
    }
}

#[tokio::main]
async fn main() {
    let mt = MyType {};
    mt.foo().await;
}

显然,这将不起作用,因为闭包不是 async,给我:

error[E0728]: `await` is only allowed inside `async` functions and blocks
 --> src/main.rs:8:13
  |
7 |         (0..2).for_each(|v| {
  |                         --- this is not `async`
8 |             self.bar(v).await;
  |             ^^^^^^^^^^^^^^^^^ only allowed inside `async` functions and blocks

在寻找如何从非 async 函数调用 async 函数的答案后,我想到了这个:

tokio::spawn(async move {
    self.bar(v).await;
});

但现在我遇到了终身问题:

error[E0759]: `self` has an anonymous lifetime `'_` but it needs to satisfy a `'static` lifetime requirement
 --> src/main.rs:4:18
  |
4 |     async fn foo(&self) {
  |                  ^^^^^
  |                  |
  |                  this data with an anonymous lifetime `'_`...
  |                  ...is captured here...
...
8 |             tokio::spawn(async move {
  |             ------------ ...and is required to live as long as `'static` here

这也不令我感到惊讶,因为据我所知,Rust 编译器无法知道线程将存活多长时间。鉴于此,由 tokio::spawn 生成的线程可能比 MyType 类型的存活时间更长。

我想出的第一个解决方法是使 bar 成为关联函数,复制我在闭包中需要的所有内容并将其作为值传递给 bar 并使用 MyType::bar(copies_from_self) 调用它但这变得越来越难看,因为有很多复制。这也感觉像是一种解决不知道生命周期如何运作的方法。

我改为尝试使用 futures::executor::block_on,它适用于像这篇文章中的任务这样的简单任务:

(0..2).for_each(|v| {
    futures::executor::block_on(self.bar(v));
});

但是,当将其放在我使用第三方库1(也使用 tokio)的现实生活示例中时,事情不再起作用。阅读文档后,我意识到 #[tokio::main] 是一个宏,它最终将所有内容都包装在 block_on 中,因此这样做会嵌套 block_on。这可能是在 async 中调用的 bar 方法之一只是停止工作而没有任何错误或日志记录的原因(没有 block_on 工作,因此代码不应该是任何东西)。我联系了作者,他们说我可以使用 for_each(|i| async move { ... }),这让我更加困惑。

(0..2).for_each(|v| async move {
    self.bar(v).await;
});

会导致编译错误

expected `()`, found opaque type`

我认为这是有道理的,因为我现在返回的是一个未来而不是 ()。我对此的幼稚方法是尝试用这样的方式等待未来:

(0..2).for_each(|v| {
    async move {
        self.bar(v).await;
    }
    .await
});

但这让我回到了第一个,导致了以下编译错误,我也认为这是有道理的,因为我现在又回到在 await 的闭包中使用 sync

only allowed inside `async` functions and blocks` since the 

这个发现也让我很难利用找到的herehere之类的答案。

在所有这些货物崇拜编程之后的问题基本上是,是否可能,如果是的话,我如何从闭包中调用我的 async 函数(最好不产生一个线程来避免生命周期问题)在迭代器中?如果这是不可能的,那么它的惯用实现是什么样的?


1This is the library/method used

1 个答案:

答案 0 :(得分:5)

Iterator::for_each 需要同步闭包,因此您不能在其中使用 .await(至少不能直接使用),也不能从中返回未来。

一种解决方案是只使用 for 循环而不是 .for_each

for v in 0..2 {
    self.bar(v).await;
}

更通用的方法是使用 streams 代替迭代器,因为它们是异步等效的(并且流上的等效方法通常也是异步的)。这不仅适用于 for_each,而且适用于大多数其他迭代器方法:

use futures::prelude::*;

futures::stream::iter(0..2)
    .for_each(|c| async move {
        self.bar(v).await;
    })
    .await;