开发者中心 开发者中心
视频教程
敢为云网站
简体中文
  • 简体中文
  • 繁體中文
  • English
  • Español
  • Français
  • العربية
  • Deutsch
  • 日本語
  • 한국어
  • 6.1.0 版本
  • 10.2.0 版本
视频教程
敢为云网站
简体中文
  • 简体中文
  • 繁體中文
  • English
  • Español
  • Français
  • العربية
  • Deutsch
  • 日本語
  • 한국어
  • 平台概述
  • 平台功能
  • 平台安装
  • 开发者指南
    • 快速开始
    • 协议插件开发
      • 原理解析
      • 开发第一个插件
      • 进阶
      • 异步协议插件开发
        • 概述
          • 什么是异步DLL
          • 为什么使用异步
          • 适用场景
        • 基类架构
          • 继承关系
        • 开发指南
          • 第1步:创建项目和类
          • 第2步:重写初始化方法 InitAsync
          • 第3步:重写数据采集方法 GetDataAsync
          • 第4步:重写设置参数方法 SetParmAsync
        • 性能优化
          • 1. 选择合适的异步模式
          • 2. 使用 SleepAsync 替代 Sleep
          • 3. 合理使用 Task.Yield
        • 完整示例:TCP 网络设备
        • 常见问题
          • Q1:我的DLL需要改为异步吗?
          • Q2:如何判断DLL是异步还是同步?
          • Q3:可以在同步方法中调用异步方法吗?
          • Q4:Sleep 和 SleepAsync 有什么区别?
          • Q5:老版本的DLL能继续使用吗?
        • 最佳实践总结
          • 推荐做法
          • 避免的错误
      • Java协议插件开发实践
      • Python协议插件开发实践
      • GWDataCenter常用接口API
      • 多语言插件开发
    • 扩展插件开发
    • 报警插件开发
    • 应用插件开发
    • Web可视化开发
    • 3D可视化开发
    • 桌面可视化开发
    • 小程序开发
    • 应用模块接口
  • 项目实战
  • 附录

异步设备DLL开发指南

# 异步设备DLL开发指南

为提升 I/O 密集型设备的并发性能,协议插件支持以异步方式开发:继承 CEquipBaseAsync 基类并实现 IEquipAsync 接口后,设备驱动在等待数据期间释放线程,把线程让给其他任务。本文介绍异步设备 DLL 的开发步骤、性能优化建议与常见问题。

前置知识

C# async/await、Task 异步编程基础。适用对象:设备驱动 DLL 开发者。

# 概述

# 什么是异步DLL

异步 DLL 是继承自 CEquipBaseAsync 基类的设备驱动程序,通过实现 IEquipAsync 接口,支持真正的异步 I/O 操作,能够在数据采集时释放线程资源,提高系统的并发性能。

# 为什么使用异步

传统同步 DLL 异步 DLL
阻塞线程等待 I/O 释放线程,不阻塞
串口/网络等待时线程空闲 线程可处理其他任务
系统并发能力受限 高并发场景性能更好
适合快速内存操作 适合 I/O 密集型操作

# 适用场景

✅ 推荐使用异步的场景:

  • 网络通讯(TCP、HTTP、Modbus TCP 等)
  • 串口通讯(存在延迟的场景)
  • 文件读写操作
  • 数据库查询
  • 任何涉及等待外部响应的场景

❌ 不推荐异步的场景:

  • 纯内存计算(模拟设备)
  • 极快速的操作(微秒级)
  • 本地变量读写

# 基类架构

# 继承关系

IEquip (接口)
    ↓
CEquipBase (同步基类)
    ↓
CEquipBaseAsync (异步基类) : CEquipBase, IEquipAsync
    ↓
您的设备类 (CEquip)

# 开发指南

# 第1步:创建项目和类

using GWDataCenter;
using GWDataCenter.Database;
using System.Threading.Tasks;

namespace YourCompany.DeviceDriver
{
    /// <summary>
    /// 设备通讯类(异步版本)
    /// 类名必须是 "CEquip"
    /// 必须继承 CEquipBaseAsync
    /// </summary>
    public class CEquip : CEquipBaseAsync
    {
        public CEquip() : base()
        {
        }
    }
}

类名必须是 CEquip,且必须继承 CEquipBaseAsync。

# 第2步:重写初始化方法 InitAsync

✅ 正确做法(推荐):

