c# - C# SerialPort 多线程发送/接收冲突
问题描述
我正在尝试在 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 发送数据时避免冲突?
解决方案
感谢您评论我的问题!
事实证明,发生错误是因为 .NET Framework 的 System.IO.Ports 类的质量非常差。
由于 System.IO.Ports 类不能正确处理 Clear-To-Send (CTS),因此可能会发生冲突的发送/接收。
我将引用 Ben Voigts 非常好的文章:
.NET 附带的 System.IO.Ports.SerialPort 类是一个明显的例外。说得委婉些,它是由计算机科学家设计的,他们的工作范围远远超出了他们的核心竞争力领域。他们既不了解串行通信的特性,也不了解常见的用例,它显示了。在发货之前,它也无法在任何真实世界场景中进行测试,没有发现缺陷会导致记录的接口和未记录的行为都乱扔垃圾,并使使用 System.IO.Ports.SerialPort(以下简称 IOPSP)的可靠通信成为真正的噩梦。(StackOverflow 上的大量证据证明了这一点,来自在 Hyperterminal 而非 .NET 中工作的设备......
最糟糕的 System.IO.Ports.SerialPort 成员,它们不仅不应该被使用,而且是代码味道很重的迹象,并且需要重新架构所有 IOPSP 使用:
- DataReceived 事件(100% 冗余,也完全不可靠)
- BytesToRead 属性(完全不可靠) Read、ReadExisting、ReadLine 方法(处理错误完全错误,并且是同步的)
- PinChanged 事件(关于您可能想知道的每件有趣的事情都乱序传递)
为了解决这个问题,我通过执行以下操作编写了自己的SerialPorts类:
- 我从 MSDN 阅读了这篇关于串行通信的完美文章
- 我从github下载了示例 MTTTY 代码
- 我将 MTTTY 示例重新设计为 .dll,以便可以从 C# 调用它
- 我学会了如何从 C调用C# 中的回调
- 我重新设计了 MTTTY 示例以运行连续侦听的 ReaderThread 和可以通过UDP 数据报调用的WriterThread
C# 代码如下所示:
#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);
});
}
通过发送 UDP 数据报很容易调用 send 命令
private void SendCommand(string command)
{
byte[] sendBuffer = Encoding.ASCII.GetBytes(command + '\n');
_sock.SendTo(sendBuffer, _endPoint);
}
我只是非常仔细地接触了 MTTTY 示例,因此我的 DLL 的工作方式与示例非常相似(非常可靠!)。
到目前为止,我还没有提供此代码,因为我必须进行一些清理。如果您想尽快获得现状,我可以按要求将其发送给任何人。
推荐阅读
- excel - SUMIF 单元格不是公式
- lua - 不是模型的有效成员 [帮助]
- node.js - 我在 heroku 中的 API CORS 连接有问题
- java - Maven 资源插件:如果目录不存在,构建不会失败
- php - 有没有办法将ajax数据保存为整数
- python - python代码在线运行时错误,离线工作
- javascript - 你可以在同一个提交上运行的一个函数中有两个 ajax 调用吗?
- php - 如何使用 PHP 对用户数组进行 JSON 编码?
- java - 如何在控制器操作中实例化 CommandLineRunner 以及所有 @Autowired 属性?
- javascript - 如何删除特定用户的最后 N 条消息