跳到主要内容

主动异步隔离 (Async Isolation)

本页是"主动异步隔离层"的参考。 它把 Build / SetState / SDO 读写 / ScanSlaves 这些会阻塞总线/邮箱的同步操作,包装成"返回 std::future、后台线程串行执行、可取消、带进度"的异步版本,避免在 Qt / MFC / 服务线程上卡死,并保证同一主站任意时刻只有一个操作打总线

语义对齐 C# —— 实现与 C# 主动异步隔离 逐点一致:每主站串行闸(std::mutex + RAII unique_lock,等价 SemaphoreSlim(1,1))、关闭守门、取消后内部自动复位中断标志、独立静态扫描闸。命名遵循现代 C++ 惯例(std::future + std::function 回调,命名空间 darra::ethercat)。旧同步 API 全部保留。

Header-Only 与 FFI

异步隔离层全部在 SDK 头文件内实现(由伞形头 ethercat.hpp 统一纳入),底层经 C SDK 的函数指针调用本机运行时库。EtherCATMaster::RunExclusiveAsync(name, body, token) 为核心串行闸(模板按 Func 推导,返回 std::future<decltype(body())>)。

一、设计要点

要点实现
串行 (Serial)每主站持有独立串行闸(随 master 移动稳定)。RunExclusiveAsync 内用 std::packaged_task + 后台 std::thread().detach(),体首行加锁强制串行(= SemaphoreSlim(1,1)),调用方拿 std::future 不堵。静态 ScanAsync 用一把进程级独立的扫描闸。
后台隔离 (Isolation)*Async 立即返回 std::future,阻塞调用在后台 std::thread 执行,不堵调用线程。
守门 (Shutdown Guard)进底层前两次查 shutting_down(等闸前 + 等闸后),置位即抛 OperationCanceled,防 use-after-free。Dispose()shutting_down
取消 (Cancel)token.Register(...) 触发后,隔离层对正在执行的阻塞调用发起中断并内部自动复位中断标志。中断入口未就绪时静默跳过。体内 ThrowIfCancellationRequested()OperationCanceledunique_lock RAII 天然规避二次释放。
进度 (Progress)ProgressCallbackstd::function<void(std::string)>),best-effort(回调抛异常吞掉),在后台线程触发,调用方需自己 marshal。
进度回调在后台线程 — 必须自己 marshal

所有 ProgressCallback 都在后台线程触发,不是 UI 线程。Qt 用 QMetaObject::invokeMethod(obj, ..., Qt::QueuedConnection) 或 signal/slot 跨线程、MFC 用 PostMessage,否则跨线程访问 UI 控件会崩溃。

master.BuildAsync([this](std::string msg){
// ✅ Qt: marshal 回 GUI 线程
QMetaObject::invokeMethod(this, [this, msg]{ label_->setText(QString::fromStdString(msg)); });
});

二、API 总览

类别方法返回类型读写说明
主站一条龙BuildAsync(progress, token)std::future<bool>异步异步执行 连接→扫描→配置→进 OP,带进度与取消
主站一条龙BuildResultAsync(progress, token)std::future<BuildResult>异步同上但返回完整结果对象
状态切换SetStateAsync(state, token)std::future<bool>异步异步状态切换,经串行闸排队
状态切换SetStateWithErrorAsync(state, token)std::future<StateResult>异步异步状态切换,返回含错误原因
静态扫描ScanAsync(adapter, secondary, token)std::future<std::vector<...>>异步异步静态扫描,走独立扫描闸 + 独立扫描中断标志
从站 SDOSlave::SDOReadAsync(index, sub, complete, token)std::future<std::vector<uint8_t>>异步异步读 SDO,经父主站同一把闸
从站 SDOSlave::SDOWriteAsync(index, sub, data, complete, token)std::future<bool>异步异步写 SDO
串行闸RunExclusiveAsync(name, body, token)std::future<decltype(body())>异步核心串行闸,自定义异步操作入闸(按 Func 推导)
串行闸RunExclusiveActionAsync(name, action, token)std::future<void>异步无返回值版串行闸
生命周期RequestAsyncShutdown()void置 shutting_down,拒绝后续异步操作
状态查询IsAsyncOperationInProgress()bool只读当前是否有异步操作在串行闸中执行

