跳到主要内容

主动异步隔离 (Async Isolation)

本页是"主动异步隔离层"的参考。 它把 Build / SetState / SDO 读写 / 静态扫描这些会阻塞总线/邮箱的同步操作,包装成"入队即返回、后台串行执行、可取消、带进度"的异步版本,避免在 GUI / Service 线程上卡死,并保证同一主站的异步隔离操作任意时刻只有一个打总线(同步 API 不经此闸,混用时的并发安全由调用方保证)。

本页的隔离层提供每主站串行闸 (一次只放行一个操作)、关闭守门 (防资源已释放后继续访问)、取消时干净中断并自动恢复可用状态、独立静态扫描闸四项工业级保证。旧同步 API 全部保留。

命名约定

异步方法统一 Async 后缀,返回 Task / Task<T>,取消通过 CancellationToken 传入。

一、设计要点​

要点实现
串行 (Serial)每主站一把串行闸(一次只放行一个操作)。同一主站的 Build / SetState / SDO 严格 FIFO 串行,绝不并发打总线/邮箱。SDO 经父主站同一把闸排队。
后台隔离 (Isolation)*Async 入队即返回,阻塞调用在线程池后台执行,不堵调用线程 / async runtime。
守门 (Shutdown Guard)Dispose / Close 先置关闭标志,再释放底层资源。入队前 + 执行前各查一次,关闭中绝不再发起底层调用,防资源已释放后继续访问。
取消 (Cancel)取消令牌触发后中断正在执行的阻塞调用,并自动恢复到可用状态(无需应用层手动复位)。扫描走独立的取消通道,与主站操作互不干扰。
进度 (Progress)逐阶段上报进度回调。回调在后台线程触发,调用方需自己 marshal 回 UI。
进度回调在后台线程 — 必须自己 marshal

自己实现 IProgress<string> 时,进度回调在后台工作线程触发,不是 UI 线程。WinForms 用 Control.BeginInvoke、WPF / Avalonia / MAUI 用 Dispatcher.InvokeAsync,否则跨线程访问 UI 控件会抛异常。

// Progress<string> 按创建它的线程回调,在主线程 new 出来就不必自己 marshal
var progress = new Progress<string>(msg => statusLabel.Text = msg);
await master.BuildAsync(progress, ct);

二、API 总览​

类别属性类型访问说明
主站一条龙BuildAsyncTask<BuildResult>异步异步执行 连接→扫描→配置 一条龙(OP 由 SetState 负责),带进度与取消
SetStateAsyncTask<bool>异步异步状态切换,经串行闸排队,带进度与取消令牌
SetStateWithErrorAsyncTask<(bool success, string error)>异步异步状态切换,返回 (success, error) 元组(out 参数无法跨 await)
从站 SDOSDOReadAsyncTask<byte[]>异步异步读 SDO,转调父主站同一把闸,失败返回 null
SDOWriteAsyncTask<bool>异步异步写 SDO,转调父主站同一把闸
静态扫描ScanAsyncTask<List<ScannedSlaveInfo>>异步异步静态扫描,走独立扫描闸 + 独立取消通道
生命周期CloseAsyncTask异步异步软停车 + 释放,先置关闭标志再释放底层资源
DisposeAsyncTask异步异步释放,转调 Dispose
状态查询IsAsyncOperationInProgressbool只读当前是否有异步隔离操作在串行闸中执行
OnInitProgressevent Action<string>订阅初始化进度多播事件(后台线程触发)

三、方法详解​

BuildAsync(IProgress<string> progress, CancellationToken cancellationToken)​

public Task<BuildResult> BuildAsync(IProgress<string> progress = null, CancellationToken cancellationToken = default)

异步执行"连接 → 扫描 → 配置"一条龙构建。入队即返回 Task,构建在后台线程串行执行。与同步 Build() 一致:只完成绑定/扫描/配置,进 OP 由 SetState / SetStateAsync 负责。

参数:

  • progress (IProgress<string>) — 进度回调,在后台线程触发,调用方自行 marshal 回 UI(可选)
  • cancellationToken (CancellationToken) — 取消令牌;取消时干净中断当前构建并自动恢复到可用状态(可选)

返回值:

  • Task<BuildResult> — 结果用 Success 看成败、Message 看原因;成功时 Master 是主站实例

示例:

var cts = new CancellationTokenSource();
var result = await master.BuildAsync(progress, cts.Token);
if (!result.Success) Console.WriteLine(result.Message);

SetStateAsync(EcState state, IProgress<string> progress, CancellationToken cancellationToken)​

public Task<bool> SetStateAsync(EcState state, IProgress<string> progress = null, CancellationToken cancellationToken = default)

异步状态切换,经主站串行闸排队,与同主站的 Build / SDO 互斥串行。重试与 SafeOp 自动 SDO 行为同 同步 SetState。

