代码在.Net4上运行。
一个设备,一个操作:'Op_A',一个队列:'QuData',三个命令:'Cmd_Req','Cmd_Res'和'Cmd_Report'。
这是我使用的代码的简化版。
//The method DataEqual is to determine that the data in the two BYTE[] are the same.
bool isSend = false;
private void SerialPort_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
if(isSend)
return;
byte[] Cmd_Report = new byte[serialPort.BytesToRead];
serialPort.Read(Cmd_Report, 0, serialPort.BytesToRead);
if (DataEqual(Cmd_Report, CMD_REPORT))
{
//var data = QuData.Dequeue();
//do something X about data then save data
}
}
public void DoWork()
{
//do something
byte[] Cmd_Req = new byte[10];
byte[] Cmd_Res = new byte[CMD_RES.Length];
isSend = true;
serialPort.Write(Cmd_Req, 0, Cmd_Req.Length );
stopWatch.Restart();
while(serialPort.BytesToRead < CMD_RES.Length && stopWatch.ElapsedMilliseconds< 1000)
System.Threading.Thread.Sleep(50);
int resCount = 0;
if (serialPort.BytesToRead < CMD_RES.Length)
resCount = serialPort.Read(Cmd_Res, 0, serialPort.BytesToRead);
else
resCount = serialPort.Read(Cmd_Res, 0, CMD_RES.Length);
isSend = false;
if (DataEqual(Cmd_Res, CMD_RES))
{
//create data, do something A about data
//QuData.Enqueue(data)
}
else
{
//do something B
}
}
我的问题:
DoWork
和'Op_A'并不总是按顺序执行。有时我需要多次调用DoWork
并等待'Op_A',然后在用户执行'Op_A'时继续调用DoWork
。因此,我必须知道Cmd_Req
是否成功,并确定设备是否报告Cmd_Report
。但是,在经常阅读和写作时,SerialPort_DataReceived
方法有时会阅读Cmd_Res
,而DoWork
方法会阅读Cmd_Report
。这将使我认为Cmd_Req
未成功执行或设备未报告Cmd_Result
。我该如何解决?修改
'OP_A'是按下设备上的按钮的动作。
答案 0 :(得分:2)
受到JohnEphraimTugado代码的启发,我想到了解决方案,并在应用场景中通过了测试。
{
"tokens": [
{
"token": "tshirts",
"start_offset": 10,
"end_offset": 18,
"type": "<ALPHANUM>",
"position": 2
}
]
}
<强>用法强>
using System;
using System.Collections.Generic;
using System.IO.Ports;
using System.Linq;
using System.Text;
using System.Threading;
public class SerialPortHandler
{
public delegate void OnReportHandler(byte[] data);
public delegate void OnReadExceptionHander(Exception error);
public delegate void OnHandlingExceptionHandler(Exception error);
public SerialPortHandler(string portName, Predicate<byte[]> reportPredicate, Func<Queue<byte>, byte[]> dequeueFunc)
: this(reportPredicate, dequeueFunc)
{
this._serialPort = new SerialPort(portName);
}
public SerialPortHandler(string portName, int baudRate, Predicate<byte[]> reportPredicate, Func<Queue<byte>, byte[]> dequeueFunc)
: this(reportPredicate, dequeueFunc)
{
this._serialPort = new SerialPort(portName, baudRate);
}
public SerialPortHandler(string portName, int baudRate, Parity parity, int dataBits, StopBits stopBits, Predicate<byte[]> reportPredicate, Func<Queue<byte>, byte[]> dequeueFunc)
: this(reportPredicate, dequeueFunc)
{
this._serialPort = new SerialPort(portName, baudRate, parity, dataBits, stopBits);
}
private SerialPortHandler(Predicate<byte[]> reportPredicate, Func<Queue<byte>, byte[]> dequeueFunc)
{
_thrdRead = new Thread(new ThreadStart(Read));
_thrdHandle = new Thread(new ThreadStart(DataHandling));
_isRun = false;
_quCmdRespone = new Queue<byte[]>();
_quReceiveBuff = new Queue<byte>();
_cmdResponseReset = new AutoResetEvent(false);
_reportPredicate = reportPredicate;
_dequeueFunc = dequeueFunc;
}
SerialPort _serialPort;
Thread _thrdRead;
Thread _thrdHandle;
bool _isRun;
/// <summary>
/// Save all data read from the serial port
/// </summary>
Queue<byte> _quReceiveBuff;
/// <summary>
/// Save the response of the last command
/// </summary>
Queue<byte[]> _quCmdRespone;
AutoResetEvent _cmdResponseReset;
bool _isSending;
/// <summary>
/// A method to determine whether a byte[] is a spontaneous report of a serial port
/// </summary>
Predicate<byte[]> _reportPredicate;
/// <summary>
/// Dequeuing a command from the received data queue method
/// </summary>
Func<Queue<byte>, byte[]> _dequeueFunc;
/// <summary>
/// Called when the serial interface is actively reporting data.
/// </summary>
public event OnReportHandler OnReport;
public event OnReadExceptionHander OnReadException;
public event OnHandlingExceptionHandler OnHandlingException;
public bool IsOpen
{
get { return this._serialPort == null ? false : this._serialPort.IsOpen; }
}
/// <summary>
/// Read data from serial port.
/// </summary>
private void Read()
{
while (_isRun)
{
try
{
if (this._serialPort == null || !this._serialPort.IsOpen || this._serialPort.BytesToRead == 0)
{
SpinWait.SpinUntil(() => this._serialPort != null && this._serialPort.IsOpen && this._serialPort.BytesToRead > 0, 10);
continue;
}
byte[] data = new byte[this._serialPort.BytesToRead];
this._serialPort.Read(data, 0, data.Length);
Array.ForEach(data, b => _quReceiveBuff.Enqueue(b));
}
catch (InvalidOperationException)
{
if (!_isRun || this._serialPort ==null)
return;
else
this._serialPort.Open();
}
catch (Exception ex)
{
this.OnReadException?.BeginInvoke(new Exception(string.Format("An error occurred in the reading processing: {0}", ex.Message), ex), null, null);
}
}
}
/// <summary>
/// Data processing
/// </summary>
private void DataHandling()
{
while (_isRun)
{
try
{
if (_quReceiveBuff.Count == 0)
{
SpinWait.SpinUntil(() => _quReceiveBuff.Count > 0, 10);
continue;
}
byte[] data = _dequeueFunc(_quReceiveBuff);
if (data == null || data.Length == 0)
{
SpinWait.SpinUntil(() => false, 10);
continue;
}
if (_reportPredicate(data))
OnReport?.BeginInvoke(data, null, null); //If the data is spontaneously reported by the serial port, the OnReport event is called
else
{ //If the command response returned by the serial port, join the command response queue
if (_quCmdRespone.Count > 0)
_quCmdRespone.Clear(); //The queue is cleared to ensure that if a command timed out does not affect subsequent command results
_quCmdRespone.Enqueue(data);
_cmdResponseReset.Set();
}
}
catch (Exception ex)
{
this.OnHandlingException?.BeginInvoke(new Exception(string.Format("An error occurred in the data processing: {0}", ex.Message), ex), null, null);
}
}
}
/// <summary>
/// Read the response of the last command.
/// </summary>
/// <param name="timeOut"></param>
/// <returns></returns>
private byte[] ReadCommandResponse(int timeOut)
{
byte[] buffer = null;
if (_cmdResponseReset.WaitOne(timeOut, false))
buffer = _quCmdRespone.Dequeue();
return buffer;
}
/// <summary>
/// Send a command
/// </summary>
/// <param name="sendData">command buff</param>
/// <param name="receiveData">REF: response of command</param>
/// <param name="timeout">timeout(millseconds)</param>
/// <returns>count of response, -1: failure, -2: port is busy</returns>
public int SendCommand(byte[] sendData, ref byte[] receiveData, int timeout)
{
if (_isSending)
return -2;
if (this._serialPort.IsOpen)
{
try
{
_isSending = true;
_cmdResponseReset.Reset(); //update 11-13
this._serialPort.Write(sendData, 0, sendData.Length);
int ret = 0;
receiveData = ReadCommandResponse(timeout);
ret = receiveData == null ? -1 : receiveData.Length;
return ret;
}
catch (Exception ex)
{
throw new Exception(string.Format("Send command is failure:{0}", ex.Message), ex);
}
finally
{
_isSending = false;
}
}
return -1;
}
public bool Open()
{
if (this._serialPort == null || this._serialPort.IsOpen)
return false;
this._serialPort.Open();
_isRun = true;
_thrdRead.Start();
_thrdHandle.Start();
return true;
}
public bool Close()
{
_isRun = false;
if (_thrdHandle.IsAlive)
_thrdHandle.Join();
if (_thrdRead.IsAlive)
_thrdRead.Join();
if (this._serialPort == null)
return false;
if (this._serialPort.IsOpen)
this._serialPort.Close();
return true;
}
}
答案 1 :(得分:1)
根据您的方案,您希望按顺序访问串行端口(一次一个)。我从我的旧项目中提取了这段代码,这应该对你有帮助。
using System;
using System.IO.Ports;
using System.Linq;
using System.Threading;
public class SPHandler
{
/// <summary>
/// Your serial port
/// </summary>
private SerialPort _serialPort;
private int _timeOut, _timeOutDefault;
private AutoResetEvent _receiveNow;
/// <summary>
/// Possible device end responses such as \r\nOK\r\n, \r\nERROR\r\n, etc.
/// </summary>
private string[] _endResponses;
public SPHandler()
{
}
public void SetPort(string portName, int baudRate, int timeOut, string[] endResponses = null)
{
_timeOut = timeOut;
_timeOutDefault = timeOut;
_serialPort = new SerialPort(portName, baudRate);
_serialPort.Parity = Parity.None;
_serialPort.Handshake = Handshake.None;
_serialPort.DataBits = 8;
_serialPort.StopBits = StopBits.One;
_serialPort.RtsEnable = true;
_serialPort.DtrEnable = true;
_serialPort.WriteTimeout = _timeOut;
_serialPort.ReadTimeout = _timeOut;
if (endResponses == null)
_endResponses = new string[0];
else
_endResponses = endResponses;
}
public bool Open()
{
try
{
if (_serialPort != null && !_serialPort.IsOpen)
{
_receiveNow = new System.Threading.AutoResetEvent(false);
_serialPort.Open();
_serialPort.DataReceived += new SerialDataReceivedEventHandler(_serialPort_DataReceived);
return true;
}
else
{
return false;
}
}
catch
{
return false;
}
}
private void _serialPort_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
try
{
if (e.EventType == SerialData.Chars)
{
_receiveNow.Set();
}
}
catch (Exception ex)
{
throw ex;
}
}
public bool Close()
{
try
{
if (_serialPort != null && _serialPort.IsOpen)
{
_serialPort.Close();
return true;
}
else
{
return false;
}
}
catch
{
return false;
}
}
public string ExecuteCommand(string cmd)
{
_serialPort.DiscardOutBuffer();
_serialPort.DiscardInBuffer();
_receiveNow.Reset();
_serialPort.Write(cmd); // Sometimes + "\r" is needed. Depends on the device
string input = ReadResponse(); // Returns device response whenever you execute a command
_timeOut = _timeOutDefault;
return input;
}
private string ReadResponse()
{
string buffer = string.Empty;
try
{
do
{
if (_receiveNow.WaitOne(_timeOut, false))
{
string t = _serialPort.ReadExisting();
buffer += t;
}
} while (!_endResponses.Any(r => buffer.EndsWith(r, StringComparison.OrdinalIgnoreCase))); // Read while end responses are not yet received
}
catch
{
buffer = string.Empty;
}
return buffer;
}
}
<强>用法:强>
SPHandler spHandler = new SPHandler();
spHandler.SetPort(params);
spHandler.Open();
string response = spHandler.ExecuteCommand("Cmd_Req");
if (response == "Cmd_Res")
{
// Inform user that operation OP_A is allowed
// Enqueue data
}
else
{
// Oh no! Cmd_Res failed?!
}
// ... etc.
spHandler.Close(); // If you need to
基本上,只要执行命令,就会等待响应。这个SPHandler
的问题是它需要一个字符串命令和响应。您可以将其转换为读取/发送字节。
您应该注意用于顺序访问的AutoResetEvent
。即使您使用多线程,也会相应地进行处理。对_receiveNow.WaitOne
的调用对你来说是神奇的。 可能这是您在代码中应用的全部内容,因为您当前的问题是同时阅读Cmd_Res
和Cmd_Req
。
修改强>
回顾你的代码。您可以简单地取消注册SerialPort_DataReceived
,因为您只需要DoWork
来顺序执行命令并处理响应。