正确释放串口

7
我正在用C#编写一个应用程序,利用SerialPort类与几个设备进行通信。现在我一直遇到的大问题是如何正确释放资源,因为当尝试使用已经被占用的串口时,会立即出现异常。
由于通常情况下垃圾回收器应该能够处理大部分工作,所以我已经没有其他想法可以尝试了...
主要我尝试了两种方法(按照我的逻辑应该可行)。我使用基于会话的通信,所以在每次通信之前和之后调用OpenPort和ClosePort方法 - 因此端口应该已经关闭。另外,我试过将包含端口的对象设置为空 - 但是我仍然一直得到UnauthorizedAccessExceptions异常 - 尽管我100%确定已经调用了SerialPort.Close()方法。
你们知道更好的释放端口的方法吗,这样我就不会再遇到那个异常了吗?
编辑: 感谢回答,但Dispose()方法并没有起作用 - 我之前也尝试过 - 也许我做错了什么,所以这里有一个示例,看看我的代码是什么样子的:
它实际上很像Øyvind建议的,虽然我只是添加了IDisposable - 但也没用:
这将是我的包装类:
class clsRS232 : IDisposable
{
  public void clsRS232()
  {
    Serialport port = new Serialport("COM1",9600,Parity.none,8,Stopbits.one);
  }
  public void openPort()
  {
     port.Open();
  }
  public void sendfunc(string str)
  {
    port.Write(str);
  }
  public string readfunc()
  {
    port.ReadTo("\n");
  }

  public void Dispose()
  {
     port.Dispose();
  }

}

现在每当我需要进行RS232通信时,我会像这样调用一个新的实例:
   clsRS232 test = new clsRS232;
   test.openport();
   test.sendfunc("test");
   test.Dispose();

但这并没有改变什么 - 我仍然会收到许多UnauthorizedAccessExceptions错误 - 如果那个人是正确的(即SerialPort类的Dispose()方法只包含SerialPort.Close()),那么我猜我从我的早期方法中并没有真正改变任何东西,其中有一个函数调用close(); 感谢您的回答 - 仍然希望找到解决方案 :)
3个回答

8

由于SerialPort实现了IDisposable,因此您应该按照以下方式编写代码:

using( SerialPort port = new SerialPort( ... ) ){
  //do what you need with the serial port here
}

这将确保在using块结束时,串口被释放,如果using块内发生异常,它也会被释放,因为using块与try/finally块完全相同,在finally块内关闭/处理SerialPort
编辑:
根据OP的需求,SerialPort应该保持开放状态的时间比方法的时间框架更长。
在这种情况下,我会将与串口相关的整个逻辑封装在自己的类中。在类的构造函数中打开串口,并编写执行所需操作的方法。然后让这个类实现IDisposable本身,并在您自己的Dispose方法中处理SerialPort。
这将使您更好地控制何时打开和关闭/处理串口,并将串口逻辑封装到一个正确的类中。
如果您想保持端口打开一段不易由代码块包含的时间,则必须在完成使用后手动处理它,例如当使用它的功能关闭或触发程序中释放com端口的任何内容时。
编辑2:
您当前的实现方式如下:
clsRS232 test = new clsRS232;
test.openport();
test.sendfunc("test");
test.Dispose();

这里的问题在于,如果sendfunc在某种情况下引发异常,它将永远不会被处理。你从一开始实现IDisposable所获得的好处是,你可以将代码改为以下形式:

using( clsRS232 test = new clsRS232 ){
 test.openport();
 test.sendfunc("test");
}

现在,使用using块时,无论其中是否发生异常,您都可以确保Dispose将被调用以释放您的串口。

谢谢您的快速回复,但我不能以那种方式做,因为我已经封装了很多自己的方法在一个类中,我用它来进行串口通信 - 它们都使用一个端口,在第一次调用该类时初始化 - 所以如果我想以这种方式做,我最终会不得不在每个使用端口的方法中编写这样的代码块,对吧? - Lorenz
你想在创建类的同时保持端口打开状态,并在所有操作完成后关闭它,还是想在每个操作之前和之后打开并关闭与端口的连接? - Øyvind Bråthen
我的包装类通过构造函数启动SerialPort的实例,然后可以通过多个方法进行发送/接收等操作,但是一旦我调用自己的close()函数(目前包含SerialPort.Close()),我希望Serialport完全释放以便再次实例化。在每个操作之前和之后打开和关闭不起作用,因为例如我写入某些内容后关闭了端口,然后再次打开它以接收某些内容-->缓冲区将消失... - Lorenz
看我的编辑。我认为这是这种工作的好方法。我在一个串行设备发送数据到我时需要打开端口读取缓冲区,但最后需要确保关闭端口的情况下做了同样的事情。 - Øyvind Bråthen
我也在我的原始帖子中进行了编辑 - 我的程序实际上与您描述的非常相似,但它似乎并没有像那样工作...请看一下,也许我在某个地方理解错误了... - Lorenz
我已经做到了这一步 - 仍然在想为什么手动调用Dispose()时整个程序没有工作,但是...谁在乎呢 :) 现在只需要改变我的旧代码,因为它基于每个设备的单个包装类实例,该实例被打开和关闭多次 :) - Lorenz

3

我知道这是非常久远的问题,但我刚刚遇到了同样的问题,并且这个解决方案对我有效,虽然它有点hacky。根据这个线程why is access to com port denied?的说法,问题出在SerialPortClass中的一个bug上。我创建了一个包装类,只打开一次端口,并为应用程序的生命周期创建该类。然后,在该类的Dispose中释放SerialPort,但是使用以下方式打开:

  private SerialPort KickerPort { get; set; }
    .
    .
    .
private bool OpenPort()
        {
            //https://dev59.com/l2w05IYBdhLWcg3weBvu
            //due to a bug in the SerialPort code, the serial port needs time to dispose if we used this recently and then closed
            //therefore the "open" could fail, so put in a loop trying for a few times
            int sleepCount = 0;
            while (!TryOpenPort())
            {
                System.Threading.Thread.Sleep(100);
                sleepCount += 1;
                System.Diagnostics.Debug.Print(sleepCount.ToString());
                if (sleepCount > 50) //5 seconds should be heaps !!!
                {
                    throw new Exception(String.Format("Failed to open kicker USB com port {0}", KickerPort.PortName));
                }
            }
            return true;
        }
     private bool TryOpenPort()
                {
                    if (!KickerPort.IsOpen)
                    {
                        try
                        {
                            KickerPort.Open();
                            return true;
                        }
                        catch (UnauthorizedAccessException)
                        {
                            return false;
                        }
                        catch (Exception ex)
                        {
                            throw ex;
                        }

                    }
                    return true;
                }

这是被调用的函数:

 try
            {
                if (OpenPort())
                {
                    //do your thing here !
                }
                return false;
            }
            catch (Exception ex)
            {  
                throw ex;
            }

在我的测试中(我用它来打开一个USB踢脚器上的钱箱),我发现有时候第一次就能打开,而另外一些时候需要循环睡眠约20次,这取决于最近钱箱被打开的时间。

1
Øyvind Bråthen 提出的实现使用 .NET 中的 IDisposable 模式。在 using 块的结束处,将调用 SerialPort 实例的 Dispose 函数,这将释放关联的非托管资源(即串口)。
当您想要释放端口时,请自行调用 port.Dispose()。

是的 - 我之前尝试过Dispose() - 不幸的是,它对我的异常没有任何改变:/ 谢谢 :) - Lorenz

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接