参数:

  • state (EcState) — 目标状态
  • progress (IProgress<string>) — 进度回调,后台线程触发(可选)
  • cancellationToken (CancellationToken) — 取消令牌(可选)

返回值:

  • Task<bool> — true 切换成功,false 失败

示例:

bool ok = await master.SetStateAsync(EcState.OP, progress, cancellationToken: ct);
少传一个参数就是另一条路

master.SetStateAsync(EcState.OP, ct) 走线程池扩展,那条路径不进串行闸。 要进串行闸就带上 progress,或把取消令牌写成具名参数 cancellationToken: ct。

SetStateWithErrorAsync(EcState state, IProgress<string> progress, CancellationToken cancellationToken)​

public Task<(bool success, string error)> SetStateWithErrorAsync(
EcState state, IProgress<string> progress = null, CancellationToken cancellationToken = default)

SetStateAsync 的变体,返回 (success, error) 元组 —— 异步路径上取失败原因用(out 参数无法跨 await)。排队与重试行为同 SetStateAsync。

示例:

var (ok, err) = await master.SetStateWithErrorAsync(EcState.OP, cancellationToken: ct);
if (!ok) Console.WriteLine(err);

SDOReadAsync / SDOWriteAsync (Slave)​

public Task<byte[]> SDOReadAsync(ushort index, byte subindex, bool completeAccess = false, CancellationToken cancellationToken = default)
public Task<bool> SDOWriteAsync(ushort index, byte subindex, byte[] data, bool completeAccess = false, CancellationToken cancellationToken = default)

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

参数:

  • index (ushort) — 对象字典索引
  • subindex (byte) — 子索引
  • data (byte[]) — 写入数据(仅 write)
  • completeAccess (bool) — 完整访问(可选)
  • cancellationToken (CancellationToken) — 取消令牌(可选)

返回值:

  • SDOReadAsync → Task<byte[]>,失败返回 null
  • SDOWriteAsync → Task<bool>

示例:

byte[] data = await master.Slaves[0].SDOReadAsync(0x6041, 0x00, cancellationToken: ct);
bool wrote = await master.Slaves[0].SDOWriteAsync(0x6040, 0x00, new byte[]{0x06,0x00}, cancellationToken: ct);

ScanAsync(string adapterName, string secondaryAdapter, CancellationToken cancellationToken)​

public static Task<List<ScannedSlaveInfo>> ScanAsync(string adapterName, string secondaryAdapter = null, CancellationToken cancellationToken = default)

异步静态扫描(不构建主站)。走进程级独立扫描闸,取消时走独立的扫描取消通道(与主站操作的取消严格区分,互不干扰)。

返回值:

  • Task<List<ScannedSlaveInfo>> — 扫描到的从站列表

示例:

var slaves = await DarraEtherCAT.ScanAsync("以太网", cancellationToken: ct);
foreach (var s in slaves) Console.WriteLine(s.Name);

CloseAsync / DisposeAsync(CancellationToken ct)​

// 扩展方法(MasterAsyncCloseExtensions),按扩展写法调在 DarraEtherCAT 上
public static Task CloseAsync(this DarraEtherCAT master, CancellationToken ct = default)
public static Task DisposeAsync(this DarraEtherCAT master, CancellationToken ct = default)

异步软停车 + 释放,转调同步 Close / Dispose。先置关闭守门标志,再释放底层资源 —— 此后入队的异步操作会被守门拒绝并以"已取消"完成,不再发起底层调用。

示例:

await master.CloseAsync(ct);

四、取消语义​

取消令牌触发后,隔离层会中断正在执行的阻塞调用,并在中断完成后自动把内部状态恢复到可用:

操作类型取消行为
主站 Build / SetState / SDO中断当前主站操作,完成后自动恢复,下一个操作可正常入队
静态扫描 ScanAsync走独立的扫描取消通道,与主站操作的取消互不影响
取消后自动恢复

中断后隔离层会自动把内部状态复位,确保后续的 Build / SetState 不会被上一次的取消影响。这一切由隔离层内置处理,应用层无需任何手动操作。

var cts = new CancellationTokenSource();
var task = master.BuildAsync(cancellationToken: cts.Token);

// 用户点取消按钮
cancelButton.Click += (_, _) => cts.Cancel();

try { await task; }
catch (OperationCanceledException) { Console.WriteLine("已取消"); }

五、串行性保证示意​

master (单主站) ── 串行闸 (一次只放行一个) ──┐
├─► [后台 worker 串行]
BuildAsync ───┐ │ Build
SetStateAsync ├─► 入队 (FIFO) ───────────┤ SetState
SDOReadAsync ─┘ (同一把闸) │ SDO ...
│ 任意时刻只有 1 个打总线
ScanAsync (静态) ── 独立扫描闸 (独立取消通道) ─┘

兼容性​

异步隔离层作为独立扩展提供,不修改任何已有同步 SDK 代码。原同步 Build / SetState / ScanSlaves / SDORead / SDOWrite 全部保留,向后兼容。