主动异步隔离 (Async Isolation)
本页是"主动异步隔离层"的参考。 它把
build/set_state/ SDO 读写 / 静态扫描这些会阻塞总线/邮箱的同步操作,包装成async fn+.await的异步版本:.await即把阻塞 FFI 丢spawn_blocking后台执行、可取消、带进度,避免堵塞 tokio runtime,并保证同一主站任意时刻只有一个操作打总线。语义对齐 C# —— 行为与 C# 主动异步隔离 逐点一致:每主站串行闸(
tokio::sync::Mutex<()>,等价SemaphoreSlim(1,1))、关闭守门、取消时自动清理中断状态、独立静态扫描闸。命名遵循 Rust 惯例(snake_case+async fn)。旧同步 API 全部保留。
异步隔离层门控于 --features async-tokio。默认不启用时不引入 tokio 依赖,纯同步 API 仍可用(向后兼容)。CancelToken / ProgressFn 由 crate 根 re-export。
[dependencies]
darra-ethercat-master = { version = "2.7", features = ["async-tokio"] }
一、设计要点
| 要点 | 实现 |
|---|---|
| 串行 (Serial) | 每主站一把 tokio::sync::Mutex<()>(按 master 索引存进程内映射表,等价 SemaphoreSlim(1,1))。lock_owned() 跨 .await 持有到后台完成,同主站的 *_async 排队串行,绝不并发打总线。 |
| 后台隔离 (Isolation) | 阻塞 FFI 一律 tokio::task::spawn_blocking 丢 blocking 线程池,调用方只 .await,不堵 async runtime / 主任务。 |
| 守门 (Shutdown Guard) | mark_shutdown(index) 置 AtomicBool;dispose_async 立即置位,拿闸后 + 后台进 native 前各查一次 shutting_down,防 use-after-free。 |
| 取消 (Cancel) | 轻量 CancelToken(Arc<AtomicBool>,不引 tokio-util)。排队前 / 拿闸后 / 进 native 前多点 check();后台 watcher 命中即中断正在执行的总线/邮箱调用,并在中断后自动清理中断状态,确保后续操作不被残留状态影响。主站与静态扫描各用独立的取消通道,互不干扰。MutexGuard RAII 天然规避二次释放。 |
| 进度 (Progress) | ProgressFn = Arc<dyn Fn(&str) + Send + Sync>,在后台 blocking 线程触发,调用方需自己 marshal 回 UI / 用 channel 转发。 |
所有进度回调都在后台 blocking 线程触发,不是主任务 / UI 线程。用 channel(如 tokio::sync::mpsc)把消息转发回主任务,或用 GUI 框架的线程安全通道(egui Context::request_repaint / Tauri emit),不要在回调里直接改 UI。
let (tx, mut rx) = tokio::sync::mpsc::unbounded_channel();
let progress: ProgressFn = Arc::new(move |msg: &str| {
let _ = tx.send(msg.to_string()); // 后台线程只发 channel
});
builder.build_async(Some(progress), None).await?;
二、API 总览
| 类别 | 方法 | 返回类型 | 读写 | 说明 |
|---|---|---|---|---|
| 主站一条龙 | MasterBuilder::build_async(self, progress, token) | Result<BuildResult> | 异步 | 异步执行 连接→扫描→配置→进 OP,带进度与取消 |
| 状态切换 | set_state_async_isolated(&mut self, state, progress, token) | Result<()> | 异步 | 异步状态切换,经串行闸排队 |
| 静态扫描 | scan_async(&str, Option<&str>, token) | Result<Vec<ScannedSlaveInfo>> | 异步 | 异步静态扫描,走独立扫描闸 + 独立取消通道 |
| 生命周期 | dispose_async(&mut self) | Result<()> | 异步 | 异步释放,先置 shutting_down 再释放 native |
| 从站 SDO | Slave::sdo_read_isolated(&self, index, sub, complete, token) | Result<Vec<u8>> | 异步 | 异步读 SDO,经父主站同一把闸 |
| 从站 SDO | Slave::sdo_write_isolated(&self, index, sub, complete, data, token) | Result<()> | 异步 | 异步写 SDO |
| 取消令牌 | CancelToken | struct | — | 轻量取消令牌(Arc<AtomicBool>),crate 根 re-export |
| 进度类型 | ProgressFn | type | — | Arc<dyn Fn(&str) + Send + Sync>,crate 根 re-export |
旧的
set_state_async/sdo_read_async等已有签名全部保留、行为 0 改动,向后兼容。
三、方法详解
MasterBuilder::build_async
pub async fn build_async(
self,
progress: Option<ProgressFn>,
token: Option<CancelToken>,
) -> Result<BuildResult>
异步执行"连接 → 扫描 → 配置 → 进 OP"一条龙。.await 时阻塞 FFI 在 spawn_blocking 后台串行执行。
参数:
progress(Option<ProgressFn>) — 进度回调,在后台线程触发,调用方自行 marshal(可选)token(Option<CancelToken>) — 取消令牌;取消时隔离层中断正在执行的总线调用并自动清理中断状态(可选)
返回值:
Result<BuildResult>— 构建结果,失败为Err
示例:
let token = CancelToken::new();
let master = MasterBuilder::new()
.nic("以太网")
.build_async(None, Some(token.clone()))
.await?;
set_state_async_isolated
pub async fn set_state_async_isolated(
&mut self,
state: EcState,
progress: Option<ProgressFn>,
token: Option<CancelToken>,
) -> Result<()>
异步状态切换,经主站串行闸排队,与同主站的 build / SDO 互斥串行。
示例:
master.set_state_async_isolated(EcState::Op, None, None).await?;
Slave::sdo_read_isolated / sdo_write_isolated
pub async fn sdo_read_isolated(&self, index: u16, sub: u8, complete: bool, token: Option<CancelToken>) -> Result<Vec<u8>>
pub async fn sdo_write_isolated(&self, index: u16, sub: u8, complete: bool, data: Vec<u8>, token: Option<CancelToken>) -> Result<()>
异步 SDO 读写。经父主站的同一把串行闸排队,绝不与同主站 PDO / 状态切换并发打邮箱。
示例:
let data = master.slave(1).sdo_read_isolated(0x6041, 0x00, false, None).await?;
master.slave(1).sdo_write_isolated(0x6040, 0x00, false, vec![0x06, 0x00], None).await?;
scan_async
pub async fn scan_async(adapter: &str, secondary: Option<&str>, token: Option<CancelToken>) -> Result<Vec<ScannedSlaveInfo>>
异步静态扫描。走进程级独立扫描闸,取消时走独立的扫描取消通道(与主站取消严格区分,互不影响)。
dispose_async
pub async fn dispose_async(&mut self) -> Result<()>
异步释放。先置 shutting_down 守门标志,再释放 native —— 此后入队的异步操作会被守门拒绝,不再打 native。
四、取消语义
CancelToken 触发后,隔离层对正在执行的总线/邮箱阻塞调用发起中断,并在中断后自动清理中断状态:
| 操作类型 | 取消行为 |
|---|---|
| 主站 build / set_state / SDO | 中断正在执行的总线/邮箱调用,随后自动清理中断状态 |
| 静态扫描 scan_async | 走独立的取消通道中断扫描,与主站取消互不影响 |
中断后隔离层会自动清理中断状态,确保后续 build / set_state 不会被残留状态误判为"被中断"而失败。应用层无需任何手动复位操作。
let token = CancelToken::new();
let t2 = token.clone();
let handle = tokio::spawn(async move {
builder.build_async(None, Some(t2)).await
});
// 用户点取消
token.cancel();
match handle.await? {
Err(e) => println!("已取消或失败: {e}"),
Ok(_) => {}
}
文件清单
异步隔离层位于 Rust/src/master/async_isolation.rs(串行闸 + 守门 + 取消)与 async_api.rs(公开 *_async API),门控于 --features async-tokio,在 master/mod.rs 与 lib.rs 接线。原同步 build / set_state / read_slave_info / sdo_read / sdo_write 及已有 set_state_async / sdo_read_async 全部保留,向后兼容。