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() 之后仍然调用 的相关文章

  • 并行化斐波那契序列生成器

    我正在学习并行化 在一项练习中 我得到了一些我应该提高性能的算法 其中之一是斐波那契数列生成器 array 0 0 array 1 1 for q 2 q lt MAX q array q array q 1 array q 2 我怀疑 这
  • 将设置函数(setter)标记为 constexpr 的目的是什么? [复制]

    这个问题在这里已经有答案了 我无法理解将 setter 函数标记为的目的constexpr 自 C 14 起这是允许的 我的误解来自以下情况 我使用 constexpr c tor 声明一个类 并且我将通过创建该类的 constexpr 实
  • 在 ASP.NET MVC 中将模型从视图传递到控制器

    我正在 ASP NET MVC 中开发我的第一个应用程序 但遇到了一个我无法解决的问题 即使在阅读了整个互联网之后也是如此 因此 我有几个使用视图模型创建的视图 它们是报告 这些视图模型是根据用户选择标准填充的 我正在尝试构建一种接受模型并
  • C# 处理标准输入

    我目前正在尝试通过命令行断开与网络文件夹的连接 并使用以下代码 System Diagnostics Process process2 new System Diagnostics Process System Diagnostics Pr
  • 为什么需要数字后缀?

    C 语言 我确信还有其他语言 需要在数字文字末尾添加后缀 这些后缀指示文字的类型 例如 5m是一个小数 5f是一个浮点数 我的问题是 这些后缀真的有必要吗 或者是否可以从上下文中推断出文字的类型 例如 代码decimal d 5 0应该推断
  • 在 C# 中何时使用 ArrayList 而不是 array[]?

    我经常使用一个ArrayList而不是 正常 array 当我使用时 我感觉好像我在作弊 或懒惰 ArrayList 什么时候可以使用ArrayList在数组上 数组是强类型的 并且可以很好地用作参数 如果您知道集合的长度并且它是固定的 则
  • 通过 C# Mailkit / Mimekit 发送电子邮件,但出现服务器证书错误

    Visual Studio 2015 中的 0 代码 1 我正在使用 Mailkit 最新版本 1 18 1 1 从我自己的电子邮件服务器发送电子邮件 2 电子邮件服务器具有不受信任的自签名证书 3 我在代码中添加了以下两行 以忽略服务器证
  • Resharper:IEnumerable 的可能多重枚举

    我正在使用新的 Resharper 版本 6 在我的代码中的几个地方 它给一些文本加了下划线 并警告我可能存在IEnumerable 可能的多重枚举 我理解这意味着什么 并在适当的情况下采纳了建议 但在某些情况下 我不确定这实际上是一个大问
  • 如何在 C# 中获取 Json 数组?

    我有一个像这样的 Json 字符串 我想将它加载到 C 数组中 当我尝试这样做时 我收到异常 我的字符串 customerInformation customerId 123 CustomerName Age 39 Gender Male
  • C++ 到 C# 事件处理

    所以我有我的C WinForm 应用程序 我从中调用我的C CLI MFC dll图书馆 但也有一些events在我的 C 库上 甚至此事件也发生在该库的本机 非 CLI 部分 我需要从我的 C 应用程序调用一些代码 并获取一些有关此事件的
  • 如何使用 CSI.exe 脚本参数

    当你运行csi exe 安装了 Visual Studio 2015 update 2 您将得到以下语法 Microsoft R Visual C Interactive Compiler version 1 2 0 51106 Copyr
  • C 中使用 getrandom 实现随机浮点数

    我试图生成一个介于 0 和 1 之间的随机浮点数 无论是在 0 1 还是 0 1 对我来说都不重要 网上关于此的每个问题似乎都涉及rand 呼叫 播种time NULL 但我希望能够每秒多次调用我的程序 并每次都获得不同的随机数 这引导我找
  • rabbitmq 的 REST API

    有没有办法从 ajax 向 RabbitMQ 发送数据 我的应用程序由数千个 Web 客户端 用 js 编写 和 WCF REST 服务组成 现在我试图弄清楚如何为我的应用程序创建可扩展点 这个想法是有一个rabbitmq实例 它从放置在一
  • 浮点字节序?

    我正在为实时海上模拟器编写客户端和服务器 并且由于我必须通过套接字发送大量数据 因此我使用二进制数据来最大化可以发送的数据量 我已经了解整数字节顺序以及如何使用htonl and ntohl为了规避字节顺序问题 但我的应用程序与几乎所有模拟
  • 如何在dll级别读取app.config? [复制]

    这个问题在这里已经有答案了 我在一个解决方案中有一个控制台应用程序项目和库项目 dll The 图书馆项目有 app config 文件 我在其中存储我在库中使用的一些键值对 控制台应用程序引用此 dll 我有另一个 app config
  • 如何将 int 作为“void *”传递给线程启动函数?

    我最初有一个用于斐波那契变量数组的全局变量 但发现这是不允许的 我需要进行基本的多线程处理并处理竞争条件 但我无法在 pthread 创建中将 int 作为 void 参数提供 我尝试过使用常量指针 但没有成功 由于某些奇怪的原因 void
  • printf或iostream如何指定点后的最大位数

    字符串采用什么格式printf or iomanip我应该使用 iostream 中的运算符以以下格式打印浮点数 125 0 gt 125 125 1 gt 125 1 125 12312 gt 125 12 1 12345 gt 1 12
  • 将 Swagger 与命名空间版本的 WebApi 结合使用

    我已经找到了如何使用基于名称空间的 WebAPI 版本这个班 https aspnet codeplex com SourceControl changeset view dd207952fa86 Samples WebApi Namesp
  • 如何在c linux中收听特定接口上的广播?

    我目前可以通过执行以下操作来收听我编写的简单广播服务器 仅广播 hello int fd socket PF INET SOCK DGRAM 0 struct sockaddr in addr memset addr 0 sizeof ad
  • 嵌入式二进制资源 - 如何枚举嵌入的图像文件?

    我按照中的说明进行操作这本书 http www apress com book view 9781430225492 关于资源等的章节 我不太明白的是 如何替换它 images Add new BitmapImage new Uri Ima

随机推荐