rust - 为什么这两个非常相似的异步 Rust 函数之一会触发线程安全错误?

标签 rust async-await thread-safety future wasm-bindgen

我正在尝试一个学习 Rust 项目,其中我有一个库,它通过 HTTP 请求特征使用一些 REST API,我计划分别填写该特征以供 native 和 WebAssembly 使用,以便我可以绑定(bind)这个库在不同的环境中。

我的问题出现在 WASM 部分,我正在尝试调整 fetch example here :

pub async fn run(url: String, authz: String) -> Result<serde_json::Value, JsValue> {
    let mut opts = RequestInit::new();
    opts.method("GET");
    opts.mode(RequestMode::Cors);

    let request = Request::new_with_str_and_init(&url, &opts)?;

    request.headers().set("Authorization", &authz)?;

    let window = web_sys::window().unwrap();
    let resp_value = JsFuture::from(window.fetch_with_request(&request)).await?;

    assert!(resp_value.is_instance_of::<Response>());
    let resp: Response = resp_value.dyn_into().unwrap();

    let json = JsFuture::from(resp.json()?).await?;

    let parsed: serde_json::Value = json.into_serde().unwrap();
    Ok(parsed)
}

我做了一些轻微的调整来适应我的目的,但效果很好。不起作用的是我试图将其硬塞到特征界面中。返回类型是 anyhow::Result,并且我 unwrap() 一堆我不应该放在这里的东西,以暂时避免错误类型兼容性问题:

#[async_trait]
impl FetchRequest for WasmFetchRequest {
    async fn get(&mut self) -> Result<serde_json::Value> {
        let mut opts = RequestInit::new();
        opts.method("GET");
        opts.mode(RequestMode::Cors);

        let request = Request::new_with_str_and_init(&self.path, &opts).unwrap();

        request.headers().set("Authorization", &self.authz);

        let window = web_sys::window().unwrap();
        let resp_value = JsFuture::from(window.fetch_with_request(&request)).await.unwrap();

        assert!(resp_value.is_instance_of::<Response>());
        let resp: Response = resp_value.dyn_into().unwrap();

        let json = JsFuture::from(resp.json().unwrap()).await.unwrap();

        let parsed: serde_json::Value = resp.into_serde().unwrap();
        Ok(parsed)
        /*
        // hoped this might work too, same errors
        Ok(run(
            self.path.to_string(),
            self.authz.to_string()
        ).await.map_err(|err| err.into())?);
        */
    }
}

构建不满意:

error: future cannot be sent between threads safely
  --> src/lib.rs:66:58
   |
