主动异步隔离 (Async Isolation)
本页是"主动异步隔离层"的参考。 它把
Build/SetState/ SDO 读写 /ScanSlaves这些会阻塞总线/邮箱的同步操作,包装成"返回std::future、后台线程串行执行、可取消、带进度"的异步版本,避免在 Qt / MFC / 服务线程上卡死,并保证同一主站任意时刻只有一个操作打总线。语义对齐 C# —— 实现与 C# 主动异步隔离 逐点一致:每主站串行闸(
std::mutex+ RAIIunique_lock,等价SemaphoreSlim(1,1))、关闭守门、取消后内部自动复位中断标志、独立静态扫描闸。命名遵循现代 C++ 惯例(std::future+std::function回调,命名空间darra::ethercat)。旧同步 API 全部保留。
异步隔离层全部在 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() 抛 OperationCanceled。unique_lock RAII 天然规避二次释放。 |
| 进度 (Progress) | ProgressCallback(std::function<void(std::string)>),best-effort(回调抛异常吞掉),在后台线程触发,调用方需自己 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<...>> | 异步 | 异步静态扫描,走独立扫描闸 + 独立扫描中断标志 |
| 从站 SDO | Slave::SDOReadAsync(index, sub, complete, token) | std::future<std::vector<uint8_t>> | 异步 | 异步读 SDO,经父主站同一把闸 |
| 从站 SDO | Slave::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(sharedstd::atomic<bool>+ RAIIRegister)、进度类型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 全部保留,向后兼容。