public override async Task<bool> InitAsync(EquipItem item)
{
    // 1. 先调用基类的异步初始化(不会死锁)
    if (!await base.InitAsync(item))
        return false;

    // 2. 添加自己的初始化逻辑
    // 解析配置参数
    if (!string.IsNullOrEmpty(item.communication_param))
    {
        // 解析设备参数
        ParseDeviceParams(item.communication_param);
    }

    // 3. 初始化连接(如果需要异步操作)
    if (_needConnection)
    {
        await InitializeConnectionAsync();
    }

    return true;
}

⚠️ 错误做法(会导致问题):

// ❌ 错误1:调用同步方法 base.init()
public override async Task<bool> InitAsync(EquipItem item)
{
    if (!base.init(item))  // ❌ 不推荐,虽然能工作但不一致
        return false;
    // ...
}

// ❌ 错误2:在同步方法中同步等待异步方法
public override bool init(EquipItem item)
{
    // ❌ 会死锁!
    return InitAsync(item).GetAwaiter().GetResult();
}

# 第3步:重写数据采集方法 GetDataAsync

有两种重写方式。

# 方式 A:重写 GetDataAsync,完全控制采集流程

public override async Task<CommunicationState> GetDataAsync(CEquipBase pEquip)
{
    // 使用异步休眠替代同步休眠
    await SleepAsync(500);  // ✅ 不阻塞线程

    // 检查设置标志
    if (RunSetParmFlag)
        return CommunicationState.setreturn;

    // 建立连接(如果需要)
    if (!await ConnectAsync())
        return CommunicationState.fail;

    // 采集YC数据
    if (ycprows != null)
    {
        foreach (var r in ycprows)
        {
            if (RunSetParmFlag)
                return CommunicationState.setreturn;

            if (!await GetYCAsync(r))
                return CommunicationState.fail;
        }
    }

    // 采集YX数据
    if (yxprows != null)
    {
        foreach (var r in yxprows)
        {
            if (RunSetParmFlag)
                return CommunicationState.setreturn;

            if (!await GetYXAsync(r))
                return CommunicationState.fail;
        }
    }

    return CommunicationState.ok;
}

# 方式 B:重写 GetYCAsync / GetYXAsync,使用基类采集流程

public override async Task<CommunicationState> GetDataAsync(CEquipBase pEquip)
{
    // 1. 使用异步休眠
    await SleepAsync(500);

    // 2. 调用基类的采集流程(会自动调用 GetYCAsync 和 GetYXAsync)
    return await base.GetDataAsync(pEquip);
}

// 重写YC数据采集
protected override async Task<bool> GetYCAsync(YcpTableRow r)
{
    // 异步读取设备数据
    var value = await ReadFromDeviceAsync(r.main_instruction, r.minor_instruction);

    // 设置数据
    SetYCData(r, value);

    return true;
}

// 重写YX数据采集
protected override async Task<bool> GetYXAsync(YxpTableRow r)
{
    // 异步读取设备状态
    var state = await ReadStateFromDeviceAsync(r.main_instruction);

    // 设置数据
    SetYXData(r, state);

    return true;
}

# 第4步:重写设置参数方法 SetParmAsync

public override async Task<bool> SetParmAsync(string cmd1, string cmd2, string value)
{
    try
    {
        // 异步发送设置命令到设备
        await SendCommandAsync(cmd1, cmd2, value);

        // 等待设备响应
        var response = await WaitForResponseAsync(timeout: 5000);

        if (response.IsSuccess)
        {
            // 生成成功事件
            var evt = new EquipEvent($"设置成功:{cmd1}", MessageLevel.Info, DateTime.Now);
            EquipEventList.Add(evt);
            return true;
        }
        else
        {
            return false;
        }
    }
    catch (Exception ex)
    {
        DataCenter.Write2Log($"设置失败:{ex.Message}", LogLevel.Error);
        return false;
    }
}

# 性能优化

# 1. 选择合适的异步模式

// ✅ 真实I/O设备 - 使用真正的异步
protected override async Task<bool> GetYCAsync(YcpTableRow r)
{
    // 网络读取(真正的异步I/O)
    var value = await _tcpClient.ReadAsync(buffer, 0, length);
    SetYCData(r, value);
    return true;
}

// ⚠️ 模拟设备或快速操作 - 考虑同步
protected override async Task<bool> GetYCAsync(YcpTableRow r)
{
    // 只是内存操作,异步开销可能更大
    await Task.Yield();  // 让出线程即可

    double value = CalculateValue(r);  // 纯内存计算
    SetYCData(r, value);
    return true;
}

