“安全手柄已关闭”使用 C# 中的串行端口和线程

发布于 2024-08-02 20:50:40 字数 944 浏览 5 评论 0原文

大家下午好!

我有这个线程 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() 来分别创建线程和中止线程。

Good afternoon everybody!

I have this threaded SerialPort wrapper that reads in a line from the serial port. Here is my thread's code.

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

when I call myThread.Abort(); I get an exception (with no line or reference to code) "Safe handle has been closed". Can anyone spot what I am doing wrong? Thanks.

By the way, I have a Start() and a Stop() that creates the thread and aborts the thread, respectfully.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

东风软 2024-08-09 20:50:40

我怀疑这是因为您正在使用 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();
} 

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

I would suspect that it is because you are using Thread.Abort to end the thread - which is generally frowned upon. The thread behavior when you abort it is not predictable. Because of that, since the serial port is a wrapper over native code, there are native resources - represented by a SafeHandle in .NET - which get disposed of unexpectedly and so you get the Exception.

You can think about what happens with your thread like this:

  • you start your thread
  • you open the serial port (which allocates native resources and uses SafeHandle(s) to hold on to those resources)
  • you start reading from the serial port
  • then at some point (unexpected to your thread) you call Thread.Abort on it
  • most likely the code in your thread is at that point trying to access the serial port (to read data)
  • the thread gets killed and the serial port handle is destroyed implicitly
  • you get an exception thrown from the code inside the ReadLine() function of the serial port because the handle it had is no longer valid

You really should use a different method for aborting the thread so that you get a proper chance to close and dispose of the serial port.

A proper way to close your thread could be implemented using a ManualResetEvent like this:

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

Of course, when using the events method to stop the thread you have to be careful to include an event state check in all your long running loops or tasks. If you don't your thread may appear not to respond to your setting the event - until it gets out of the long-running loop, or task and gets a chance to "see" that the event has been set.

甜尕妞 2024-08-09 20:50:40

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

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

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

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

Ran into a similar situation where I attempted to create serial port connections local to a single method.

The idea was to create up to four serialPort objects (For each serial port). Once one of the ports came back with good data I'd know which port my device was connected to. I'd discard all my "trial" objects and create a new connection to the specific com port.

Each time through the method I'd create/discard my serialPort objects. Oops. It so happens that if the GC hadn't run by the time I called the method again it would attempt to create a second serial connection replacing the first connection and they would collide. That would trigger this error.

Solution: make all serial port connection objects global to the class.
Ugly Hack: Yes, but it Works

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文