[英]How to do robust SerialPort programming with .NET / C#?
我正在编写一个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的打开/关闭和升高使我的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();
因为您的对象没有打开端口,所以无法关闭它。
即使发生异常,您也应该关闭并处理串行端口
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
应异步调用委托给重启方法。 回调实现应该在catch
块之后立即退出,以便重启逻辑可以销毁当前会话(会话状态几乎肯定已损坏)并创建新会话。 不能在会话状态类上实现重新启动方法,因为这会阻止它破坏和重新创建会话。
当SerialPort对象关闭时(将在应用程序退出时发生),可能存在挂起的I / O操作。 如果是这样,关闭SerialPort将触发回调,并且在这些条件下, EndRead
将抛出一个与通用comms shitfit无法区分的异常。 您应该在会话状态中设置一个标志以禁止catch
块中的重新启动行为。 这将阻止重启方法干扰自然关机。
可以依赖此体系结构不会意外地保留SerialPort对象。
restart方法管理串行端口对象的关闭和重新打开。 在SerialPort
对象上调用Close()
之后,调用Thread.Sleep(5)
让它有机会放手。 其他东西可以抓住端口,所以准备好在重新打开它时处理它。
我试过改变这样的工作线程,结果完全相同。 一旦HyperTerminal成功“捕获端口”(当我的线程正在休眠时),我的服务将无法再次打开端口。
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();
}
}
}
我想我已经得出结论,超级终端不能很好地发挥作用。 我运行了以下测试:
以“控制台模式”启动我的服务,它开始/关闭设备(我可以通过它的LED告诉)。
启动HyperTerminal并连接到端口。 设备保持打开状态(HyperTerminal引发DTR)我的服务写入事件日志,无法打开端口
停止HyperTerminal,我使用任务管理器验证它已正确关闭
设备保持关闭状态(超级终端降低了DTR),我的应用程序继续写入事件日志,说它无法打开端口。
我启动第三个应用程序(我需要共存的应用程序),并告诉它连接到端口。 我这样做了。 这里没有错误。
我停止上面提到的申请。
VOILA,我的服务再次启动,端口成功打开,LED开启/关闭。
这个答案很长时间才成为评论......
我相信当你的程序在Thread.Sleep(1000)中并打开超级终端连接时,超级终端可以控制串口。 当您的程序唤醒并尝试打开串行端口时,将抛出IOException。
重新设计您的方法并尝试以不同的方式处理端口的打开。
编辑:关于您必须在程序失败时重新启动计算机...
这可能是因为你的程序没有真正关闭,打开你的任务管理员,看看你是否能找到你的程序服务。 在退出应用程序之前,请务必停止所有线程。
有没有充分的理由让您的服务“拥有”该端口? 看看内置的UPS服务 - 一旦你告诉它有一个连接到COM1的UPS,你可以亲吻那个端口再见。 除非有强大的操作要求来共享端口,否则我建议你这样做。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.