SerialPort 的 Finalize() 在 GC.SuppressFinalize() 之后仍然调用

2024-01-23

我正在努力使用SerialPort在 C# 中,发现 .NET 中的串行端口 API 自 2010 年以来就有错误。我结合了 中制作的补丁这篇博文 http://zachsaw.blogspot.ca/2010/07/serialport-ioexception-workaround-in-c.html and 那个答案 https://stackoverflow.com/a/9057158/5771029.

但是,当我尝试关闭串行端口时,我仍然在 GC 线程中遇到未处理的异常,导致程序崩溃。有趣的是,堆栈跟踪的顶部恰好位于SerialStream's Finalize()方法即使SerialStream对象已经是GC.SuppressFinalize()通过我链接的答案的代码。

这是例外情况:

System.ObjectDisposedException: Safe handle has been closed
  at System.Runtime.InteropServices.SafeHandle.DangerousAddRef(Boolean& success)
  at System.StubHelpers.StubHelpers.SafeHandleAddRef(SafeHandle pHandle, Boolean& success)
  at Microsoft.Win32.UnsafeNativeMethods.SetCommMask(SafeFileHandle hFile, Int32 dwEvtMask)
  at System.IO.Ports.SerialStream.Dispose(Boolean disposing)
  at System.IO.Ports.SerialStream.Finalize()

以下是串口调试的日志:

2017-20-07 14:29:22, Debug, Working around .NET SerialPort class Dispose bug 
2017-20-07 14:29:22, Debug, Waiting for the SerialPort internal EventLoopRunner thread to finish...
2017-20-07 14:29:22, Debug, Wait completed. Now it is safe to continue disposal. 
2017-20-07 14:29:22, Debug, Disposing internal serial stream 
2017-20-07 14:29:22, Debug, Disposing serial port

这是我的代码,是我发布的 2 个链接的略微修改版本:

public static class SerialPortFixerExt
{
    public static void SafeOpen(this SerialPort port)
    {
        using(new SerialPortFixer(port.PortName))
        {
        }

        port.Open();
    }

    public static void SafeClose(this SerialPort port, Logger logger)
    {
        try
        {
            Stream internalSerialStream = (Stream)port.GetType()
                .GetField("internalSerialStream", BindingFlags.Instance | BindingFlags.NonPublic).GetValue(port);

            GC.SuppressFinalize(port);
            GC.SuppressFinalize(internalSerialStream);

            ShutdownEventLoopHandler(internalSerialStream, logger);

            try
            {
                logger.Debug("serial-port-debug", "Disposing internal serial stream");
                internalSerialStream.Close();
            }
            catch(Exception ex)
            {
                logger.Debug("serial-port-debug", String.Format("Exception in serial stream shutdown of port {0}: ", port.PortName), ex);
            }

            try
            {
                logger.Debug("serial-port-debug", "Disposing serial port");
                port.Close();
            }
            catch(Exception ex)
            {
                logger.Debug("serial-port-debug", String.Format("Exception in port {0} shutdown: ", port.PortName), ex);
            }

        }
        catch(Exception ex)
        {
            logger.Debug("serial-port-debug", String.Format("Exception in port {0} shutdown: ", port.PortName), ex);
        }
    }

