【问题标题】:"Safe handle has been closed" with SerialPort and a thread in C#使用 SerialPort 和 C# 中的线程“安全句柄已关闭”
【发布时间】:2010-11-22 02:02:03
【问题描述】:

大家下午好!

我有这个线程化的SerialPort 包装器,它从串行端口读取一行。这是我线程的代码。

protected void ReadData()
{
     SerialPort serialPort = null;
     try
     {
         serialPort = SetupSerialPort(_serialPortSettings);
         serialPort.Open();

         string data;
         while (serialPort.IsOpen)
         {
             try
             {

                 data = serialPort.ReadLine();
                 if (data.Length > 0)
                     ReceivedData(serialPort, new ReceivedDataEventArgs(data));

             }
             catch (TimeoutException)
             {
                 //  No action
             }
         }
     }
     catch (ThreadAbortException)
     {
         if (serialPort != null)
             serialPort.Close();
     }
}

当我调用myThread.Abort(); 时,我得到一个异常(没有行或对代码的引用)“安全句柄已关闭”。谁能发现我做错了什么?谢谢。

顺便说一句,我有一个 Start() 和一个 Stop(),它们分别创建线程并中止线程。

【问题讨论】:

    标签: c# multithreading exception serial-port stream


    【解决方案1】:

    我怀疑这是因为您使用 Thread.Abort 来结束线程 - 这通常是不受欢迎的。 中止时的线程行为是不可预测的。正因为如此,由于串行端口是本机代码的包装器,因此存在本机资源 - 由 .NET 中的 SafeHandle 表示 - 会意外处理,因此您会收到异常。

    你可以这样想你的线程会发生什么:

    • 你开始你的话题
    • 您打开串行端口(分配本机资源并使用 SafeHandle 来保留这些资源)
    • 你开始从串口读取
    • 然后在某个时候(您的线程出乎意料)您调用 Thread.Abort 就可以了
    • 很可能您的线程中的代码此时正试图访问串行端口(以读取数据)
    • 线程被杀死,串口句柄被隐式销毁
    • 您会从串口的 ReadLine() 函数内的代码中抛出异常,因为它的句柄不再有效

    您确实应该使用不同的方法来中止线程,以便您有适当的机会关闭和处理串行端口。

    可以使用像这样的 ManualResetEvent 来实现关闭线程的正确方法:

    protected ManualResetEvent threadStop = new ManualResetEvent(false);
    
    protected void ReadData()
    {
         SerialPort serialPort = null;
         try
         {
             serialPort = SetupSerialPort(_serialPortSettings);
             serialPort.Open();
    
             string data;
             while (serialPort.IsOpen)
             {
                 try
                 {
    
                     data = serialPort.ReadLine();
                     if (data.Length > 0)
                         ReceivedData(serialPort, new ReceivedDataEventArgs(data));
    
                 }
                 catch (TimeoutException)
                 {
                     //  No action
                 }
    
                 // WaitOne(0) tests whether the event was set and returns TRUE
                 //   if it was set and FALSE otherwise.
                 // The 0 tells the manual reset event to only check if it was set
                 //   and return immediately, otherwise if the number is greater than
                 //   0 it will wait for that many milliseconds for the event to be set
                 //   and only then return - effectively blocking your thread for that
                 //   period of time
                 if (threadStop.WaitOne(0))
                     break;
             }
         }
         catch (Exception exc)
         {
             // you can do something here in case of an exception
             // but a ThreadAbortedException should't be thrown any more if you
             // stop using Thread.Abort and rely on the ManualResetEvent instead
         }
         finally
         {
             if (serialPort != null)
                 serialPort.Close();
         }
    }
    
    protected void Stop()
    {
        // Set the manual reset event to a "signaled" state --> will cause the
        //   WaitOne function to return TRUE
        threadStop.Set();
    } 
    

    当然,当使用 events 方法停止线程时,您必须小心在所有长时间运行的循环或任务中包含事件状态检查。如果您不这样做,您的线程可能不会响应您设置的事件 - 直到它退出长时间运行的循环或任务并有机会“看到”事件已设置。

    【讨论】:

    • 关闭我的线程的正确方法是什么。 Stop()/Start() 用于我重新配置端口时。
    • 有几种方法可以做到这一点,我给出了一个使用 ManualResetEvent 的示例,这是一种非常常见的机制..
    • 不客气。我希望你明白为什么 Thread.Abort 不是一个好的做法。如果您想了解有关该主题的更多信息 - Joe Duffy 有一本关于 Windows 并发的非常棒的书:bluebytesoftware.com/books/winconc/winconc_book_resources.html
    • 我发现当基于 USB 的串行端口打开时拔下它们往往会导致奇怪的行为,包括“安全句柄已关闭”异常。对于只要插入就应该运行但可能随时拔出的设备,我发现唯一有效的补救措施是生成一个单独的应用程序来处理每个设备,并使用某种管道将此类应用程序绑定到主应用程序。真的很糟糕,但我不知道还能做什么。
    【解决方案2】:

    遇到了类似的情况,我尝试在单个方法本地创建串行端口连接。

    这个想法是创建最多四个 serialPort 对象(对于每个串行端口)。一旦其中一个端口返回了良好的数据,我就会知道我的设备连接到哪个端口。我会丢弃我所有的“试用”对象并创建一个到特定 com 端口的新连接。

    每次通过该方法我都会创建/丢弃我的 serialPort 对象。哎呀。碰巧的是,如果在我再次调用该方法时 GC 还没有运行,它会尝试创建第二个串行连接来替换第一个连接,并且它们会发生冲突。那会触发这个错误。

    解决方案:将所有串口连接对象设为类的全局对象。 丑陋的黑客:是的,但它有效

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2014-12-15
      • 2014-01-09
      • 1970-01-01
      • 2021-11-02
      • 1970-01-01
      • 1970-01-01
      • 2015-10-19
      • 1970-01-01
      相关资源
      最近更新 更多