1. 线程安全的单例类方案
csharp
using System;
using System.IO.Ports;
using System.Text;
using System.Threading;
public sealed class SerialInstrumentController : IDisposable
{
// 使用Lazy<T>实现线程安全的单例模式
private static readonly Lazy<SerialInstrumentController> _instance =
new Lazy<SerialInstrumentController>(() => new SerialInstrumentController());
private readonly object _serialLock = new object();
private SerialPort _serialPort;
private bool _disposed = false;
// 私有构造函数
private SerialInstrumentController()
{
// 默认初始化
InitializeSerialPort("COM1", 9600);
}
public static SerialInstrumentController Instance => _instance.Value;
/// <summary>
/// 初始化串口
/// </summary>
public void InitializeSerialPort(string portName, int baudRate)
{
lock (_serialLock)
{
ClosePort();
_serialPort = new SerialPort
{
PortName = portName,
BaudRate = baudRate,
DataBits = 8,
Parity = Parity.None,
StopBits = StopBits.One,
Handshake = Handshake.None,
ReadTimeout = 1000,
WriteTimeout = 1000
};
try
{
_serialPort.Open();
Console.WriteLine($"串口 {portName} 已打开");
}
catch (Exception ex)
{
Console.WriteLine($"打开串口失败: {ex.Message}");
_serialPort = null;
throw;
}
}
}
/// <summary>
/// 线程安全的发送命令方法
/// </summary>
public bool SendCommand(string command)
{
lock (_serialLock)
{
if (_serialPort == null || !_serialPort.IsOpen)
{
Console.WriteLine("串口未打开");
return false;
}
try
{
_serialPort.Write(command);
Console.WriteLine($"发送命令: {command}");
return true;
}
catch (Exception ex)
{
Console.WriteLine($"发送命令失败: {ex.Message}");
return false;
}
}
}
/// <summary>
/// 线程安全的读取响应方法
/// </summary>
public string ReadResponse(int timeoutMs = 1000)
{
lock (_serialLock)
{
if (_serialPort == null || !_serialPort.IsOpen)
{
return "ERROR: 串口未打开";
}
try
{
// 设置读取超时
_serialPort.ReadTimeout = timeoutMs;
StringBuilder response = new StringBuilder();
DateTime startTime = DateTime.Now;
while ((DateTime.Now - startTime).TotalMilliseconds < timeoutMs)
{
if (_serialPort.BytesToRead > 0)
{
string data = _serialPort.ReadExisting();
response.Append(data);
// 根据实际情况判断是否读取完成
// 例如,如果仪器响应以特定字符结尾
if (data.Contains("\n") || data.Contains("\r"))
{
break;
}
}
Thread.Sleep(10); // 短暂休眠避免CPU占用过高
}
return response.ToString().Trim();
}
catch (TimeoutException)
{
return "TIMEOUT: 读取超时";
}
catch (Exception ex)
{
return $"ERROR: {ex.Message}";
}
}
}
/// <summary>
/// 原子操作:发送命令并接收响应
/// </summary>
public string SendAndReceive(string command, int timeoutMs = 1000)
{
lock (_serialLock)
{
if (!SendCommand(command))
{
return "ERROR: 发送命令失败";
}
return ReadResponse(timeoutMs);
}
}
/// <summary>
/// 关闭串口
/// </summary>
private void ClosePort()
{
if (_serialPort != null)
{
if (_serialPort.IsOpen)
{
_serialPort.Close();
}
_serialPort.Dispose();
_serialPort = null;
}
}
/// <summary>
/// 实现IDisposable接口
/// </summary>
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (!_disposed)
{
if (disposing)
{
ClosePort();
}
_disposed = true;
}
}
~SerialInstrumentController()
{
Dispose(false);
}
}
2. 使用示例和测试代码
csharp
using System;
using System.Threading.Tasks;
class Program
{
static void Main(string[] args)
{
Console.WriteLine("工控仪器控制演示");
// 使用单例实例
var controller = SerialInstrumentController.Instance;
try
{
// 初始化串口
controller.InitializeSerialPort("COM3", 9600);
// 单线程测试
Console.WriteLine("单线程测试:");
string response = controller.SendAndReceive("*IDN?");
Console.WriteLine($"响应: {response}");
// 多线程并发测试
Console.WriteLine("\n多线程并发测试:");
RunMultiThreadTest(controller);
// 异步任务测试
Console.WriteLine("\n异步任务测试:");
RunAsyncTest(controller).Wait();
}
catch (Exception ex)
{
Console.WriteLine($"错误: {ex.Message}");
}
finally
{
// 清理资源
controller.Dispose();
}
Console.WriteLine("\n按任意键退出...");
Console.ReadKey();
}
static void RunMultiThreadTest(SerialInstrumentController controller)
{
int threadCount = 5;
var tasks = new Task[threadCount];
for (int i = 0; i < threadCount; i++)
{
int threadId = i;
tasks[i] = Task.Run(() =>
{
string cmd = $"READ{threadId}";
Console.WriteLine($"线程 {threadId}: 发送命令 {cmd}");
try
{
string response = controller.SendAndReceive(cmd);
Console.WriteLine($"线程 {threadId}: 收到响应 {response}");
}
catch (Exception ex)
{
Console.WriteLine($"线程 {threadId}: 错误 {ex.Message}");
}
});
}
Task.WaitAll(tasks);
}
static async Task RunAsyncTest(SerialInstrumentController controller)
{
var tasks = new Task<string>[3];
for (int i = 0; i < 3; i++)
{
tasks[i] = Task.Run(() =>
{
return controller.SendAndReceive("MEASURE?");
});
}
var responses = await Task.WhenAll(tasks);
for (int i = 0; i < responses.Length; i++)
{
Console.WriteLine($"任务 {i} 响应: {responses[i]}");
}
}
}
3. 更高级的异步队列模式实现
csharp
using System;
using System.Collections.Concurrent;
using System.IO.Ports;
using System.Threading;
using System.Threading.Tasks;
public class InstrumentCommandQueue : IDisposable
{
private class CommandRequest
{
public string Command { get; set; }
public TaskCompletionSource<string> CompletionSource { get; set; }
public int TimeoutMs { get; set; }
}
private readonly BlockingCollection<CommandRequest> _commandQueue =
new BlockingCollection<CommandRequest>();
private readonly SerialPort _serialPort;
private readonly Thread _workerThread;
private readonly CancellationTokenSource _cancellationTokenSource;
private bool _disposed = false;
public InstrumentCommandQueue(string portName, int baudRate)
{
_serialPort = new SerialPort
{
PortName = portName,
BaudRate = baudRate,
DataBits = 8,
Parity = Parity.None,
StopBits = StopBits.One,
ReadTimeout = 1000,
WriteTimeout = 1000
};
_cancellationTokenSource = new CancellationTokenSource();
try
{
_serialPort.Open();
Console.WriteLine($"串口 {portName} 已打开");
}
catch (Exception ex)
{
Console.WriteLine($"打开串口失败: {ex.Message}");
throw;
}
// 启动工作线程
_workerThread = new Thread(ProcessCommands);
_workerThread.IsBackground = true;
_workerThread.Start();
}
/// <summary>
/// 提交命令到队列
/// </summary>
public Task<string> SubmitCommandAsync(string command, int timeoutMs = 1000)
{
var tcs = new TaskCompletionSource<string>();
var request = new CommandRequest
{
Command = command,
CompletionSource = tcs,
TimeoutMs = timeoutMs
};
_commandQueue.Add(request);
return tcs.Task;
}
private void ProcessCommands()
{
try
{
foreach (var request in _commandQueue.GetConsumingEnumerable(_cancellationTokenSource.Token))
{
try
{
// 串口操作(单线程执行,无需锁)
_serialPort.Write(request.Command);
Console.WriteLine($"发送: {request.Command}");
// 读取响应
string response = ReadWithTimeout(request.TimeoutMs);
// 完成Task
request.CompletionSource.SetResult(response);
}
catch (Exception ex)
{
request.CompletionSource.SetException(ex);
}
}
}
catch (OperationCanceledException)
{
// 正常取消
}
}
private string ReadWithTimeout(int timeoutMs)
{
StringBuilder response = new StringBuilder();
DateTime startTime = DateTime.Now;
while ((DateTime.Now - startTime).TotalMilliseconds < timeoutMs)
{
if (_serialPort.BytesToRead > 0)
{
string data = _serialPort.ReadExisting();
response.Append(data);
if (data.Contains("\n") || response.Length > 1024) // 防止缓冲区溢出
{
break;
}
}
Thread.Sleep(10);
}
return response.ToString().Trim();
}
public void Dispose()
{
if (!_disposed)
{
_disposed = true;
// 取消工作线程
_cancellationTokenSource.Cancel();
// 等待工作线程结束
if (_workerThread != null && _workerThread.IsAlive)
{
_workerThread.Join(2000);
}
// 清理队列
_commandQueue.Dispose();
// 关闭串口
if (_serialPort != null)
{
if (_serialPort.IsOpen)
{
_serialPort.Close();
}
_serialPort.Dispose();
}
_cancellationTokenSource.Dispose();
Console.WriteLine("InstrumentCommandQueue 已释放");
}
}
}
4. 工厂模式:支持多种仪器类型
csharp
using System;
public interface IInstrumentController
{
string SendCommand(string command);
Task<string> SendCommandAsync(string command);
}
public class InstrumentControllerFactory
{
private static readonly Lazy<InstrumentControllerFactory> _instance =
new Lazy<InstrumentControllerFactory>(() => new InstrumentControllerFactory());
private readonly ConcurrentDictionary<string, IInstrumentController> _controllers =
new ConcurrentDictionary<string, IInstrumentController>();
private readonly object _factoryLock = new object();
public static InstrumentControllerFactory Instance => _instance.Value;
private InstrumentControllerFactory() { }
/// <summary>
/// 获取或创建仪器控制器
/// </summary>
public IInstrumentController GetController(string instrumentType, string portName, int baudRate)
{
string key = $"{instrumentType}_{portName}_{baudRate}";
return _controllers.GetOrAdd(key, k =>
{
switch (instrumentType.ToUpper())
{
case "AGILENT_34401A":
return new Agilent34401AController(portName, baudRate);
case "KEITHLEY_2400":
return new Keithley2400Controller(portName, baudRate);
case "FLUKE_5500A":
return new Fluke5500AController(portName, baudRate);
default:
return new GenericInstrumentController(portName, baudRate);
}
});
}
/// <summary>
/// 关闭所有控制器
/// </summary>
public void CloseAll()
{
foreach (var controller in _controllers.Values)
{
if (controller is IDisposable disposable)
{
disposable.Dispose();
}
}
_controllers.Clear();
}
}
// 具体的仪器控制器实现
public class Agilent34401AController : IInstrumentController, IDisposable
{
private readonly SerialInstrumentController _serialController;
private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(1, 1);
public Agilent34401AController(string portName, int baudRate)
{
_serialController = SerialInstrumentController.Instance;
_serialController.InitializeSerialPort(portName, baudRate);
}
public string SendCommand(string command)
{
// 特定的仪器命令处理逻辑
if (command.StartsWith("MEAS:"))
{
// 添加仪器特定的格式化
command = command.Replace("MEAS:", "MEASURE:") + ";*OPC?";
}
return _serialController.SendAndReceive(command);
}
public async Task<string> SendCommandAsync(string command)
{
await _semaphore.WaitAsync();
try
{
return await Task.Run(() => SendCommand(command));
}
finally
{
_semaphore.Release();
}
}
public void Dispose()
{
_semaphore.Dispose();
}
}
5. 使用建议和最佳实践
csharp
public static class InstrumentControlBestPractices
{
// 1. 使用配置类管理串口参数
public class SerialPortConfig
{
public string PortName { get; set; } = "COM1";
public int BaudRate { get; set; } = 9600;
public int DataBits { get; set; } = 8;
public Parity Parity { get; set; } = Parity.None;
public StopBits StopBits { get; set; } = StopBits.One;
public int ReadTimeout { get; set; } = 1000;
public int WriteTimeout { get; set; } = 1000;
}
// 2. 使用依赖注入
public static IServiceCollection AddInstrumentServices(this IServiceCollection services, SerialPortConfig config)
{
services.AddSingleton(config);
services.AddSingleton<IInstrumentController>(provider =>
{
var cfg = provider.GetRequiredService<SerialPortConfig>();
return new GenericInstrumentController(cfg.PortName, cfg.BaudRate);
});
return services;
}
// 3. 异常处理包装器
public static T ExecuteWithRetry<T>(Func<T> action, int maxRetries = 3, int delayMs = 100)
{
int retryCount = 0;
while (retryCount < maxRetries)
{
try
{
return action();
}
catch (Exception ex) when (retryCount < maxRetries - 1)
{
retryCount++;
Console.WriteLine($"操作失败,第 {retryCount} 次重试: {ex.Message}");
if (ex is UnauthorizedAccessException || ex is IOException)
{
// 串口访问错误,可能需要重新初始化
Thread.Sleep(delayMs * retryCount);
}
else
{
throw;
}
}
}
throw new InvalidOperationException($"操作在 {maxRetries} 次重试后仍失败");
}
}
总结
在C#中实现工控串口仪器控制的线程安全,最简单的是使用线程安全的单例模式,但需要根据具体场景选择:
推荐线程安全方案:
- 简单应用:使用
Lazy<T>实现的单例模式 - 高并发场景:使用消息队列模式
- 多仪器类型:使用工厂模式配合单例
关键要点:
- 使用
lock语句保护共享资源访问 - 实现
IDisposable接口确保资源正确释放 - 添加适当的超时机制防止线程阻塞
- **使用
Task和async/await**支持异步操作