# 2. 使用 SleepAsync 替代 Sleep

// ❌ 阻塞线程
public override async Task<CommunicationState> GetDataAsync(CEquipBase pEquip)
{
    Sleep(1000);  // 阻塞当前线程1秒
    // ...
}

// ✅ 释放线程
public override async Task<CommunicationState> GetDataAsync(CEquipBase pEquip)
{
    await SleepAsync(1000);  // 线程被释放,可以处理其他任务
    // ...
}

# 3. 合理使用 Task.Yield

// 场景1:纯内存操作,但想支持异步接口
protected override async Task<bool> GetYCAsync(YcpTableRow r)
{
    await Task.Yield();  // 让出线程给其他任务

    // 快速的内存操作
    double value = _random.Next(100);
    SetYCData(r, value);
    return true;
}

// 场景2:长时间CPU计算,定期让出线程
private async Task<double> ComplexCalculationAsync(YcpTableRow r)
{
    double result = 0;
    for (int i = 0; i < 1000000; i++)
    {
        result += Math.Sin(i);

        // 每1000次让出线程
        if (i % 1000 == 0)
            await Task.Yield();
    }
    return result;
}

# 完整示例:TCP 网络设备

using System;
using System.Net.Sockets;
using System.Threading.Tasks;
using GWDataCenter;
using GWDataCenter.Database;

public class CEquip : CEquipBaseAsync
{
    private TcpClient _tcpClient;
    private NetworkStream _stream;
    private string _ipAddress;
    private int _port;

    public CEquip() : base()
    {
    }

    public override async Task<bool> InitAsync(EquipItem item)
    {
        // 调用基类初始化
        if (!await base.InitAsync(item))
            return false;

        // 解析IP和端口
        var parts = item.communication_param.Split(':');
        _ipAddress = parts[0];
        _port = int.Parse(parts[1]);

        // 异步连接
        try
        {
            _tcpClient = new TcpClient();
            await _tcpClient.ConnectAsync(_ipAddress, _port);
            _stream = _tcpClient.GetStream();

            DataCenter.Write2Log($"设备 {item.iEquipno} 连接成功: {_ipAddress}:{_port}");
            return true;
        }
        catch (Exception ex)
        {
            DataCenter.Write2Log($"设备 {item.iEquipno} 连接失败: {ex.Message}", LogLevel.Error);
            return false;
        }
    }

    public override async Task<CommunicationState> GetDataAsync(CEquipBase pEquip)
    {
        try
        {
            // 检查设置标志
            if (RunSetParmFlag)
                return CommunicationState.setreturn;

            // 调用基类流程(会自动调用 GetYCAsync 和 GetYXAsync)
            return await base.GetDataAsync(pEquip);
        }
        catch (Exception ex)
        {
            DataCenter.Write2Log($"数据采集异常: {ex.Message}", LogLevel.Error);
            return CommunicationState.fail;
        }
    }

    protected override async Task<bool> GetYCAsync(YcpTableRow r)
    {
        try
        {
            // 构造Modbus读取命令
            byte[] command = BuildModbusCommand(r.main_instruction, r.minor_instruction);

            // 异步发送命令
            await _stream.WriteAsync(command, 0, command.Length);

            // 异步读取响应
            byte[] buffer = new byte[256];
            int bytesRead = await _stream.ReadAsync(buffer, 0, buffer.Length);

            // 解析数据
            double value = ParseModbusResponse(buffer, bytesRead);
            SetYCData(r, value);

            return true;
        }
        catch (Exception ex)
        {
            DataCenter.Write2Log($"读取YC数据失败: {ex.Message}", LogLevel.Error);
            return false;
        }
    }

    protected override async Task<bool> GetYXAsync(YxpTableRow r)
    {
        // 类似 GetYCAsync 的实现
        await Task.Delay(10);  // 模拟读取
        SetYXData(r, true);
        return true;
    }

    public override async Task<bool> SetParmAsync(string cmd1, string cmd2, string value)
    {
        try
        {
            // 构造设置命令
            byte[] command = BuildSetCommand(cmd1, cmd2, value);

            // 异步发送
            await _stream.WriteAsync(command, 0, command.Length);

            // 等待响应
            byte[] buffer = new byte[128];
            int bytesRead = await _stream.ReadAsync(buffer, 0, buffer.Length);

            return bytesRead > 0;
        }
        catch (Exception ex)
        {
            DataCenter.Write2Log($"设置参数失败: {ex.Message}", LogLevel.Error);
            return false;
        }
    }

