如何使用 .NET / C# 进行可靠的串行端口编程?
-
22-07-2019 - |
题
我正在编写一个 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 的打开/关闭和提升导致我的磁条阅读器开机(等待 1 秒)、关闭(等待 1 秒)等。
如果我启动超级终端并连接到同一 COM 端口,超级终端会告诉我该端口当前正在使用中。如果我反复按Enter in Hyperterminal,以尝试重新打开端口,它将在重试后成功。
这会导致我的工作线程中出现 IOException,这是预期的。但是,即使我关闭超级终端,我的工作线程中仍然会遇到相同的 IOException。唯一的解决方法实际上是重新启动计算机。
其他程序(不使用 .NET 库进行端口访问)此时似乎可以正常工作。
关于造成这种情况的原因有什么想法吗?
解决方案
@thomask
是,超级终端事实上确实使fAbortOnError在SetCommState的DCB,这解释了对于大多数由的SerialPort对象抛出的IOExceptions的。有些PC /手持设备也有对错误标志的中止默认开启的UART - 所以它的当务之急是串口的初始化程序中清除(微软忽视了这样做)。我写了一篇长文最近在更详细地说明这一点(见这个一>如果你有兴趣)。
其他提示
您不能关闭别人连接到一个端口,下面的代码将永远不会工作:
if (serialPort.IsOpen) serialPort.Close();
由于你的对象没有打开的端口,你无法将其关闭。
另外,应关闭并出现异常后,即使配置串口
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与会话状态类,它们的实例管理的缓冲和缓冲在调用共享光标,并且在一个EndRead
包装try...catch
一个回调实现。在正常操作中,最后一件事try
块应该做的是建立下一个重叠I / O回调函数调用BeginRead()
。
当事情出差错,catch
应该异步调用的委托,以重新启动方法。回调实现应该在catch
块后立即退出,以便重新启动逻辑可以摧毁当前会话(会话状态几乎可以肯定是腐败),并创建一个新的会话。重启方法必须的不的进行会话状态类实现的,因为这会阻止其破坏并重新创建会话。
当所述的SerialPort对象被关闭(这会发生在应用程序退出)很可能有一个挂起的I / O操作。如果是这样,关闭一个串口将触发回调,并在这些条件下EndRead
会抛出异常是从一般的通讯科shitfit没有什么区别。你应该在你的会话状态设置一个标志,禁止在catch
块的重新启动行为。这将从自然关机停止干涉你的重启方法。
此架构可根据不可靠不放不料的SerialPort对象。
在重启方法管理串行端口对象的闭合和重新打开。当你调用Close()
对象SerialPort
,呼吁Thread.Sleep(5)
给它一个机会,放手。这可能是别的东西抢了口,所以要准备好应对这个而重新打开它。
我想我已经得出了超级终端玩得不好的结论。我已经运行了以下测试:
以“控制台模式”启动我的服务,它开始打开/关闭设备(我可以通过它的 LED 来判断)。
启动超级终端并连接到端口。该设备停留在(高端提高DTR)我的服务为事件日志上写的,它无法打开端口
停止超级终端,我使用任务管理器验证它已正确关闭
设备保持关闭状态(超级终端已降低 DTR),我的应用程序继续写入事件日志,表示无法打开端口。
我启动第三个应用程序(我需要与之共存的应用程序),并告诉它连接到端口。我就是这样做的。这里没有错误。
我停止上述应用程序。
瞧,我的服务再次启动,端口成功打开,并且 LED 亮起/熄灭。
我试图改变工作线程这样,用完全相同的结果。一旦超级终端一次成功地“捕获端口”(而我的线程处于休眠状态),我的服务将无法再次打开端口。
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加打开/关闭端口,并计划不断滴答作响。
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)和您打开超级终端连接,超级终端接管串口控制。当你的程序,然后醒来并试图打开串行端口,则抛出IOException。
重新设计您的方法并尝试处理的端口以不同的方式开口。
编辑: 关于你必须重新启动计算机时,你的程序失败...
这可能是因为你的程序isn't真的关闭了,打开任务管理器,看看是否能找到您的节目服务。请确保在退出应用程序之前,停止所有的线程。
有一个很好的理由让从“拥有”端口的服务?看看内置的UPS服务 - 一旦你告诉它有连接到,比方说,COM1的UPS,你可以亲吻该端口告别。我建议你也这样做,除非有共享端口较强的可操作性的要求。