66 |       async fn get(&mut self) -> Result<serde_json::Value> {
   |  __________________________________________________________^
67 | |         /*
68 | |         Ok(run(
69 | |             self.path.to_string(),
...  |
91 | |         Ok(parsed)
92 | |     }
   | |_____^ future created by async block is not `Send`
   |
   = help: within `impl Future`, the trait `Send` is not implemented for `Rc<RefCell<wasm_bindgen_futures::Inner>>`
note: future is not `Send` as it awaits another future which is not `Send`
  --> src/lib.rs:83:26
   |
83 |         let resp_value = JsFuture::from(window.fetch_with_request(&request)).await.unwrap();
   |                          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ await occurs here on type `JsFuture`, which is not `Send`
   = note: required for the cast to the object type `dyn Future<Output = Result<Value, rescale_core::Error>> + Send`

error: future cannot be sent between threads safely
  --> src/lib.rs:66:58
   |
66 |       async fn get(&mut self) -> Result<serde_json::Value> {
   |  __________________________________________________________^
67 | |         /*
68 | |         Ok(run(
69 | |             self.path.to_string(),
...  |
91 | |         Ok(parsed)
92 | |     }
   | |_____^ future created by async block is not `Send`
   |
   = help: within `impl Future`, the trait `Send` is not implemented for `*mut u8`
note: future is not `Send` as this value is used across an await
  --> src/lib.rs:88:20
   |
83 |         let resp_value = JsFuture::from(window.fetch_with_request(&request)).await.unwrap();
   |             ---------- has type `wasm_bindgen::JsValue` which is not `Send`
...
88 |         let json = JsFuture::from(resp.json().unwrap()).await.unwrap();
   |                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ await occurs here, with `resp_value` maybe used later
...
92 |     }
   |     - `resp_value` is later dropped here
   = note: required for the cast to the object type `dyn Future<Output = Result<Value, rescale_core::Error>> + Send`

error: future cannot be sent between threads safely
  --> src/lib.rs:66:58
   |
66 |       async fn get(&mut self) -> Result<serde_json::Value> {
   |  __________________________________________________________^
67 | |         /*
68 | |         Ok(run(
69 | |             self.path.to_string(),
...  |
91 | |         Ok(parsed)
92 | |     }
   | |_____^ future created by async block is not `Send`
   |
   = help: within `Request`, the trait `Sync` is not implemented for `*mut u8`
note: future is not `Send` as this value is used across an await
  --> src/lib.rs:83:26
   |
83 |         let resp_value = JsFuture::from(window.fetch_with_request(&request)).await.unwrap();
   |                          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ first, await occurs here, with `&request` maybe used later...
note: `&request` is later dropped here
  --> src/lib.rs:83:92
   |
83 |         let resp_value = JsFuture::from(window.fetch_with_request(&request)).await.unwrap();
   |                                                                   --------                 ^
   |                                                                   |
   |                                                                   has type `&Request` which is not `Send`
help: consider moving this into a `let` binding to create a shorter lived borrow
  --> src/lib.rs:83:41
   |
83 |         let resp_value = JsFuture::from(window.fetch_with_request(&request)).await.unwrap();
   |                                         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   = note: required for the cast to the object type `dyn Future<Output = Result<Value, rescale_core::Error>> + Send`

导致这些错误的相关差异是什么?我认为它一定是 &mut self 或返回类型的东西,但我超出了我的深度。

可能不相关,但 native 对应项足够适合界面:

#[async_trait]
impl FetchRequest for NativeFetchRequest {
    async fn get(&mut self) -> Result<serde_json::Value> {
        let client = reqwest::Client::new();
        let mut req = client.get(&self.path);
        req = req.header("Authorization", self.authz.as_str());
        let res = req.send().await?;
        res.error_for_status()?
            .json::<serde_json::Value>().await
            .map_err(|err| err.into())
    }
}

最佳答案

根据async_trait documentation ,异步特征方法返回的 futures 默认必须是 Send :

Async fns get transformed into methods that return Pin<Box<dyn Future + Send + 'async>> and delegate to a private async freestanding function.

您的async fn产生了非Send future 。那么你的原始代码和使用 async_trait 的代码之间的区别是原始代码不需要 Send future ,非Send也没关系,而 async_trait默认情况下需要 Send future 。

要解决此问题,您需要告诉 async_trait not to require Send 使用#[async_trait(?Send)]关于特征和 impl block 。换句话说,替换 #[async_trait]#[async_trait(?Send)]在特征声明和实现中,您的代码应该可以编译。 ( Playground .)

关于rust - 为什么这两个非常相似的异步 Rust 函数之一会触发线程安全错误?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/69694548/

相关文章:

rust - Rust 和旧迭代器模式中的生命周期

struct - 如何存储和使用接受引用并返回 future 的可选闭包?

c# - ASP.NET Controller : An asynchronous module or handler completed while an asynchronous operation was still pending

java - 调用wait()方法后锁被释放?

types - 如何键入注释宏的返回值?

data-structures - 字符串的循环缓冲区

.net - .net核心中是否有DBSet.UpdateAsync()和RemoveAsync()?

java - 哈希表中的并发性和故障安全行为

java - Spring和Hibernate : Multiple connections,线程安全

assembly - 使用Rust内联汇编时奇怪的机器代码字节顺序