    // 辅助方法
    private byte[] BuildModbusCommand(string cmd1, string cmd2)
    {
        // 实现Modbus命令构造
        return new byte[] { 0x01, 0x03, 0x00, 0x00, 0x00, 0x01 };
    }

    private double ParseModbusResponse(byte[] buffer, int length)
    {
        // 实现Modbus响应解析
        return BitConverter.ToInt16(buffer, 3);
    }

    private byte[] BuildSetCommand(string cmd1, string cmd2, string value)
    {
        // 实现设置命令构造
        return new byte[] { 0x01, 0x06 };
    }

    // 同步兼容方法
    public override bool init(EquipItem item)
    {
        return InitAsync(item).GetAwaiter().GetResult();
    }

    public override CommunicationState GetData(CEquipBase pEquip)
    {
        return GetDataAsync(pEquip).GetAwaiter().GetResult();
    }

    public override bool SetParm(string cmd1, string cmd2, string value)
    {
        return SetParmAsync(cmd1, cmd2, value).GetAwaiter().GetResult();
    }
}

# 常见问题

# Q1:我的DLL需要改为异步吗?

根据场景决定:

场景 是否需要异步 原因
网络通讯(TCP/HTTP) ✅ 强烈推荐 网络延迟高,异步可提升并发
串口通讯 ✅ 推荐 串口等待期间释放线程
模拟设备(纯内存) ❌ 不推荐 无 I/O 等待,异步开销更大
快速 Modbus 读取 ⚖️ 可选 根据实际性能测试决定
数据库查询 ✅ 推荐 数据库操作天然适合异步

# Q2:如何判断DLL是异步还是同步?

系统启动时会在日志中显示:

设备 1 使用异步模式 (DLL实现了IEquipAsync接口)
设备 2 使用同步模式 (传统DLL)

也可以通过性能报告查看:

  • 异步设备:模式列显示"异步"
  • 同步设备:模式列显示"同步"

# Q3:可以在同步方法中调用异步方法吗?

可以,但要小心死锁:

// ✅ 方法1:使用 Task.Run 隔离(推荐)
public override bool init(EquipItem item)
{
    return Task.Run(async () => await InitAsync(item)).GetAwaiter().GetResult();
}

// ❌ 方法2:直接等待(可能死锁)
public override bool init(EquipItem item)
{
    return InitAsync(item).GetAwaiter().GetResult();  // 危险!
}

// ✅ 方法3:调用基类的同步方法或 InitializeCore(最安全)
public override bool init(EquipItem item)
{
    return InitializeCore(item);  // 推荐
}

# Q4:Sleep 和 SleepAsync 有什么区别?

方法 行为 适用场景
Sleep(1000) 阻塞当前线程1秒 同步方法、模拟设备
await SleepAsync(1000) 释放线程,1秒后继续 异步方法、真实 I/O

性能对比(100 个设备):

  • 使用 Sleep:需要 100 个线程,每个阻塞 1 秒
  • 使用 SleepAsync:只需要少量线程,轮流处理

# Q5:老版本的DLL能继续使用吗?

可以,完全兼容:

  • 老 DLL 继承 CEquipBase,系统识别为同步模式
  • 新 DLL 继承 CEquipBaseAsync,系统识别为异步模式
  • 两种 DLL 可以在同一个系统中混用
  • 系统会自动调用对应的同步/异步方法

# 最佳实践总结

# 推荐做法

  • 继承 CEquipBaseAsync 并重写异步方法
  • 调用 await base.InitAsync() 而不是 base.init()
  • 使用 await SleepAsync() 替代 Sleep()
  • 在异步方法中使用 await Task.Delay() 或真实异步 I/O
  • 提供同步兼容方法(调用异步版本)
  • 添加详细的日志和异常处理
  • 根据设备类型选择合适的模式(真实 I/O 用异步,模拟用同步)

# 避免的错误

  • 在同步方法中同步等待异步方法(可能死锁)
  • 在 lock 内使用 await(编译错误)
  • GetYCAsync 和 GetYC 都有延迟(双重延迟)
  • 纯内存操作强行使用异步(性能下降)
  • 不提供同步兼容方法(兼容性问题)
上次更新: 2026/10/10 17:08:35

← 进阶 Java协议插件开发实践→

目录
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式