跳到主要内容

主动异步隔离 (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 全部保留。

feature 门控

异步隔离层门控于 --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)AtomicBooldispose_async 立即置位,拿闸后 + 后台进 native 前各查一次 shutting_down,防 use-after-free。
取消 (Cancel)轻量 CancelTokenArc<AtomicBool>,不引 tokio-util)。排队前 / 拿闸后 / 进 native 前多点 check();后台 watcher 命中即中断正在执行的总线/邮箱调用,并在中断后自动清理中断状态,确保后续操作不被残留状态影响。主站与静态扫描各用独立的取消通道,互不干扰。MutexGuard RAII 天然规避二次释放。
进度 (Progress)ProgressFn = Arc<dyn Fn(&str) + Send + Sync>在后台 blocking 线程触发,调用方需自己 marshal 回 UI / 用 channel 转发。
进度回调在后台线程 — 必须自己 marshal

所有进度回调都在后台 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
从站 SDOSlave::sdo_read_isolated(&self, index, sub, complete, token)Result<Vec<u8>>异步异步读 SDO,经父主站同一把闸
从站 SDOSlave::sdo_write_isolated(&self, index, sub, complete, data, token)Result<()>异步异步写 SDO
取消令牌CancelTokenstruct轻量取消令牌(Arc<AtomicBool>),crate 根 re-export
进度类型ProgressFntypeArc<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.rslib.rs 接线。原同步 build / set_state / read_slave_info / sdo_read / sdo_write 及已有 set_state_async / sdo_read_async 全部保留,向后兼容。