C# 在.Net 2.0中关闭SerialPort时发生ObjectDisposedException

C# 在.Net 2.0中关闭SerialPort时发生ObjectDisposedException,c#,.net-2.0,serial-port,objectdisposedexception,C#,.net 2.0,Serial Port,Objectdisposedexception,我有一个C#windows窗体应用程序,它通过COM端口与USB加密狗通信。我使用.NET2.0中的SerialPort类进行通信,并且SerialPort对象在应用程序的生命周期内是开放的。应用程序向设备发送命令,也可以从设备接收未经请求的数据 我的问题发生在表单关闭时-我在尝试关闭COM端口时(不幸的是,随机)得到一个ObjectDisposedException。以下是Windows堆栈跟踪: System.ObjectDisposedException was unhandled M

我有一个C#windows窗体应用程序,它通过COM端口与USB加密狗通信。我使用.NET2.0中的SerialPort类进行通信,并且SerialPort对象在应用程序的生命周期内是开放的。应用程序向设备发送命令,也可以从设备接收未经请求的数据

我的问题发生在表单关闭时-我在尝试关闭COM端口时(不幸的是,随机)得到一个ObjectDisposedException。以下是Windows堆栈跟踪:

System.ObjectDisposedException was unhandled


Message=Safe handle has been closed
  Source=System
  ObjectName=""
  StackTrace:
       at Microsoft.Win32.UnsafeNativeMethods.SetCommMask(SafeFileHandle hFile, Int32 dwEvtMask)
       at System.IO.Ports.SerialStream.Dispose(Boolean disposing)
       at System.IO.Ports.SerialStream.Finalize()
  InnerException: 
我从有类似问题的人那里找到了帖子,并尝试了解决方法[这里][1]

[1] :尽管这是针对IOException的,但并没有停止问题

我的Close()代码如下:

        public void Close()
    {
        try
        {
            Console.WriteLine("******ComPort.Close - baseStream.Close*******");
            baseStream.Close();
        }
        catch (Exception ex)
        {
            Console.WriteLine("******ComPort.Close baseStream.Close raised exception: " + ex + "*******");
        }
        try
        {
            _onDataReceived = null;
            Console.WriteLine("******ComPort.Close - _serialPort.Close*******");
            _serialPort.Close();
        }
        catch (Exception ex)
        {
            Console.WriteLine("******ComPort.Close - _serialPort.Close raised exception: " + ex + "*******");
        }            
    }
我的日志显示,除了尝试关闭SerialPort的基流(这是在第一个
try
块中)之外,执行从未停止过,因此我尝试删除这一行,但仍然会定期引发异常-第二个
try
块中的日志记录出现,然后异常发生。两个catch块都不能捕获异常

有什么想法吗

更新-添加完整类:

    namespace My.Utilities
{
    public interface ISerialPortObserver
    {
        void SerialPortWriteException();
    }

    internal class ComPort : ISerialPort
    {
        private readonly ISerialPortObserver _observer;
        readonly SerialPort _serialPort;

        private DataReceivedDelegate _onDataReceived;
        public event DataReceivedDelegate OnDataReceived
        {
            add { lock (_dataReceivedLocker) { _onDataReceived += value; } }
            remove { lock (_dataReceivedLocker) { _onDataReceived -= value; } }            
        }

        private readonly object _dataReceivedLocker = new object();
        private readonly object _locker = new object();

        internal ComPort()
        {         
            _serialPort = new SerialPort { ReadTimeout = 10, WriteTimeout = 100, DtrEnable = true };
            _serialPort.DataReceived += DataReceived;
        }

        internal ComPort(ISerialPortObserver observer) : this()
        {
            _observer = observer;         
        }

        private void DataReceived(object sender, SerialDataReceivedEventArgs e)
        {
            DataReceivedDelegate temp = null;

            lock (_locker)
            {
                lock (_dataReceivedLocker)
                {
                    temp = _onDataReceived;
                }

                string dataReceived = string.Empty;
                var sp = (SerialPort) sender;

                try
                {
                    dataReceived = sp.ReadExisting();
                }
                catch (Exception ex)
                {
                    Logger.Log(TraceLevel.Error, "ComPort.DataReceived raised exception: " + ex);
                }

                if (null != temp && string.Empty != dataReceived)
                {
                    try
                    {
                        temp(dataReceived, TickProvider.GetTickCount());
                    }
                    catch (Exception ex)
                    {
                        Logger.Log(TraceLevel.Error, "ComPort.DataReceived raised exception calling handler: " + ex);
                    }
                }
            }
        }