取消令牌类型 CancelToken(shared std::atomic<bool> + RAII Register)、进度类型 ProgressCallback、取消异常 OperationCanceled 均在 darra::ethercat 命名空间。

三、方法详解

BuildAsync / BuildResultAsync

std::future<bool>        BuildAsync(ProgressCallback progress = {}, CancelToken token = {});
std::future<BuildResult> BuildResultAsync(ProgressCallback progress = {}, CancelToken token = {});

异步执行"连接 → 扫描 → 配置 → 进 OP"一条龙。立即返回 std::future,构建在后台线程串行执行。

参数:

  • progress (ProgressCallback) — 进度回调,在后台线程触发,调用方自行 marshal(可选)
  • token (CancelToken) — 取消令牌;取消时隔离层中断当前操作并内部自动复位中断标志(可选)

返回值:

  • std::future<bool>true 构建成功并进入 OP;取消时 future 持有 OperationCanceled 异常

示例:

darra::ethercat::CancelToken token;
auto fut = master.BuildAsync(
[](std::string msg){ /* 后台线程, marshal 回 UI */ },
token);
try { bool ok = fut.get(); }
catch (const darra::ethercat::OperationCanceled&) { /* 已取消 */ }

SetStateAsync / SetStateWithErrorAsync

std::future<bool>        SetStateAsync(EcState state, CancelToken token = {});
std::future<StateResult> SetStateWithErrorAsync(EcState state, CancelToken token = {});

异步状态切换,经主站串行闸排队,与同主站的 Build / SDO 互斥串行。SetStateWithErrorAsync 的结果含失败原因。

Slave::SDOReadAsync / SDOWriteAsync

std::future<std::vector<uint8_t>> SDOReadAsync(uint16_t index, uint8_t sub, bool complete = false, CancelToken token = {});
std::future<bool> SDOWriteAsync(uint16_t index, uint8_t sub, std::vector<uint8_t> data, bool complete = false, CancelToken token = {});

异步 SDO 读写。经父主站的同一把串行闸排队,绝不与同主站 PDO / 状态切换并发打邮箱。

示例:

auto data = master.GetSlave(1).SDOReadAsync(0x6041, 0x00).get();
bool ok = master.GetSlave(1).SDOWriteAsync(0x6040, 0x00, {0x06, 0x00}).get();

ScanAsync

std::future<std::vector<ScannedSlaveInfo>> ScanAsync(const std::string& adapter, const std::string& secondary = "", CancelToken token = {});

异步静态扫描。走进程级独立扫描闸,取消时使用独立的扫描中断标志(与主站中断严格区分),不影响任何已构建主站的运行。

RunExclusiveAsync

template <typename Func>
auto RunExclusiveAsync(const std::string& name, Func body, CancelToken token = {})
-> std::future<decltype(body())>;

核心串行闸:把任意 body 包成在主站串行闸内串行执行的异步任务。模板按 Func 推导(C++ 成员模板,调用时不写显式模板实参)。体内可调 ThrowIfCancellationRequested() 响应取消。

四、取消语义

CancelToken 触发后,隔离层对正在执行的阻塞调用发起中断,并在中断完成后内部自动复位中断标志:

操作类型中断作用域
主站 Build / SetState / SDO主站级中断;中断后内部自动复位
静态扫描 ScanAsync独立扫描级中断(与主站中断严格区分);中断后内部自动复位
复位由隔离层内置,应用层无需手动处理

中断后中断标志必须复位,否则残留标志会让后续 Build / SetState 误判为"被中断"而失败。隔离层已内置复位,应用层无需关心。

darra::ethercat::CancelToken token;
auto fut = master.BuildAsync({}, token);
// 用户点取消
token.Cancel();
try { fut.get(); }
catch (const darra::ethercat::OperationCanceled&) { /* 已取消 */ }

兼容性

异步隔离层全部在 SDK 头文件内实现,由伞形头 ethercat.hpp 统一纳入,仅头文件即可使用。原同步 Build / SetState / ScanSlaves / SDORead / SDOWrite 全部保留,向后兼容。