From e7702405d8741c268654d2182b63a5df6438e262 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E2=80=9Chsc=E2=80=9D?= <“huangsucan@kaiyili-lab.com”>
Date: Thu, 10 Sep 2026 14:32:22 +0800
Subject: [PATCH] =?UTF-8?q?P2:=20Flexible=20=E5=9B=9B=E7=B1=BB=E9=80=9A?=
=?UTF-8?q?=E4=BF=A1=E9=94=81=E4=BB=8E=E5=85=A8=E5=B1=80=E9=9D=99=E6=80=81?=
=?UTF-8?q?=E6=94=B9=E4=B8=BA=E6=8C=89=E7=AB=AF=E5=8F=A3/IP=E7=B2=92?=
=?UTF-8?q?=E5=BA=A6=EF=BC=88ConcurrentDictionary=EF=BC=89=EF=BC=8C?=
=?UTF-8?q?=E4=B8=8D=E5=90=8C=E7=89=A9=E7=90=86=E7=AB=AF=E5=8F=A3=E4=BA=92?=
=?UTF-8?q?=E4=B8=8D=E9=98=BB=E5=A1=9E?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
DeviceCommand/Flexible/FModbusRTU.cs | 29 ++++++++++++++++-----------
DeviceCommand/Flexible/FModbusTCP.cs | 29 ++++++++++++++++-----------
DeviceCommand/Flexible/FSerialPort.cs | 21 +++++++++++--------
DeviceCommand/Flexible/FTCP.cs | 25 ++++++++++++++---------
4 files changed, 62 insertions(+), 42 deletions(-)
diff --git a/DeviceCommand/Flexible/FModbusRTU.cs b/DeviceCommand/Flexible/FModbusRTU.cs
index c478d6d..5e87aed 100644
--- a/DeviceCommand/Flexible/FModbusRTU.cs
+++ b/DeviceCommand/Flexible/FModbusRTU.cs
@@ -2,6 +2,7 @@
using NModbus;
using NModbus.Serial;
using System;
+using System.Collections.Concurrent;
using System.IO.Ports;
using System.Threading;
using System.Threading.Tasks;
@@ -10,14 +11,18 @@ namespace DeviceCommand.Flexible
{
///
/// 灵活型 Modbus RTU 串口通信类(免实例化,每次调用临时创建串口连接)。
- /// 支持保持寄存器与线圈的读写操作,内部使用通信锁保证同一时刻只有一个事务在执行,
- /// 适用于偶发性、无需保持长连接的 Modbus RTU 设备读写场景。
+ /// 支持保持寄存器与线圈的读写操作,内部使用按串口名称粒度的通信锁保证同一串口同一时刻只有一个事务在执行,
+ /// 不同串口之间互不阻塞,适用于偶发性、无需保持长连接的 Modbus RTU 设备读写场景。
///
[ACPCommand]
public static class FModbusRTU
{
- // 通信锁:保证同一时刻只有一个 Modbus 事务在执行
- private static readonly SemaphoreSlim _commLock = new(1, 1);
+ // 按串口名称粒度的通信锁:同一串口同一时刻只有一个事务在执行,不同串口互不阻塞
+ private static readonly ConcurrentDictionary _commLocks = new();
+
+ /// 获取指定串口的通信锁(不存在则自动创建)
+ private static SemaphoreSlim GetLock(string portName)
+ => _commLocks.GetOrAdd(portName, _ => new SemaphoreSlim(1, 1));
///
/// 创建串口实例并配置超时参数。
@@ -76,7 +81,7 @@ namespace DeviceCommand.Flexible
int writeTimeout = 3000,
CancellationToken ct = default)
{
- await _commLock.WaitAsync(ct);
+ await GetLock(portName).WaitAsync(ct);
try
{
using var port = CreatePort(
@@ -96,7 +101,7 @@ namespace DeviceCommand.Flexible
}
finally
{
- _commLock.Release();
+ GetLock(portName).Release();
}
}
@@ -127,7 +132,7 @@ namespace DeviceCommand.Flexible
int writeTimeout = 3000,
CancellationToken ct = default)
{
- await _commLock.WaitAsync(ct);
+ await GetLock(portName).WaitAsync(ct);
try
{
using var port = CreatePort(
@@ -147,7 +152,7 @@ namespace DeviceCommand.Flexible
}
finally
{
- _commLock.Release();
+ GetLock(portName).Release();
}
}
@@ -183,7 +188,7 @@ namespace DeviceCommand.Flexible
int writeTimeout = 3000,
CancellationToken ct = default)
{
- await _commLock.WaitAsync(ct);
+ await GetLock(portName).WaitAsync(ct);
try
{
using var port = CreatePort(
@@ -203,7 +208,7 @@ namespace DeviceCommand.Flexible
}
finally
{
- _commLock.Release();
+ GetLock(portName).Release();
}
}
@@ -234,7 +239,7 @@ namespace DeviceCommand.Flexible
int writeTimeout = 3000,
CancellationToken ct = default)
{
- await _commLock.WaitAsync(ct);
+ await GetLock(portName).WaitAsync(ct);
try
{
using var port = CreatePort(
@@ -254,7 +259,7 @@ namespace DeviceCommand.Flexible
}
finally
{
- _commLock.Release();
+ GetLock(portName).Release();
}
}
diff --git a/DeviceCommand/Flexible/FModbusTCP.cs b/DeviceCommand/Flexible/FModbusTCP.cs
index 113a550..47ea999 100644
--- a/DeviceCommand/Flexible/FModbusTCP.cs
+++ b/DeviceCommand/Flexible/FModbusTCP.cs
@@ -1,6 +1,7 @@
using Common.Attributes;
using NModbus;
using System;
+using System.Collections.Concurrent;
using System.Net.Sockets;
using System.Text;
using System.Threading;
@@ -10,14 +11,18 @@ namespace DeviceCommand.Flexible
{
///
/// 灵活型 Modbus TCP 通信类(免实例化,每次调用临时建立 TCP 连接)。
- /// 支持保持寄存器与线圈的读写操作,内部使用通信锁保证同一时刻只有一个事务在执行,
- /// 适用于偶发性、无需保持长连接的 Modbus TCP 设备读写场景。
+ /// 支持保持寄存器与线圈的读写操作,内部使用按端点粒度的通信锁保证同一端点同一时刻只有一个事务在执行,
+ /// 不同端点之间互不阻塞,适用于偶发性、无需保持长连接的 Modbus TCP 设备读写场景。
///
[ACPCommand]
public static class FModbusTCP
{
- // 通信锁:保证同一时刻只有一个 Modbus 事务在执行
- private static readonly SemaphoreSlim _commLock = new(1, 1);
+ // 按端点粒度的通信锁:同一端点同一时刻只有一个事务在执行,不同端点互不阻塞
+ private static readonly ConcurrentDictionary _commLocks = new();
+
+ /// 获取指定端点的通信锁(不存在则自动创建)
+ private static SemaphoreSlim GetLock(string ipAddress, int port)
+ => _commLocks.GetOrAdd($"{ipAddress}:{port}", _ => new SemaphoreSlim(1, 1));
///
/// 建立 TCP 连接并创建 Modbus TCP 主站。
@@ -57,7 +62,7 @@ namespace DeviceCommand.Flexible
int receiveTimeout = 3000,
CancellationToken ct = default)
{
- await _commLock.WaitAsync(ct);
+ await GetLock(ipAddress, port).WaitAsync(ct);
try
{
using var master = await ConnectAsync(ipAddress, port, sendTimeout, receiveTimeout, ct) as IDisposable;
@@ -68,7 +73,7 @@ namespace DeviceCommand.Flexible
}
finally
{
- _commLock.Release();
+ GetLock(ipAddress, port).Release();
}
}
@@ -93,7 +98,7 @@ namespace DeviceCommand.Flexible
int receiveTimeout = 3000,
CancellationToken ct = default)
{
- await _commLock.WaitAsync(ct);
+ await GetLock(ipAddress, port).WaitAsync(ct);
try
{
using var master = await ConnectAsync(ipAddress, port, sendTimeout, receiveTimeout, ct) as IDisposable;
@@ -103,7 +108,7 @@ namespace DeviceCommand.Flexible
}
finally
{
- _commLock.Release();
+ GetLock(ipAddress, port).Release();
}
}
@@ -133,7 +138,7 @@ namespace DeviceCommand.Flexible
int receiveTimeout = 3000,
CancellationToken ct = default)
{
- await _commLock.WaitAsync(ct);
+ await GetLock(ipAddress, port).WaitAsync(ct);
try
{
using var master = await ConnectAsync(ipAddress, port, sendTimeout, receiveTimeout, ct) as IDisposable;
@@ -144,7 +149,7 @@ namespace DeviceCommand.Flexible
}
finally
{
- _commLock.Release();
+ GetLock(ipAddress, port).Release();
}
}
@@ -169,7 +174,7 @@ namespace DeviceCommand.Flexible
int receiveTimeout = 3000,
CancellationToken ct = default)
{
- await _commLock.WaitAsync(ct);
+ await GetLock(ipAddress, port).WaitAsync(ct);
try
{
using var master = await ConnectAsync(ipAddress, port, sendTimeout, receiveTimeout, ct) as IDisposable;
@@ -179,7 +184,7 @@ namespace DeviceCommand.Flexible
}
finally
{
- _commLock.Release();
+ GetLock(ipAddress, port).Release();
}
}
diff --git a/DeviceCommand/Flexible/FSerialPort.cs b/DeviceCommand/Flexible/FSerialPort.cs
index 5d958b5..05f873b 100644
--- a/DeviceCommand/Flexible/FSerialPort.cs
+++ b/DeviceCommand/Flexible/FSerialPort.cs
@@ -1,5 +1,6 @@
using Common.Attributes;
using System;
+using System.Collections.Concurrent;
using System.IO.Ports;
using System.Text;
using System.Threading;
@@ -10,14 +11,18 @@ namespace DeviceCommand.Flexible
///
/// 灵活型串口通信类(免实例化,每次调用临时创建串口连接)。
/// 支持只发送指令、发送并读取应答两种最常用操作,
- /// 内部使用通信锁保证同一时刻只有一个串口事务在执行,
- /// 适用于偶发性、无需保持长连接的串口设备通信场景(如示波器、电源的 SCPI 指令)。
+ /// 内部使用按串口名称粒度的通信锁保证同一串口同一时刻只有一个事务在执行,
+ /// 不同串口之间互不阻塞,适用于偶发性、无需保持长连接的串口设备通信场景(如示波器、电源的 SCPI 指令)。
///
[ACPCommand]
public static class FSerialPort
{
- // 通信锁:保证同一时刻只有一个串口事务在执行
- private static readonly SemaphoreSlim _commLock = new(1, 1);
+ // 按串口名称粒度的通信锁:同一串口同一时刻只有一个事务在执行,不同串口互不阻塞
+ private static readonly ConcurrentDictionary _commLocks = new();
+
+ /// 获取指定串口的通信锁(不存在则自动创建)
+ private static SemaphoreSlim GetLock(string portName)
+ => _commLocks.GetOrAdd(portName, _ => new SemaphoreSlim(1, 1));
///
/// 创建串口实例并配置 UTF8 编码与超时参数。
@@ -48,7 +53,7 @@ namespace DeviceCommand.Flexible
/// 异步取消令牌
public static async Task 发送指令(string portName,int baudRate,Parity parity,int dataBits,StopBits stopBits,int sendTimeout,int receiveTimeout,string command,CancellationToken ct = default)
{
- await _commLock.WaitAsync(ct);
+ await GetLock(portName).WaitAsync(ct);
try
{
using var port = CreatePort(
@@ -66,7 +71,7 @@ namespace DeviceCommand.Flexible
}
finally
{
- _commLock.Release();
+ GetLock(portName).Release();
}
}
@@ -90,7 +95,7 @@ namespace DeviceCommand.Flexible
/// 去除结束符并去除首尾空白后的应答字符串
public static async Task 发送并读取应答(string portName,int baudRate, Parity parity, int dataBits, StopBits stopBits,int sendTimeout, int receiveTimeout,string command,string delimiter = "\n", CancellationToken ct = default)
{
- await _commLock.WaitAsync(ct);
+ await GetLock(portName).WaitAsync(ct);
try
{
using var port = CreatePort(
@@ -131,7 +136,7 @@ namespace DeviceCommand.Flexible
}
finally
{
- _commLock.Release();
+ GetLock(portName).Release();
}
}
diff --git a/DeviceCommand/Flexible/FTCP.cs b/DeviceCommand/Flexible/FTCP.cs
index 491a905..b485f58 100644
--- a/DeviceCommand/Flexible/FTCP.cs
+++ b/DeviceCommand/Flexible/FTCP.cs
@@ -1,4 +1,5 @@
using Common.Attributes;
+using System.Collections.Concurrent;
using System.Net.Sockets;
using System.Text;
@@ -7,14 +8,18 @@ namespace DeviceCommand.Flexible
///
/// 灵活型 TCP 通信类(免实例化,每次调用临时建立 TCP 连接)。
/// 支持字节/文本发送、定长字节读取、按结束符读取文本行四种操作,
- /// 内部使用通信锁保证同一时刻只有一个 TCP 事务在执行,
- /// 适用于偶发性、无需保持长连接的 TCP 设备通信场景。
+ /// 内部使用按端点粒度的通信锁保证同一端点同一时刻只有一个 TCP 事务在执行,
+ /// 不同端点之间互不阻塞,适用于偶发性、无需保持长连接的 TCP 设备通信场景。
///
[ACPCommand]
public static class FTCP
{
- // 通信锁:保证同一时刻只有一个 TCP 事务在执行
- private static readonly SemaphoreSlim _commLock = new(1, 1);
+ // 按端点粒度的通信锁:同一端点同一时刻只有一个事务在执行,不同端点互不阻塞
+ private static readonly ConcurrentDictionary _commLocks = new();
+
+ /// 获取指定端点的通信锁(不存在则自动创建)
+ private static SemaphoreSlim GetLock(string ipAddress, int port)
+ => _commLocks.GetOrAdd($"{ipAddress}:{port}", _ => new SemaphoreSlim(1, 1));
#region Send
@@ -28,7 +33,7 @@ namespace DeviceCommand.Flexible
/// 异步取消令牌
public static async Task 发送字节数据(string ipAddress,int port,int sendTimeout, byte[] buffer, CancellationToken ct = default)
{
- await _commLock.WaitAsync(ct);
+ await GetLock(ipAddress, port).WaitAsync(ct);
try
{
using var client = new TcpClient();
@@ -41,7 +46,7 @@ namespace DeviceCommand.Flexible
}
finally
{
- _commLock.Release();
+ GetLock(ipAddress, port).Release();
}
}
@@ -73,7 +78,7 @@ namespace DeviceCommand.Flexible
/// 实际读取到的字节数组(对端提前关闭时可能短于请求长度)
public static async Task 读取字节数据(string ipAddress,int port,int receiveTimeout,int length,CancellationToken ct = default)
{
- await _commLock.WaitAsync(ct);
+ await GetLock(ipAddress, port).WaitAsync(ct);
try
{
using var client = new TcpClient();
@@ -100,7 +105,7 @@ namespace DeviceCommand.Flexible
}
finally
{
- _commLock.Release();
+ GetLock(ipAddress, port).Release();
}
}
@@ -115,7 +120,7 @@ namespace DeviceCommand.Flexible
/// 去除结束符并去除首尾空白后的文本行
public static async Task 读取文本行( string ipAddress, int port, int receiveTimeout, string delimiter = "\n",CancellationToken ct = default)
{
- await _commLock.WaitAsync(ct);
+ await GetLock(ipAddress, port).WaitAsync(ct);
try
{
using var client = new TcpClient();
@@ -147,7 +152,7 @@ namespace DeviceCommand.Flexible
}
finally
{
- _commLock.Release();
+ GetLock(ipAddress, port).Release();
}
}