    static void ShutdownEventLoopHandler(Stream internalSerialStream, Logger logger)
    {
        try
        {
            logger.Debug("serial-port-debug", "Working around .NET SerialPort class Dispose bug");

            FieldInfo eventRunnerField = internalSerialStream.GetType()
                .GetField("eventRunner", BindingFlags.NonPublic | BindingFlags.Instance);

            if(eventRunnerField == null)
            {
                logger.Warning("serial-port-debug",
                    "Unable to find EventLoopRunner field. "
                    + "SerialPort workaround failure. Application may crash after "
                    + "disposing SerialPort unless .NET 1.1 unhandled exception "
                    + "policy is enabled from the application's config file.");
            }
            else
            {
                object eventRunner = eventRunnerField.GetValue(internalSerialStream);
                Type eventRunnerType = eventRunner.GetType();

                FieldInfo endEventLoopFieldInfo = eventRunnerType.GetField(
                    "endEventLoop", BindingFlags.Instance | BindingFlags.NonPublic);

                FieldInfo eventLoopEndedSignalFieldInfo = eventRunnerType.GetField(
                    "eventLoopEndedSignal", BindingFlags.Instance | BindingFlags.NonPublic);

                FieldInfo waitCommEventWaitHandleFieldInfo = eventRunnerType.GetField(
                    "waitCommEventWaitHandle", BindingFlags.Instance | BindingFlags.NonPublic);

                if(endEventLoopFieldInfo == null
                   || eventLoopEndedSignalFieldInfo == null
                   || waitCommEventWaitHandleFieldInfo == null)
                {
                    logger.Warning("serial-port-debug",
                        "Unable to find the EventLoopRunner internal wait handle or loop signal fields. "
                        + "SerialPort workaround failure. Application may crash after "
                        + "disposing SerialPort unless .NET 1.1 unhandled exception "
                        + "policy is enabled from the application's config file.");
                }
                else
                {
                    logger.Debug("serial-port-debug",
                        "Waiting for the SerialPort internal EventLoopRunner thread to finish...");

                    var eventLoopEndedWaitHandle =
                        (WaitHandle)eventLoopEndedSignalFieldInfo.GetValue(eventRunner);
                    var waitCommEventWaitHandle =
                        (ManualResetEvent)waitCommEventWaitHandleFieldInfo.GetValue(eventRunner);

                    endEventLoopFieldInfo.SetValue(eventRunner, true);

                    // Sometimes the event loop handler resets the wait handle
                    // before exiting the loop and hangs (in case of USB disconnect)
                    // In case it takes too long, brute-force it out of its wait by
                    // setting the handle again.
                    do
                    {
                        waitCommEventWaitHandle.Set();
                    } while(!eventLoopEndedWaitHandle.WaitOne(2000));

                    logger.Debug("serial-port-debug", "Wait completed. Now it is safe to continue disposal.");
                }
            }
        }
        catch(Exception ex)
        {
            logger.Warning("serial-port-debug",
                "SerialPort workaround failure. Application may crash after "
                + "disposing SerialPort unless .NET 1.1 unhandled exception "
                + "policy is enabled from the application's config file: " +
                ex);
        }
    }
}

public class SerialPortFixer : IDisposable
{
    #region IDisposable Members

    public void Dispose()
    {
        if(m_Handle != null)
        {
            m_Handle.Close();
            m_Handle = null;
        }
    }

    #endregion

    #region Implementation

    private const int DcbFlagAbortOnError = 14;
    private const int CommStateRetries = 10;
    private SafeFileHandle m_Handle;

    internal SerialPortFixer(string portName)
    {
        const int dwFlagsAndAttributes = 0x40000000;
        const int dwAccess = unchecked((int)0xC0000000);

        if((portName == null) || !portName.StartsWith("COM", StringComparison.OrdinalIgnoreCase))
        {
            throw new ArgumentException("Invalid Serial Port", "portName");
        }
        SafeFileHandle hFile = CreateFile(@"\\.\" + portName, dwAccess, 0, IntPtr.Zero, 3, dwFlagsAndAttributes,
            IntPtr.Zero);
        if(hFile.IsInvalid)
        {
            WinIoError();
        }
        try
        {
            int fileType = GetFileType(hFile);
            if((fileType != 2) && (fileType != 0))
            {
                throw new ArgumentException("Invalid Serial Port", "portName");
            }
            m_Handle = hFile;
            InitializeDcb();
        }
        catch
        {
            hFile.Close();
            m_Handle = null;
            throw;
        }
    }

