在新线程中从共享库执行异步函数 (Rust)

Execute asyncronus function from shared library in a new thread (Rust)

我正在关注 Michael-F-Bryan 的 Rust FFI 指南中的 Dynamic Loading & Plugins 章节,但我将插件存储在 HashMap (HashMap<&'static str, Box<dyn Plugin>) 而不是 Vec 这样我就可以单独调用 Plugin 的函数了。

我希望插件定义一个异步函数,它有自己的循环,使用通道 (std or tokio) 与应用程序的主要部分进行通信。

由于 async_trait 板条箱,解决 traits 中不能有 async 函数的问题很容易,但我现在面临的问题是,我无法生成一个使用模块的新线程,因为新线程可能比 PluginManager.

寿命更长

我试图在没有动态模块的 rust playground 中重新创建它,但我遇到了同样的错误here(注意:这不包括任何类型的通道通信)

与第一个错误不同,我无法为第二个错误重新创建一个 Rust 游乐场(因为它发生在运行时)。我没有生成新线程,而是使用 tokio 的事件循环来处理 async 函数。这在 sandbox 中有效,但在使用共享库作为插件时无效。在运行时它抛出:

thread '<unnamed>' panicked at 'there is no timer running, must be called from the context of Tokio runtime', /home/admin/.cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.0.1/src/time/driver/handle.rs:50:18

如果有人知道解决这个问题的方法,或者甚至有同样的问题,如果你能与我分享它们就太好了,因为我已经尝试了很多但没有对我有利。

您已经认识到这个问题:对 PluginManager 拥有的对象的引用正在被移动到正在生成的未来,因此可能 运行 在另一个线程而不是拥有 PluginManager 的人。编译器无法知道未来不会超过 PluginManager.

对此有多种可能的解决方案,例如:

  • 将插件实例存储在 Arc<Box<T>> 中,以便在 运行 时间内对它们进行引用计数。那么即使插件管理器的寿命没有你生成的 futures 长,也没关系

  • 使PluginManager成为不可变的静态单例

在这种情况下,我怀疑您希望 PluginManager 成为单例。您可以通过将 new 构造函数替换为延迟构造实例的 get 方法和 returns 对它的引用来实现:

use once_cell::sync::Lazy;

impl PluginManager {

    pub fn get() -> &'static Self {
        static PLUGINMANAGER: Lazy<PluginManager> = Lazy::new(|| {
            let mut mgr = PluginManager {
                plugins: HashMap::new()
            };
            mgr.load_plugins();
            mgr
        });
        &PLUGINMANAGER
    }
// ...
}

请注意,此惰性构造函数会完全初始化静态实例,包括加载插件。

它使用了 once_cell crate. This functionality is also available in nightly std 中的 once_cell::sync::Lazy,所以它最终会成为标准库的一部分。

spawn_plugins 函数需要稍微修改以指定它需要静态自身:

pub async fn spawn_plugins(&'static self) {
//...

最后你的主要功能变成了:

async fn main() {
    let mgr = PluginManager::get();
    mgr.spawn_plugins().await;
}

Playground Link

关于您的其他问题,这是一个单独的问题 - 如果您一次 post 解决一个问题,则堆栈溢出效果最好。