![](/img/trans.png)
[英]Multithread C# serialPort1_DataReceived event does not fire
[英]C# SerialPort multithread send/receive collisions
我正在尝试在 C# 命令行应用程序(状态机)和通过 SerialPort 连接的外部设备之间进行通信。 我有一个 .Net Core 3.1.0 应用程序并且正在使用 Sytem.IO.Ports (4.7.0)。 我正在使用 FTDI USB 串行适配器。
我必须监视发送到我的计算机的数据指示的设备状态,并根据设备状态回复命令以进入下一个状态。
使用 Putty 时发送和接收工作没有问题。
当使用命令行从键盘和输入输出它的工作原理没有问题。
不幸的是,当从另一个线程向设备发送数据时,两个站点似乎同时发送,并且发生了使我的目标崩溃的冲突。
Initialization:
_port = new SerialPort(
Parameters.TelnetCOMport,
Parameters.TelnetBaudrate,
Parameters.TelnetParity,
Parameters.TelnetDataBits,
Parameters.TelnetStopBits);
_port.DataReceived += Port_DataReceived;
_port.ErrorReceived += Port_ErrorReceived;
_port.Handshake = Handshake.RequestToSend;
_port.RtsEnable = true;
_port.DtrEnable = true;
_port.Encoding = Encoding.ASCII;
_port.Open();
private void Send_Command(string command)
{
lock (_portLock)
{
_port.Write(command + "\n");
}
}
private string _dataReceived;
private void Port_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
lock (_portLock)
{
byte[] data = new byte[_port.BytesToRead];
_port.Read(data, 0, data.Length);
_dataReceived += Encoding.UTF8.GetString(data);
ProcessDataReceived();
}
}
我试图用锁来避免这种情况,但它没有帮助。 唯一有帮助的是在收到一个状态指示器后添加一个很长的延迟,以确保在发送新命令之前设备绝对不会发送任何内容。
所以我的问题是:如何在从多线程应用程序通过 SerialPort 发送数据时避免冲突?
感谢您评论我的问题!
由于 System.IO.Ports 类不能正确处理清除发送 (CTS),因此可能会发生发送/接收冲突。
我将引用 Ben Voigts 非常好的文章:
.NET 附带的 System.IO.Ports.SerialPort 类是一个明显的例外。 委婉地说,它是由计算机科学家设计的,这些科学家远远超出了他们的核心竞争力领域。 他们既不了解串行通信的特征,也不了解常见的用例,这说明了这一点。 它也无法在发货之前在任何真实世界场景中进行测试,而不会发现存在文档接口和未文档行为的缺陷,并使使用 System.IO.Ports.SerialPort(以下称为 IOPSP)的可靠通信成为真正的噩梦。 (StackOverflow 上的大量证据证明了这一点,来自在超级终端中工作但不在 .NET 中工作的设备......
最严重的 System.IO.Ports.SerialPort 成员,不仅不应该使用,而且是代码味道很深的迹象,需要重新构建所有 IOPSP 用法:
- DataReceived 事件(100% 冗余,也完全不可靠)
- BytesToRead 属性(完全不可靠) Read、ReadExisting、ReadLine 方法(处理错误完全错误,并且是同步的)
- PinChanged 事件(根据您可能想了解的每件有趣的事情按顺序发送)
#region Load C DLL
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
private delegate void ProgressCallback(IntPtr buffer, int length);
[DllImport("mttty.dll")]
static extern void ConnectToSerialPort([MarshalAs(UnmanagedType.FunctionPtr)] ProgressCallback telnetCallbackPointer,
[MarshalAs(UnmanagedType.FunctionPtr)] ProgressCallback statusCallbackPointer,
[MarshalAs(UnmanagedType.FunctionPtr)] ProgressCallback errorCallbackPointer);
#endregion
#region Callbacks
private void TelnetCallback(IntPtr unsafeBuffer, int length)
{
byte[] safeBuffer = new byte[length];
Marshal.Copy(unsafeBuffer, safeBuffer, 0, length);
Console.WriteLine(Encoding.UTF8.GetString(safeBuffer));
}
private void StatusCallback(IntPtr unsafeBuffer, int length)
{
byte[] safeBuffer = new byte[length];
Marshal.Copy(unsafeBuffer, safeBuffer, 0, length);
Console.WriteLine("Status: "+Encoding.UTF8.GetString(safeBuffer));
}
private void ErrorCallback(IntPtr unsafeBuffer, int length)
{
byte[] safeBuffer = new byte[length];
Marshal.Copy(unsafeBuffer, safeBuffer, 0, length);
Console.WriteLine("Error: "+Encoding.UTF8.GetString(safeBuffer));
}
#endregion
private static Socket _sock;
private static IPEndPoint _endPoint;
public StartSerialPortListenerThread()
{
// This socket is used to send Write request to thread in C dll
_sock = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
IPAddress serverAddr = IPAddress.Parse("127.0.0.1");
_endPoint = new IPEndPoint(serverAddr, 5555);
// This starts the listener thread in the C dll
Task.Factory.StartNew(() =>
{
ConnectToSerialPort(TelnetCallback, StatusCallback, ErrorCallback);
});
}
private void SendCommand(string command)
{
byte[] sendBuffer = Encoding.ASCII.GetBytes(command + '\n');
_sock.SendTo(sendBuffer, _endPoint);
}
我只是非常仔细地接触了 MTTTY 示例,因此我的 DLL 的工作方式与示例非常相似(非常可靠!)。
到目前为止,我还没有提供此代码,因为我必须进行一些清理。 如果您想尽快获得现状,我可以应要求将其发送给任何人。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.