如何使用.NET / C#进行强大的SerialPort编程?

我正在编写一个Windows服务,用于与串行磁条读取器和中继板(访问控制系统)进行通信。

在另一个程序通过打开与我的服务相同的串行端口“中断”进程后,我遇到了代码停止工作的问题(我得到IOExceptions)。

部分代码如下:

public partial class Service : ServiceBase { Thread threadDoorOpener; public Service() { threadDoorOpener = new Thread(DoorOpener); } public void DoorOpener() { while (true) { SerialPort serialPort = new SerialPort(); Thread.Sleep(1000); string[] ports = SerialPort.GetPortNames(); serialPort.PortName = "COM1"; serialPort.BaudRate = 9600; serialPort.DataBits = 8; serialPort.StopBits = StopBits.One; serialPort.Parity = Parity.None; if (serialPort.IsOpen) serialPort.Close(); serialPort.Open(); serialPort.DtrEnable = true; Thread.Sleep(1000); serialPort.Close(); } } public void DoStart() { threadDoorOpener.Start(); } public void DoStop() { threadDoorOpener.Abort(); } protected override void OnStart(string[] args) { DoStart(); } protected override void OnStop() { DoStop(); } } 

我的示例程序成功启动了工作线程,DTR的打开/关闭和boost使我的Mag-stripe读取器上电(等待1秒),关闭(等待1秒),依此类推。

如果我启动HyperTerminal并连接到同一个COM端口,HyperTerminal会告诉我该端口当前正在使用中。 如果我在HyperTerminal中反复按ENTER键,尝试重新打开端口,它会在重试几次后成功。

这会导致我的工作线程中出现IOExceptions,这是预期的。 但是,即使我关闭HyperTerminal,我仍然在我的工作线程中获得相同的IOException。 唯一的办法就是重启计算机。

其他程序(不使用.NET库进行端口访问)似乎在这一点上正常工作。

关于是什么导致这个的任何想法?

@thomask

是的,Hyperterminal确实在SetCommState的DCB中启用了fAbortOnError,这解释了SerialPort对象抛出的大多数IOExceptions。 有些PC /手持设备也有UART,默认情况下会打开错误标志中止 – 所以串口的init例程必须清除它(微软忽略了这一点)。 我最近写了一篇长篇文章来更详细地解释这个问题(如果你有兴趣的话,请看这个 )。

你不能关闭别人与端口的连接,以下代码永远不会工作:

 if (serialPort.IsOpen) serialPort.Close(); 

因为您的对象没有打开端口,所以无法关闭它。

即使发生exception,您也应该关闭并处理串行端口

 try { //do serial port stuff } finally { if(serialPort != null) { if(serialPort.IsOpen) { serialPort.Close(); } serialPort.Dispose(); } } 

如果您希望该过程可以中断,那么您应该检查端口是否打开然后退回一段时间然后再试一次,例如。

 while(serialPort.IsOpen) { Thread.Sleep(200); } 

您是否尝试在应用程序中打开端口,打开/关闭DtrEnable,然后在应用程序关闭时关闭端口? 即:

 using (SerialPort serialPort = new SerialPort("COM1", 9600)) { serialPort.Open(); while (true) { Thread.Sleep(1000); serialPort.DtrEnable = true; Thread.Sleep(1000); serialPort.DtrEnable = false; } serialPort.Close(); } 

我不熟悉DTR语义,所以我不知道这是否有效。

如何做可靠的异步通信

不要使用阻塞方法,内部帮助程序类有一些微妙的错误。

将APM与会话状态类一起使用,其实例管理跨调用共享的缓冲区和缓冲区游标,以及在try...catch中包装EndRead的回调实现。 在正常操作中, try块应该做的最后一件事是通过调用BeginRead()来设置下一个重叠的I / O回调。

当事情出错时, catch应该异步调用一个委托给一个restart方法。 回调实现应该在catch块之后立即退出,以便重启逻辑可以销毁当前会话(会话状态几乎肯定已损坏)并创建新会话。 不能在会话状态类上实现重新启动方法,因为这会阻止它破坏和重新创建会话。

当SerialPort对象关闭时(将在应用程序退出时发生)可能会有待处理的I / O操作。 如果是这样,关闭SerialPort将触发回调,并且在这些条件下, EndRead将抛出一个与通用comms shitfit无法区分的exception。 您应该在会话状态中设置一个标志以禁止catch块中的重新启动行为。 这将阻止重启方法干扰自然关机。

可以依赖此体系结构,而不是意外地保留SerialPort对象。

restart方法管理串行端口对象的关闭和重新打开。 在SerialPort对象上调用Close()之后,调用Thread.Sleep(5)让它有机会放手。 其他东西可以抓住端口,所以准备好在重新打开它时处理它。

我想我已经得出结论,超级终端不能很好地发挥作用。 我运行了以下测试:

  1. 在“控制台模式”下启动我的服务,它开始打开/关闭设备(我可以通过它的LED告诉)。

  2. 启动HyperTerminal并连接到端口。 设备保持打开状态(超级终端提升DTR)我的服务写入事件日志,无法打开端口

  3. 停止HyperTerminal,我使用任务管理器validation它已正确关闭

  4. 设备保持关闭状态(超级终端降低了DTR),我的应用程序继续写入事件日志,说它无法打开端口。

  5. 我启动第三个应用程序(我需要共存的应用程序),并告诉它连接到端口。 我这样做了。 这里没有错误。

  6. 我停止上面提到的申请。

  7. VOILA,我的服务再次启动,端口成功打开,LED开启/关闭。

我试过改变这样的工作线程,结果完全相同。 一旦HyperTerminal成功“捕获端口”(当我的线程正在hibernate时),我的服务将无法再次打开端口。

 public void DoorOpener() { while (true) { SerialPort serialPort = new SerialPort(); Thread.Sleep(1000); serialPort.PortName = "COM1"; serialPort.BaudRate = 9600; serialPort.DataBits = 8; serialPort.StopBits = StopBits.One; serialPort.Parity = Parity.None; try { serialPort.Open(); } catch { } if (serialPort.IsOpen) { serialPort.DtrEnable = true; Thread.Sleep(1000); serialPort.Close(); } serialPort.Dispose(); } } 

此代码似乎正常工作。 我已经在我的本地机器上在控制台应用程序中测试了它,使用Procomm Plus打开/关闭端口,程序继续滴答作响。

  using (SerialPort port = new SerialPort("COM1", 9600)) { while (true) { Thread.Sleep(1000); try { Console.Write("Open..."); port.Open(); port.DtrEnable = true; Thread.Sleep(1000); port.Close(); Console.WriteLine("Close"); } catch { Console.WriteLine("Error opening serial port"); } finally { if (port.IsOpen) port.Close(); } } } 

这个答案得到了很长时间的评论……

我相信当你的程序在Thread.Sleep(1000)中并打开超级终端连接时,HyperTerminal会控制串口。 当您的程序唤醒并尝试打开串行端口时,将抛出IOException。

重新设计您的方法并尝试以不同的方式处理端口的打开。

编辑:关于您必须在程序失败时重新启动计算机…

这可能是因为您的程序并未真正关闭,请打开您的任务管理器,看看您是否可以找到您的程序服务。 在退出应用程序之前,请务必停止所有线程。

有没有充分的理由让您的服务“拥有”该端口? 看看内置的UPS服务 – 一旦你告诉它有一个连接到COM1的UPS,你可以亲吻那个端口再见。 我建议你这样做,除非有强大的操作要求来共享端口。