        public string Port
        {
            set
            {
                try
                {
                    _serialPort.PortName = value;
                }
                catch (Exception ex)
                {
                    Logger.Log(TraceLevel.Error, "ComPort.Port raised exception: " + ex);
                }
            }
        }

        private System.IO.Stream comPortStream = null;
        public bool Open()
        {
            SetupSerialPortWithWorkaround();
            try
            {
                _serialPort.Open();
                comPortStream = _serialPort.BaseStream;
                return true;
            }
            catch (Exception ex)
            {
                Logger.Log(TraceLevel.Warning, "ComPort.Open raised exception: " + ex);
                return false;
            }
        }

        public bool IsOpen
        {
            get
            {
                SetupSerialPortWithWorkaround();
                try
                {
                    return _serialPort.IsOpen;
                }
                catch(Exception ex)
                {
                    Logger.Log(TraceLevel.Error, "ComPort.IsOpen raised exception: " + ex);
                }

                return false;
            }
        }

        internal virtual void SetupSerialPortWithWorkaround()
        {
            try
            {
                //http://zachsaw.blogspot.com/2010/07/net-serialport-woes.html
                // This class is meant to fix the problem in .Net that is causing the ObjectDisposedException.
                SerialPortFixer.Execute(_serialPort.PortName);
            }
            catch (Exception e)
            {
                Logger.Log(TraceLevel.Info, "Work around for .Net SerialPort object disposed exception failed with : " + e + " Will still attempt open port as normal");
            }
        }

        public void Close()
        {
            try
            {
                comPortStream.Close();
            }
            catch (Exception ex)
            {
                Logger.Log(TraceLevel.Error, "ComPortStream.Close raised exception: " + ex);
            }
            try
            {
                _onDataReceived = null;
                _serialPort.Close();
            }
            catch (Exception ex)
            {
                Logger.Log(TraceLevel.Error, "ComPort.Close raised exception: " + ex);
            }            
        }

        public void WriteData(string aData, DataReceivedDelegate handler)
        {
            try
            {
                OnDataReceived += handler;
                _serialPort.Write(aData + "\r\n");
            }
            catch (Exception ex)
            {
                Logger.Log(TraceLevel.Error, "ComPort.WriteData raised exception: " + ex);                

                if (null != _observer)
                {
                    _observer.SerialPortWriteException();
                }
            }
        }
    }    
}

是的,SerialPort类中有一个缺陷,使得这种崩溃成为可能。调用Open()时,SerialPort会启动一个线程。该线程监视端口上的事件,例如,这就是获取DataReceived事件的方式。当调用BaseStream.Close()或Close()或Dispose()方法(它们都做相同的事情)时,SerialPort只要求线程退出,而不等待它退出

这导致了各种各样的问题。一个是有文档记录的一个,关闭端口后不应该立即打开它。但这里的不幸之处在于,在Close()调用之后,程序立即退出或垃圾收集。它运行终结器并尝试关闭句柄。它仍处于打开状态,因为工作线程仍在使用它。线程竞赛现在是可能的,这是没有正确联锁。当工作线程设法关闭句柄并在终结器线程尝试执行相同操作之前退出时,就会发生kaboom。异常是不可调度的,因为它发生在终结器线程中,CLR中止程序

自2.0以来,每个版本的.NET都对类进行了一些小的更改,以解决串行端口问题。到目前为止,如果您仍在.NET 2.0上,最好的做法是不要实际调用Close()。不管怎样,它是自动发生的,终结器会处理它。即使由于某种原因(硬崩溃或程序中止)没有发生这种情况,Windows也会确保端口关闭。

注意:当前的发现仅在Windows 7上的.NET Framework 4.0 32位上进行了测试,如果它在其他版本上有效,请随时发表评论

编辑:TL;博士: 这是解决办法的关键。请参见下面的解释。 打开串行端口时也不要忘记使用。 ILog来自log4net

