几个小时后,我仍然没有找到一个(好的)方法来解决这个问题,即结构A使用A配置的助手结构B,以便B对A有一个异步回调:
// Helper for struct DeviceModel. An async timer, such as a thread.
trait AsyncTimer {
// Set a timeout. After that, code code behind callback should be
// executed. The closure owns Arc<DeviceModel> to operate on DeviceModel
// and to ensure its lifetime.
fn configure_timeout(&self, duration: Duration, callback: Box<dyn Fn()>);
}
struct AsyncTimerImpl {
// impl details for thread handling
}
impl Asynctimer for AsyncTimerImpl {
fn configure_timeout(&self, duration: Duration, callback: Box<dyn Fn()>) {
// set next timeout when the thread should call `callback`
}
}
struct DeviceModel {
// Device model owns this relation. AsyncTimer must also be
able to asyncorunously call something on device model.
async_timer: Box<dyn AsyncTimer>
}
impl DeviceModel {
fn setup_next_timeout(&self) {
let this = /* ... */; // actually, I need an Arc here instead of &self...
let cb = Box::new(move || this.handle_timeout() );
async_timer.configure_timeout(Duration::from_secs(1), cb);
}
fn handle_timeout(&self) {}
}
字符串
问题如下:DeviceModel
可以调用self.async_timer.configure_timeout()
,这将影响AsyncTimerImpl
结构体后面运行的线程的行为。当线程触发下一个事件时,它应该调用DeviceModel
上的操作。DevideModel
应该负责在其自身上定义任意计算。
但是,我遇到了循环依赖问题,因为DeviceModel
需要更改AsyncTimer
的状态,并且传递给AsyncTimer
的回调必须调用DeviceModel
内部的代码:
1.不能在两个方向上都使用Arc<>
,因为它可能会泄漏内存
1.在DeviceModel
中有Arc<AsyncTimer>
,在AsyncTimerImpl
中有Weak<DeviceModel>
,解决了1),但仍然需要对两个结构进行非常难看的惰性初始化。
我希望DeviceModel
能够完全控制configure_timeout
以及回调操作,但这似乎不可能解决。
有什么想法吗,我的 rust 齿动物们?PS:AsyncTimer不应该比相应的DeviceModel活得更长。有一辈子的联系就好了。
4条答案
按热度按时间x8diyxa71#
幸运的是,Rust有适合你的东西:
Arc::new_cyclic
。它一起使用Arc
和Weak
,专门设计用于处理循环数据结构,就像这里一样。您的代码可能看起来有点像这样:
字符串
唯一的限制是这需要Rust 1.60或更新版本,但除非你有很强的兼容性要求,否则这应该可以正常工作。
x33g5p2x2#
我发现了一个被诅咒的变种,它使用了不安全的Rust,但是我认为,它是安全的(miri报告说它不安全,我在那里做的事情确实非常受诅咒)。但是考虑到控制流,它应该可以工作。
TL;DR:
trait AsyncTimer
有一个函数fn set_timeout(&self, timestamp: Duration, cb: Box<dyn Fn() + Send>)
。要在device_model.set_timeout()
中创建回调,可以使用以下简单的方法字符串
完整的代码可以在Rust Playground或下面找到。它产生以下输出:
的数据
flmtquvp3#
我不确定我完全理解你的问题。如果我是对的,您正在寻找一种优雅的方式来设置许多异步任务(在单独的线程中),这些任务也可以发出另一个任务。所有这些都必须在
DeviceModel
字段中定义的数据上完成。如果是这样的话,我建议您考虑通道(https://doc.rust-lang.org/rust-by-example/std_misc/channels.html)并以这种方式管理您的
DeviceModel
:字符串
您应该将
data
传递到任务中,以便它可以在那里更改。如果一个任务需要发射另一个任务,它通过定义为tx
和rx
的通道传输新任务的参数,主线程产生新线程。所有关于data
的逻辑都在Data
中实现。由于Rust闭包支持Send
trait,因此可以直接在通道中传递它们(在我的示例中作为Task
结构的一部分)。型
我相信这个模式是非常清晰的,它可以让你避免错误和混乱的代码。
wvmv3b1j4#
在@bk2204提到的
Arc::new_cyclic
的帮助下,我可以满意地解决我的问题。所需的复杂性在合理的范围内。我做了一个改变:回调不是传递
Box<Fn()>
,而是在AsyncTimer::on_timeout
方法上定义。这“感觉”就像一个更好的设计。Rust playground上的代码:Playground link
字符串