26

我正在编写一个 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,尝试重新打开端口,它会在重试几次后成功。

这会在我的工作线程中导致 IOExceptions,这是预期的。但是,即使我关闭了超级终端,我的工作线程中仍然会收到相同的 IOException。唯一的治疗方法实际上是重新启动计算机。

其他程序(未使用 .NET 库进行端口访问)此时似乎可以正常工作。

关于造成这种情况的任何想法?

4

9 回答 9

24

@thomask

是的,Hyperterminal 实际上在 SetCommState 的 DCB 中启用了 fAbortOnError,这解释了 SerialPort 对象抛出的大多数 IOExceptions。一些 PC / 手持设备还具有默认打开错误中止标志的 UART - 因此串行端口的 init 例程必须清除它(微软忽略了这样做)。我最近写了一篇很长的文章来更详细地解释这一点(如果你有兴趣,请看这个)。

于 2010-07-05T01:32:39.393 回答
4

您不能关闭其他人与端口的连接,以下代码将永远无法工作:

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);
}
于 2009-01-14T01:33:25.187 回答
2

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

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 语义,所以我不知道这是否可行。

于 2009-01-14T01:50:17.913 回答
2

如何进行可靠的异步通信

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

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

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

当 SerialPort 对象关闭时(这将在应用程序退出时发生),很可能会有一个挂起的 I/O 操作。在这种情况下,关闭 SerialPort 将触发回调,并且在这些情况下EndRead会抛出一个与一般的 comms shitfit 无法区分的异常。您应该在会话状态中设置一个标志以禁止catch块中的重新启动行为。这将阻止您的重启方法干扰自然关机。

可以依赖此架构不会意外地持有 SerialPort 对象。

restart 方法管理串行端口对象的关闭和重新打开。Close()在你呼唤SerialPort对象之后,打电话Thread.Sleep(5)给它一个放手的机会。其他东西可能会抢占端口,因此请准备好在重新打开它时处理这个问题。

于 2009-01-28T06:46:53.357 回答
1

我试过像这样改变工作线程,结果完全相同。一旦超级终端成功“捕获端口”(当我的线程处于睡眠状态时),我的服务将无法再次打开端口。

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();
    }
}
于 2009-01-14T02:00:02.153 回答
1

这段代码似乎工作正常。我已经在我的本地机器上的控制台应用程序中对其进行了测试,使用 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();
            }
        }
    }
于 2009-01-14T02:13:55.680 回答
1

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

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

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

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

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

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

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

  7. 瞧,我的服务再次启动,端口成功打开,LED 亮/灭。

于 2009-01-14T15:14:11.940 回答
0

这个答案很长才能成为评论......

我相信,当您的程序处于 Thread.Sleep(1000) 并且您打开超级终端连接时,超级终端会控制串行端口。当您的程序唤醒并尝试打开串行端口时,会引发 IOException。

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

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

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

于 2009-01-14T14:21:23.717 回答
0

是否有充分的理由阻止您的服务“拥有”端口?看看内置的 UPS 服务——一旦你告诉它有一个 UPS 连接到,比如说,COM1,你就可以和那个端口说再见了。我建议您也这样做,除非对共享端口有很强的操作要求。

于 2009-01-14T15:05:21.490 回答