static readonly ILog s_Log = LogManager.GetType("SerialWorkaroundLogger");

static void SafeDisconnect(SerialPort port, Stream internalSerialStream)
{
    GC.SuppressFinalize(port);
    GC.SuppressFinalize(internalSerialStream);

    ShutdownEventLoopHandler(internalSerialStream);

    try
    {
        s_Log.DebugFormat("Disposing internal serial stream");
        internalSerialStream.Close();
    }
    catch (Exception ex)
    {
        s_Log.DebugFormat(
            "Exception in serial stream shutdown of port {0}: {1}", port.PortName, ex);
    }

    try
    {
        s_Log.DebugFormat("Disposing serial port");
        port.Close();
    }
    catch (Exception ex)
    {
        s_Log.DebugFormat("Exception in port {0} shutdown: {1}", port.PortName, ex);
    }
}

static void ShutdownEventLoopHandler(Stream internalSerialStream)
{
    try
    {
        s_Log.DebugFormat("Working around .NET SerialPort class Dispose bug");

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

        if (eventRunnerField == null)
        {
            s_Log.WarnFormat(
                "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)
            {
                s_Log.WarnFormat(
                    "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
            {
                s_Log.DebugFormat(
                    "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));

                s_Log.DebugFormat("Wait completed. Now it is safe to continue disposal.");
            }
        }
    }
    catch (Exception ex)
    {
        s_Log.ErrorFormat(
            "SerialPort workaround failure. Application may crash after "
            + "disposing SerialPort unless .NET 1.1 unhandled exception "
            + "policy is enabled from the application's config file: {0}",
            ex);
    }
}
在最近的一个项目中,我花了几天的时间来解决这个问题

NET SerialPort类中有许多不同的bug(到目前为止我已经看到了),它们导致了web上所有令人头痛的问题

  • 此处缺少DCB结构标志: 这一个由SerialPortFixer类修复,该类的学分归作者所有

  • 移除USB串行设备时,关闭SerialPortStream时,将要求eventLoopRunner停止,SerialPort.IsOpen返回false。处理后,将检查此属性并跳过关闭内部串行流,从而无限期地保持原始句柄打开(直到终结器运行导致下一个问题)

    这个问题的解决方案是手动关闭内部串行流。我们可以在异常发生之前通过SerialPort.BaseStream或通过反射获取其引用 以及获取“internalSerialStream”字段

  • 移除USB串行设备时,关闭内部串行流会引发异常并关闭内部句柄,而无需等待其eventLoopRunner线程完成,从而在流的终结器运行时导致后台事件循环运行器线程发生不可调度的ObjectDisposedException(奇怪的是,它避免了抛出异常,但仍然无法等待eventLoopRunner)

    这里的症状是:

    解决方案是手动请求事件循环运行器停止(通过反射),并在关闭内部串行流之前等待它完成

  • 由于Dispose会引发异常,因此不会抑制终结器。这很容易解决:

    总承包商(港口); GC.SuppressFinalize(端口基流)

  • 下面是一个封装串行端口并修复所有这些问题的类:

    使用此解决方案类,不需要恢复到.NET 1.1未经处理的异常行为,并且其工作稳定性非常好


    这是我的第一篇文章,如果我做得不对,请原谅。我希望它能帮助别人。

    我知道这是一个很老的问题,但也是当前的问题。 我最近遇到了这个问题,在寻找解决方案之后,根据发行说明,.NETFramework 4.7似乎终于解决了这个问题。

    修复了SerialPort中的一个问题,在执行过程中拔下设备可能会导致SerialStream类中的内存泄漏。[288363]


    您似乎正在“泄漏”(而不是关闭或处理)
    SerialStream
    类的实例(因为在堆栈跟踪中调用了
    SerialStream.Finalize
    ),我建议这是一个问题,但是要确定这与您当前的问题有什么关系,需要更多的信息。感谢您的回复。哪些信息将有助于确定问题?包含上述
    Close
    方法的整个类将有帮助。此错误纯粹是癌症,MS仍然没有修复此问题sue现在在2017年谢谢Hans,这很有帮助。我已经添加了完整的clas