386 lines
12 KiB
C#
386 lines
12 KiB
C#
using System;
|
|
using System.Buffers;
|
|
using System.Diagnostics;
|
|
using System.IO.Ports;
|
|
using System.Text;
|
|
using System.Text.RegularExpressions;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
|
|
namespace RIZO_Helper.Tools
|
|
{
|
|
public sealed class ComScanHelper : IDisposable
|
|
{
|
|
private readonly SerialPort _serialPort;
|
|
private readonly StringBuilder _receiveBuffer = new StringBuilder();
|
|
private readonly Regex _lineSplitter = new Regex(@"\r\n|\n|\r", RegexOptions.Compiled);
|
|
private readonly object _lockObject = new object();
|
|
private readonly CancellationTokenSource _disposeCts = new CancellationTokenSource();
|
|
private bool _isDisposed;
|
|
private int _isProcessingData;
|
|
private int _disposeFlag;
|
|
private readonly int DisposeTimeout = 500; // 处置超时时间(毫秒)
|
|
|
|
public event EventHandler<string>? DataReceived;
|
|
public event EventHandler<Exception>? ErrorOccurred;
|
|
public bool IsOpen => _serialPort.IsOpen;
|
|
public string PortName => _serialPort.PortName;
|
|
public int BytesToRead => _serialPort.BytesToRead;
|
|
|
|
public ComScanHelper(string portName = "COM1", int baudRate = 9600, Parity parity = Parity.None,
|
|
int dataBits = 8, StopBits stopBits = StopBits.One, int readBufferSize = 4096)
|
|
{
|
|
_serialPort = new SerialPort
|
|
{
|
|
PortName = portName,
|
|
BaudRate = baudRate,
|
|
Parity = parity,
|
|
DataBits = dataBits,
|
|
StopBits = stopBits,
|
|
ReadTimeout = 500,
|
|
WriteTimeout = 500,
|
|
Encoding = Encoding.UTF8,
|
|
ReadBufferSize = readBufferSize,
|
|
DiscardNull = false,
|
|
NewLine = "\n"
|
|
};
|
|
}
|
|
|
|
public async Task<bool> OpenAsync()
|
|
{
|
|
if (_isDisposed)
|
|
throw new ObjectDisposedException(nameof(ComScanHelper));
|
|
|
|
lock (_lockObject)
|
|
{
|
|
if (_serialPort.IsOpen)
|
|
return true;
|
|
}
|
|
|
|
try
|
|
{
|
|
await Task.Run(() => _serialPort.Open(), _disposeCts.Token);
|
|
_serialPort.DataReceived += OnSerialDataReceived;
|
|
return true;
|
|
}
|
|
catch (OperationCanceledException) when (_disposeCts.IsCancellationRequested)
|
|
{
|
|
return false;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
ErrorOccurred?.Invoke(this, ex);
|
|
Debug.WriteLine($"打开串口 {_serialPort.PortName} 失败: {ex.Message}");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
public async Task CloseAsync()
|
|
{
|
|
if (_isDisposed)
|
|
return;
|
|
|
|
await Task.Run(() => Close(), _disposeCts.Token);
|
|
}
|
|
|
|
public void Close()
|
|
{
|
|
if (_isDisposed)
|
|
return;
|
|
|
|
lock (_lockObject)
|
|
{
|
|
if (!_serialPort.IsOpen)
|
|
return;
|
|
|
|
try
|
|
{
|
|
_serialPort.DataReceived -= OnSerialDataReceived;
|
|
_serialPort.DiscardInBuffer();
|
|
_serialPort.DiscardOutBuffer();
|
|
_serialPort.Close();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
ErrorOccurred?.Invoke(this, ex);
|
|
Debug.WriteLine($"关闭串口 {_serialPort.PortName} 失败: {ex.Message}");
|
|
}
|
|
}
|
|
}
|
|
|
|
public async Task<bool> SendDataAsync(string data, CancellationToken cancellationToken = default)
|
|
{
|
|
if (_isDisposed)
|
|
throw new ObjectDisposedException(nameof(ComScanHelper));
|
|
|
|
lock (_lockObject)
|
|
{
|
|
if (!_serialPort.IsOpen)
|
|
throw new InvalidOperationException("串口未打开");
|
|
}
|
|
|
|
try
|
|
{
|
|
byte[] buffer = Encoding.UTF8.GetBytes(data);
|
|
await _serialPort.BaseStream.WriteAsync(buffer, 0, buffer.Length, cancellationToken);
|
|
await _serialPort.BaseStream.FlushAsync(cancellationToken);
|
|
return true;
|
|
}
|
|
catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested)
|
|
{
|
|
return false;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
ErrorOccurred?.Invoke(this, ex);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
public async Task<string?> ReadLineAsync(CancellationToken cancellationToken = default)
|
|
{
|
|
if (_isDisposed)
|
|
throw new ObjectDisposedException(nameof(ComScanHelper));
|
|
|
|
lock (_lockObject)
|
|
{
|
|
if (!_serialPort.IsOpen)
|
|
throw new InvalidOperationException("串口未打开");
|
|
}
|
|
|
|
try
|
|
{
|
|
return await Task.Run(() =>
|
|
{
|
|
cancellationToken.ThrowIfCancellationRequested();
|
|
return _serialPort.ReadLine();
|
|
}, cancellationToken);
|
|
}
|
|
catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested)
|
|
{
|
|
return null;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
ErrorOccurred?.Invoke(this, ex);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
private void OnSerialDataReceived(object sender, SerialDataReceivedEventArgs e)
|
|
{
|
|
if (Interlocked.Exchange(ref _isProcessingData, 1) != 0)
|
|
return;
|
|
|
|
try
|
|
{
|
|
if (_isDisposed || !_serialPort.IsOpen)
|
|
return;
|
|
|
|
ProcessSerialData();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
ErrorOccurred?.Invoke(this, ex);
|
|
}
|
|
finally
|
|
{
|
|
Interlocked.Exchange(ref _isProcessingData, 0);
|
|
}
|
|
}
|
|
|
|
private void ProcessSerialData()
|
|
{
|
|
int bytesToRead = _serialPort.BytesToRead;
|
|
if (bytesToRead <= 0)
|
|
return;
|
|
|
|
byte[] buffer = ArrayPool<byte>.Shared.Rent(bytesToRead);
|
|
try
|
|
{
|
|
int bytesRead = _serialPort.Read(buffer, 0, bytesToRead);
|
|
if (bytesRead <= 0)
|
|
return;
|
|
|
|
string newData = _serialPort.Encoding.GetString(buffer, 0, bytesRead);
|
|
ProcessReceivedData(newData);
|
|
}
|
|
finally
|
|
{
|
|
ArrayPool<byte>.Shared.Return(buffer);
|
|
}
|
|
}
|
|
|
|
private void ProcessReceivedData(string newData)
|
|
{
|
|
if (string.IsNullOrEmpty(newData))
|
|
return;
|
|
|
|
lock (_receiveBuffer)
|
|
{
|
|
_receiveBuffer.Append(newData);
|
|
string bufferContent = _receiveBuffer.ToString();
|
|
string[] lines = _lineSplitter.Split(bufferContent);
|
|
|
|
for (int i = 0; i < lines.Length - 1; i++)
|
|
{
|
|
string line = lines[i].Trim();
|
|
if (!string.IsNullOrEmpty(line))
|
|
PostDataReceived(line);
|
|
}
|
|
|
|
_receiveBuffer.Clear();
|
|
_receiveBuffer.Append(lines[lines.Length - 1]);
|
|
}
|
|
}
|
|
|
|
private void PostDataReceived(string data)
|
|
{
|
|
try
|
|
{
|
|
EventHandler<string>? handler = DataReceived;
|
|
if (handler != null)
|
|
{
|
|
Task.Run(() =>
|
|
{
|
|
try
|
|
{
|
|
handler(this, data);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
ErrorOccurred?.Invoke(this, ex);
|
|
Debug.WriteLine($"事件处理失败: {ex.Message}");
|
|
}
|
|
}, _disposeCts.Token);
|
|
}
|
|
}
|
|
catch (OperationCanceledException) when (_disposeCts.IsCancellationRequested)
|
|
{
|
|
// 忽略取消操作
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
ErrorOccurred?.Invoke(this, ex);
|
|
}
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
if (Interlocked.Exchange(ref _disposeFlag, 1) != 0)
|
|
return;
|
|
|
|
try
|
|
{
|
|
DisposeAsync().GetAwaiter().GetResult();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Debug.WriteLine($"同步Dispose期间发生异常: {ex}");
|
|
ErrorOccurred?.Invoke(this, ex);
|
|
}
|
|
}
|
|
|
|
private void Dispose(bool disposing)
|
|
{
|
|
if (Interlocked.Exchange(ref _disposeFlag, 1) != 0)
|
|
return;
|
|
|
|
_isDisposed = true;
|
|
_disposeCts.Cancel();
|
|
|
|
try
|
|
{
|
|
if (disposing)
|
|
{
|
|
// 同步执行异步释放方法
|
|
using var cts = new CancellationTokenSource(DisposeTimeout);
|
|
var disposeTask = DisposeAsync();
|
|
|
|
try
|
|
{
|
|
Task.WaitAny(disposeTask, Task.Delay(Timeout.Infinite, cts.Token));
|
|
}
|
|
catch (AggregateException ex)
|
|
{
|
|
foreach (var innerEx in ex.InnerExceptions)
|
|
{
|
|
if (!(innerEx is TaskCanceledException))
|
|
throw innerEx;
|
|
}
|
|
}
|
|
|
|
if (!disposeTask.IsCompleted)
|
|
{
|
|
Debug.WriteLine("异步释放操作超时,资源可能未完全释放");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// 非托管资源清理逻辑
|
|
try
|
|
{
|
|
if (_serialPort.IsOpen)
|
|
{
|
|
_serialPort.DataReceived -= OnSerialDataReceived;
|
|
_serialPort.Close();
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Debug.WriteLine($"析构函数关闭串口时发生异常: {ex.Message}");
|
|
}
|
|
|
|
_serialPort.Dispose();
|
|
_disposeCts.Dispose();
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Debug.WriteLine($"释放资源时发生异常: {ex.Message}");
|
|
}
|
|
}
|
|
|
|
public async Task DisposeAsync()
|
|
{
|
|
if (Interlocked.Exchange(ref _disposeFlag, 1) != 0)
|
|
return;
|
|
|
|
_isDisposed = true;
|
|
_disposeCts.Cancel();
|
|
|
|
try
|
|
{
|
|
using var timeoutCts = new CancellationTokenSource(DisposeTimeout);
|
|
var closeTask = CloseAsync();
|
|
|
|
await Task.WhenAny(closeTask, Task.Delay(Timeout.Infinite, timeoutCts.Token));
|
|
|
|
if (!closeTask.IsCompleted)
|
|
{
|
|
timeoutCts.Cancel();
|
|
Debug.WriteLine("关闭串口操作超时,正在强制终止");
|
|
}
|
|
}
|
|
catch (OperationCanceledException)
|
|
{
|
|
Debug.WriteLine("关闭串口操作被取消");
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
ErrorOccurred?.Invoke(this, ex);
|
|
Debug.WriteLine($"关闭串口时发生异常: {ex.Message}");
|
|
}
|
|
finally
|
|
{
|
|
_disposeCts.Dispose();
|
|
_serialPort.Dispose();
|
|
}
|
|
}
|
|
|
|
~ComScanHelper()
|
|
{
|
|
Dispose(false);
|
|
}
|
|
}
|
|
} |