我正在编写一个 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)给它一个机会,放手。这可能是别的东西抢了口,所以要准备好应对这个而重新打开它。

我想我已经得出了超级终端玩得不好的结论。我已经运行了以下测试:

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

  2. 启动超级终端并连接到端口。该设备停留在(高端提高DTR)我的服务为事件日志上写的,它无法打开端口

  3. 停止超级终端,我使用任务管理器验证它已正确关闭

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

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

  6. 我停止上述应用程序。

  7. 瞧,我的服务再次启动,端口成功打开,并且 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,你可以亲吻该端口告别。我建议你也这样做,除非有共享端口较强的可操作性的要求。

scroll top