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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/441559/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-04 03:30:24  来源:igfitidea点击:

How to do robust SerialPort programming with .NET / C#?

c#.netserial-port

提问by Thomas Kj?rnes

I'm writing a Windows Service for communication with a Serial Mag-stripe reader and a relay board (access control system).

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

I run into problems where the code stops working (i get IOExceptions) after another program has "interrupted" the process by opening the same serial port as my service.

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

Part of the code is as follows:

部分代码如下:

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();
    }
}

My sample program successfully starts the work-thread, and the opening/closing and raising of DTR causes my Mag-stripe reader to power up (wait 1sec), shut down (wait 1 sec) and so on.

我的示例程序成功启动了工作线程,并且 DTR 的打开/关闭和提升导致我的磁条阅读器通电(等待 1 秒)、关闭(等待 1 秒)等等。

If I launch HyperTerminal and connects to the same COM port, HyperTerminal tells me the port is currently in use. If i repeatedly press ENTER in HyperTerminal, to try to reopen the port it will succeed after a few retries.

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

This has the effect of causing IOExceptions in my work-thread, which is expected. However, even if I close down HyperTerminal, i still get the same IOException in my work-thread. The only cure is actually to restart the computer.

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

Other programs (which is not using .NET libraries for port-access) seem to work normally at this point.

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

Any ideas as to what is causing this?

关于是什么导致这种情况的任何想法?

采纳答案by Zach Saw

@thomask

@thomask

Yes, Hyperterminal does in fact enable fAbortOnError in SetCommState's DCB, which explains for most of the IOExceptions thrown by the SerialPort object. Some PCs / handhelds also have UARTs that have the abort on error flag turned on by default - so it's imperative that a serial port's init routine clears it (which Microsoft neglected to do). I wrote a long article recently to explain this in greater detail (see thisif you're interested).

是的,超级终端实际上​​在 SetCommState 的 DCB 中启用了 fAbortOnError,这解释了 SerialPort 对象抛出的大多数 IOExceptions。一些个人电脑/手持设备也有 UART,默认情况下会打开错误标志中止 - 因此串行端口的 init 例程必须清除它(微软忽略了这样做)。我最近写了一篇很长的文章来更详细地解释这一点(如果您有兴趣,请参阅内容)。

回答by trampster

You can't close someone elses connection to a port, the following code will never work:

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

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

Because your object didn't open the port you can't close it.

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

Also you should close and dispose the serial port even after exceptions occur

即使发生异常,您也应该关闭并处理串口

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

If you want the process to be interruptible then you should Check if the port is open and then back off for a period and then try again, something like.

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

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

回答by FryGuy

Have you tried leaving the port open in your application, and just turning DtrEnable on/off, and then closing the port when your application closes? i.e:

您是否尝试过在应用程序中打开端口,然后打开/关闭 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();
}

I'm not familiar with DTR semantics, so I don't know if this would work.

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

回答by Thomas Kj?rnes

I've tried changing the work-thread like this, with the exact same result. Once HyperTerminal once succeeds in "capturing the port" (while my thread is sleeping), my service won't be able to open the port again.

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

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();
    }
}

回答by FryGuy

This code seems to work properly. I've tested it on my local machine in a console application, using Procomm Plus to open/close the port, and the program keeps on ticking.

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

回答by Anders R

This answer got to long to be a comment...

这个答案很长一段时间才能成为评论......

I believe that when your program is in a Thread.Sleep(1000) and you open your HyperTerminal connection, the HyperTerminal takes control over the serial port. When your program then wakes up and trying to open the serial port, an IOException is thrown.

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

Redesign your method and try to handle the opening of the port in a different way.

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

EDIT: About that you have to reboot your computer when your program fails...

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

That probably because your program isn′t really closed, open your taskmanager and see if you can find your program service. Be sure to stop all your threads before exiting your application.

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

回答by Coderer

Is there a good reason to keep your service from "owning" the port? Look at the built-in UPS service -- once you tell it there's an UPS attached to, say, COM1, you can kiss that port goodbye. I'd suggest you do the same unless there's a strong operational requirement to share the port.

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

回答by Thomas Kj?rnes

I think I have come to the conclusion that HyperTerminal does not play well. I've run the following test:

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

  1. Start my service in "console mode", it starts switching the device on/off (i can tell by it's LED).

  2. Start HyperTerminal and connect to the port. The device stays on (HyperTerminal raises DTR) My service writes to the event log, that it cannot open the port

  3. Stop HyperTerminal, I verify it is properly closed using task manager

  4. The device stays off (HyperTerminal has lowered DTR), my app keeps on writing to the event log, saying it cannot open the port.

  5. I start a third application (the one I need to coexist with), and tell it to connect to the port. I does so. No errors here.

  6. I stop the above mentioned application.

  7. VOILA, my service kicks in again, the port opens successfully, and the LED goes ON/OFF.

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

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

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

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

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

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

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

回答by Peter Wone

How to do reliable async comms

如何进行可靠的异步通信

Don't use the blocking methods, the internal helper class has some subtle bugs.

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

Use APM with a session state class, instances of which manage a buffer and buffer cursor shared across calls, and a callback implementation that wraps EndReadin a try...catch. In normal operation, the last thing the tryblock should do is set up the next overlapped I/O callback with a call to BeginRead().

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

When things go awry, catchshould asynchronously invoke a delegate to a restart method. The callback implementation should exit immediately after the catchblock so that the restart logic can destroy the current session (session state is almost certainly corrupt) and create a new session. The restart method must notbe implemented on the session state class because this would prevent it from destroying and recreating the session.

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

When the SerialPort object is closed (which will happen when the application exits) there may well be a pending I/O operation. When this is so, closing the SerialPort will trigger the callback, and under these conditions EndReadwill throw an exception that is indistinguishable from a general comms shitfit. You should set a flag in your session state to inhibit the restart behaviour in the catchblock. This will stop your restart method from interfering with natural shutdown.

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

This architecture can be relied upon not to hold onto the SerialPort object unexpectedly.

可以依靠这种体系结构不会意外地保留 SerialPort 对象。

The restart method manages the closing and re-opening of the serial port object. After you call Close()on the SerialPortobject, call Thread.Sleep(5)to give it a chance to let go. It is possible for something else to grab the port, so be ready to deal with this while re-opening it.

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