    [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    private static extern int FormatMessage(int dwFlags, HandleRef lpSource, int dwMessageId, int dwLanguageId,
        StringBuilder lpBuffer, int nSize, IntPtr arguments);

    [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    private static extern bool GetCommState(SafeFileHandle hFile, ref Dcb lpDcb);

    [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    private static extern bool SetCommState(SafeFileHandle hFile, ref Dcb lpDcb);

    [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    private static extern bool ClearCommError(SafeFileHandle hFile, ref int lpErrors, ref Comstat lpStat);

    [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    private static extern SafeFileHandle CreateFile(string lpFileName, int dwDesiredAccess, int dwShareMode,
        IntPtr securityAttrs, int dwCreationDisposition,
        int dwFlagsAndAttributes, IntPtr hTemplateFile);

    [DllImport("kernel32.dll", SetLastError = true)]
    private static extern int GetFileType(SafeFileHandle hFile);

    private void InitializeDcb()
    {
        Dcb dcb = new Dcb();
        GetCommStateNative(ref dcb);
        dcb.Flags &= ~(1u << DcbFlagAbortOnError);
        SetCommStateNative(ref dcb);
    }

    private static string GetMessage(int errorCode)
    {
        StringBuilder lpBuffer = new StringBuilder(0x200);
        if(
            FormatMessage(0x3200, new HandleRef(null, IntPtr.Zero), errorCode, 0, lpBuffer, lpBuffer.Capacity,
                IntPtr.Zero) != 0)
        {
            return lpBuffer.ToString();
        }
        return "Unknown Error";
    }

    private static int MakeHrFromErrorCode(int errorCode)
    {
        return (int)(0x80070000 | (uint)errorCode);
    }

    private static void WinIoError()
    {
        int errorCode = Marshal.GetLastWin32Error();
        throw new IOException(GetMessage(errorCode), MakeHrFromErrorCode(errorCode));
    }

    private void GetCommStateNative(ref Dcb lpDcb)
    {
        int commErrors = 0;
        Comstat comStat = new Comstat();

        for(int i = 0; i < CommStateRetries; i++)
        {
            if(!ClearCommError(m_Handle, ref commErrors, ref comStat))
            {
                WinIoError();
            }
            if(GetCommState(m_Handle, ref lpDcb))
            {
                break;
            }
            if(i == CommStateRetries - 1)
            {
                WinIoError();
            }
        }
    }

    private void SetCommStateNative(ref Dcb lpDcb)
    {
        int commErrors = 0;
        Comstat comStat = new Comstat();

        for(int i = 0; i < CommStateRetries; i++)
        {
            if(!ClearCommError(m_Handle, ref commErrors, ref comStat))
            {
                WinIoError();
            }
            if(SetCommState(m_Handle, ref lpDcb))
            {
                break;
            }
            if(i == CommStateRetries - 1)
            {
                WinIoError();
            }
        }
    }

    #region Nested type: COMSTAT

    [StructLayout(LayoutKind.Sequential)]
    private struct Comstat
    {
        public readonly uint Flags;
        public readonly uint cbInQue;
        public readonly uint cbOutQue;
    }

    #endregion

    #region Nested type: DCB

    [StructLayout(LayoutKind.Sequential)]
    private struct Dcb
    {
        public readonly uint DCBlength;
        public readonly uint BaudRate;
        public uint Flags;
        public readonly ushort wReserved;
        public readonly ushort XonLim;
        public readonly ushort XoffLim;
        public readonly byte ByteSize;
        public readonly byte Parity;
        public readonly byte StopBits;
        public readonly byte XonChar;
        public readonly byte XoffChar;
        public readonly byte ErrorChar;
        public readonly byte EofChar;
        public readonly byte EvtChar;
        public readonly ushort wReserved1;
    }

    #endregion

    #endregion
}

该代码的用法:

public void Connect(ConnectionParameters parameters)
{
    if(m_params != null && m_params.Equals(parameters))
        return; //already connected here

    Close();

    m_params = parameters;

    try
    {
        m_comConnection = new SerialPort();
        m_comConnection.PortName = m_params.Address;
        m_comConnection.BaudRate = m_params.BaudRate;
        m_comConnection.ReadTimeout = 6000;
        m_comConnection.WriteTimeout = 6000;
        m_comConnection.Parity = m_params.Parity;
        m_comConnection.StopBits = m_params.StopBits;
        m_comConnection.DataBits = m_params.DataBits;
        m_comConnection.Handshake = m_params.Handshake;
        m_comConnection.SafeOpen();
    }
    catch(Exception)
    {
        m_params = null;
        m_comConnection = null;
        throw;
    }
}

public void Close()
{
    if(m_params == null)
        return;

    m_comConnection.SafeClose(new Logger());
    m_comConnection = null;

    m_params = null;
}

那么我做错了什么?如果我的日志看起来不像那样GC.SuppressFinalize()没有被处决。什么可以使Finalize()即使在GC.SuppressFinalize()?

Edit

我正在添加我使用的代码SerialPort端口打开和关闭之间。

    public byte[] ExchangeFrame(byte[] prefix, byte[] suffix, byte[] message, int length = -1)
    {
        if(m_params == null)
            throw new NotConnectedException();

        if(length == -1)
            length = message.Length;

        Write(prefix, 0, prefix.Length);
        Write(message, 0, length);
        Write(suffix, 0, suffix.Length);

        byte[] response = new byte[length];
        byte[] prefixBuffer = new byte[prefix.Length];
        byte[] suffixBuffer = new byte[suffix.Length];


        ReadTillFull(prefixBuffer);

        while(!ByteArrayEquals(prefixBuffer, prefix))
        {
            for(int i = 0; i < prefixBuffer.Length - 1; i++)
                prefixBuffer[i] = prefixBuffer[i + 1]; //shift them all back

            if(Read(prefixBuffer, prefixBuffer.Length - 1, 1) == 0) //read one more
                throw new NotConnectedException("Received no data when reading prefix.");
        }

        ReadTillFull(response);
        ReadTillFull(suffixBuffer);

        if(!ByteArrayEquals(suffixBuffer, suffix))
            throw new InvalidDataException("Frame received matches prefix but does not match suffix. Response: " + BitConverter.ToString(prefixBuffer) + BitConverter.ToString(response) + BitConverter.ToString(suffixBuffer));

        return response;
    }

    private void ReadTillFull(byte[] buffer, int start = 0, int length = -1)
    {
        if(length == -1)
            length = buffer.Length;

        int remaining = length;

        while(remaining > 0)
        {
            int read = Read(buffer, start + length - remaining, remaining);

            if(read == 0)
                throw new NotConnectedException("Received no data when reading suffix.");

            remaining -= read;
        }
    }

    //this method looks dumb but actually, the real code has a switch statement to check if should connect by TCP or Serial
    private int Read(byte[] buffer, int start, int length)
    {
        return  m_comConnection.Read(buffer, start, length); 
    }

    private void Write(byte[] buffer, int start, int length)
    {
        m_comConnection.Write(buffer, start, length);
    }

    [DllImport("msvcrt.dll", CallingConvention = CallingConvention.Cdecl)]
    static extern int memcmp(byte[] b1, byte[] b2, long count);

    static bool ByteArrayEquals(byte[] b1, byte[] b2)
    {
        // Validate buffers are the same length.
        // This also ensures that the count does not exceed the length of either buffer.  
        return b1.Length == b2.Length && memcmp(b1, b2, b1.Length) == 0;
    }

目前,我只是通过打开、写入、读取和关闭端口来寻找健康的通信。我将不得不处理端口被物理拔出的情况。(最终)

请注意,我正在使用ExchangeFrame方法连续多次(在循环中)。经过更多测试,我发现使用它一次不会产生任何错误。 (发送一个帧)我想这是我的代码的问题,但我不确定我在做什么可以使SerialPort多次执行时中断。

顺便说一句,我还有另一个问题,我时不时地在读取的帧中收到大量 0。 (没有注意到模式)我不知道为什么,但我知道这是此代码中出现的一个新错误。不是问题的一部分,但可能相关。

Edit 2

该问题是由我自己中断串行端口引起的Thread.Interrupt()。我正确处理了ThreadInterruptedException但它在内部破坏了串行端口。这是导致问题的代码:

    public void Stop()
    {
        m_run = false;

        if(m_acquisitor != null)
        {
            m_acquisitor.Interrupt();
            m_acquisitor.Join();
        }

        m_acquisitor = null;
    }

    private void Run()
    {
        while(m_run)
        {
            long time = DateTime.Now.Ticks, done = -1;
            double[] data;
            try
            {
                lock(Connection)
                {
                    if(!m_run)
                        break; //if closed while waiting for Connection

                    Connection.Connect(Device.ConnectionParams);

                    data = Acquire();
                }

                done = DateTime.Now.Ticks;
                Ping = (done - time) / TimeSpan.TicksPerMillisecond;
                Samples++;

                if(SampleReceived != null)
                    SampleReceived(this, data);

            }
            catch(ThreadInterruptedException)
            {
                continue; //checks if m_run is true, if not quits
            }

            try
            {
                if(done == -1)
                    Thread.Sleep(RETRY_DELAY); //retry delay
                else
                    Thread.Sleep((int)Math.Max(SamplingRate * 1000 - (done - time) / TimeSpan.TicksPerMillisecond, 0));
            }
            catch(ThreadInterruptedException) {} //continue
        }

        lock(Connection)
            Connection.Close(); //serialport's wrapper

    }

巨大的感谢Snoopy https://stackoverflow.com/users/2152513/snoopy谁帮我解决了聊天中的问题。


None

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

SerialPort 的 Finalize() 在 GC.SuppressFinalize() 之后仍然调用 的相关文章

  • 地图类容器的专用功能

    我想要专门为矢量和地图之类的容器设计一个函数模板 对于向量 我可以像下面那样做 但我不知道如何才能有一个专门版本的函数 该函数仅用于像地图这样的容器 include
  • MVVM:来自 FileOpenPicker 的图像绑定源

    我将 OnActivated 添加到 app xaml cs 中 它可以正常工作 protected async override void OnActivated IActivatedEventArgs args var continua
  • 字符串/分段错误

    Program to calculate trip and plan flights define TRIP 6 define NAMEMAX 40 define DEST 1 include
  • 二叉树和快速排序?

    我有一个家庭作业 内容如下 别生气 担心 我是not请你帮我做作业 编写一个程序 通过使用二分查找的快速排序方法对一组数字进行排序 树 推荐的实现是使用递归算法 这是什么意思 到目前为止 这是我的解释 正如我在下面解释的那样 我认为两者都有
  • 如何在建立上下文时设置连接超时-PrincipalContext

    using PrincipalContext ctx new PrincipalContext ContextType Domain Domain UserName Password UserPrincipal U new UserPrin
  • 使用c#在mac上启动外部进程

    我成功地使用 System Diagnostics Process Start 在 Windows 上启动我的外部单声道可执行文件 然而在mac上却失败了 我没有收到任何错误 只是什么也没发生 我尝试按以下方式进行操作 System Dia
  • 在编译输出中添加程序集绑定 (app.config)

    如果我编译应用程序 则会在输出中自动添加程序集绑定 具体的程序集绑定不在app config在 Visual Studio 中但在创建的应用程序配置中 有什么办法可以检查为什么会自动添加程序集绑定吗 选项AutoGenerateBindin
  • 我应该使用字节还是int?

    我记得曾在某处读到 即使您只需要字节 使用 Int32 更好 就性能而言 它 据说 仅适用于您不关心存储的情况 这是有效的吗 例如 我需要一个保存一周中某一天的变量 我是吗 int dayOfWeek or byte dayOfWeek E
  • 绑定集合的子集

    我有一个ObservableCollection
  • 大小为 k 的非连续子序列的最大值的最小值

    在开始之前 我希望这个问题不是重复的 我发现了几个类似的问题 但它们似乎都没有描述完全相同的问题 但如果它是重复的 我会很高兴看到一个解决方案 即使它与我的算法不同 我一直在尝试回答这个问题 https stackoverflow com
  • 现代编译器的 C++ 中“memset”功能的状态

    Context 不久前 我偶然发现了 Alexandrescu 在 2001 年发表的 DDJ 文章 http www ddj com cpp 184403799 http www ddj com cpp 184403799 它是关于比较将
  • 如何在C++中列出Python模块的所有函数名称?

    我有一个 C 程序 我想导入一个 Python 模块并列出该模块中的所有函数名称 我该怎么做 我使用以下代码从模块中获取字典 PyDictObject pDict PyDictObject PyModule GetDict pModule
  • 对象变空似乎是 Hangfire 中的反序列化问题

    Hangfire 似乎无法反序列化我的原始版本Scheduler对象及其所有状态 我正在调用其 Execute 方法BackgroundJob Enqueue 如下所示 Scheduler new FileInFileOut FileIn
  • 对列表中的一系列整数求和

    假设我有一个这样的列表 List
  • 模板类中模板方法专门化的 clang 自动返回类型错误?

    试图理解另一个问题 https stackoverflow com questions 38054055 clang fails to compile template function with auto return type insi
  • 结构大小与 typedef 版本不同?

    我的代码中有以下结构声明和 typedef struct blockHeaderStruct bool allocated unsigned int length typedef struct blockHeaderStruct block
  • C# 中的 mshtml.HTMLDocumentClass

    在 C 中 我设法从 InternetExplorer 对象获取整个 HTMLDocumentClass 导航到某个 URL 然而 在 Visual Studio 2008 的调试模式下 该特定 URL 的 HTMLDocumentClas
  • 使用属性和性能

    我正在优化我的代码 我注意到使用属性 甚至自动属性 对执行时间有深远的影响 请参阅下面的示例 Test public void GetterVsField PropertyTest propertyTest new PropertyTest
  • 将“C# 友好类型”名称转换为实际类型:“int” => typeof(int)

    我想得到一个System Type给定一个string指定 原始 类型C 友好名称 基本上与 C 编译器读取 C 源代码时的方式相同 我觉得描述我所追求的最好方式是单元测试的形式 我希望存在一种通用技术 可以使以下所有断言通过 而不是尝试对
  • 为什么在一行中使用这个 C++ 函数两次会导致编译错误?

    我在尝试在 Visual C 2010 中实现智能相等测试宏类型模板函数时遇到了一些麻烦 该函数与VS 中关于模板函数默认参数的错误 https stackoverflow com questions 10343177 why do i g

随机推荐