在 .Net 2.0 中关闭 SerialPort 时出现 ObjectDisposedException [英] ObjectDisposedException when closing SerialPort in .Net 2.0

查看:25
本文介绍了在 .Net 2.0 中关闭 SerialPort 时出现 ObjectDisposedException的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I have a C# windows forms application which communicates with a USB dongle via a COM port. I am using the SerialPort class in .Net 2.0 for communication, and the serial port object is open for the lifetime of the application. The application sends commands to the device and can also receive unsolicited data from the device.

我的问题在表单关闭时出现 - 我在尝试关闭 COM 端口时(随机,不幸地)收到 ObjectDisposedException.这是 Windows 堆栈跟踪:

My problem occurs when the form is closed - I get (randomly, unfortunately) an ObjectDisposedException when attempting to close the COM port. Here is the Windows stack trace:

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]

I have found posts from people with similar problems and have tried the workaround [here][1]

[1]: http://zachsaw.blogspot.com/2010/07/net-serialport-woes.html 虽然这是针对 IOException 并没有阻止问题.

[1]: http://zachsaw.blogspot.com/2010/07/net-serialport-woes.html although that is for an IOException and did not stop the problem.

我的 Close() 代码如下:

My Close() code is as follows:

        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 的 BaseStream(这是在第一个 try 块中),所以我尝试删除这一行,但仍然定期抛出异常 -登录第二个 try 块出现然后异常发生.两个 catch 块都没有捕获异常.

My logging showed that execution never got beyond attempting to close the SerialPort's BaseStream (this is in the first try block), so I experimented with removing this line but the exception is still thrown periodically - the logging in the second try block appeared then the exception happened. Neither catch block catches the exception.

有什么想法吗?

更新 - 添加完整类:

UPDATE - adding full class:

    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 + "
");
            }
            catch (Exception ex)
            {
                Logger.Log(TraceLevel.Error, "ComPort.WriteData raised exception: " + ex);                

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

推荐答案

注意:目前的发现仅在 Windows 7 的 .NET Framework 4.0 32-bit 上测试过,如果适用,请随时评论其他版本.

TL;DR:这是解决方法的关键.请参阅下面的说明.在打开 SerialPort 时不要忘记使用 SerialPortFixer.ILog 来自 log4net.

TL;DR: Here's the crux of the workaround. See below for explanation. Don't forget to use SerialPortFixer when opening the SerialPort as well. ILog is from 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);
    }
}

在最近的一个项目中,我已经为此苦苦挣扎了几天.

I've wrestled with this for a couple of days in a recent project.

.NET SerialPort 类有许多不同的错误(到目前为止我已经看到),这些错误导致了网络上的所有问题.

There are many different bugs (I've seen so far) with the .NET SerialPort class that lead to all of the headaches on the web.

  1. 此处缺少 DCB 结构标志:http://zachsaw.blogspot.com/2010/07/net-serialport-woes.html这个是由 SerialPortFixer 类修复的,这个功劳归作者所有.

  1. The missing DCB struct flag here: http://zachsaw.blogspot.com/2010/07/net-serialport-woes.html This one is fixed by the SerialPortFixer class, credits go to the author for that one.

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

When a USB serial device is removed, when closing the SerialPortStream, the eventLoopRunner is asked to stop and SerialPort.IsOpen returns false. Upon disposal this property is checked and closing the internal serial stream is skipped, thus keeping the original handle open indefinitely (until the finalizer runs which leads to the next problem).

这个解决办法是手动关闭内部串口流.我们可以在异常发生之前通过 SerialPort.BaseStream 或者通过反射来获取它的引用并获取internalSerialStream"字段.

The solution for this one is to manually close the internal serial stream. We can get its reference by SerialPort.BaseStream before the exception has happened or by reflection and getting the "internalSerialStream" field.

当移除 USB 串行设备时,关闭内部串行流会引发异常并关闭内部句柄而不等待其 eventLoopRunner 线程完成,从而导致稍后从后台事件循环运行器线程中出现无法捕获的 ObjectDisposedException流的终结器运行(奇怪地避免抛出异常但仍然无法等待 eventLoopRunner).

When a USB serial device is removed, closing the internal serial stream throws an exception and closes the internal handle without waiting for its eventLoopRunner thread to finish, causing an uncatchable ObjectDisposedException from the background event loop runner thread later on when the stream's finalizer runs (which oddly avoids throwing the exception but still fails to wait for the eventLoopRunner).

这里的症状:https://connect.microsoft.com/VisualStudio/feedback/details/140018/serialport-crashes-after-disconnect-of-usb-com-port

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

The solution is to manually ask the event loop runner to stop (via reflection) and waiting for it to finish before closing the internal serial stream.

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

Since Dispose throws exceptions, the finalizer is not suppressed. This is easily solvable:

GC.SuppressFinalize(端口);GC.SuppressFinalize(port.BaseStream);

GC.SuppressFinalize(port); GC.SuppressFinalize(port.BaseStream);

这是一个封装串行端口并修复所有这些问题的类:http://pastebin.com/KmKEVzR8

有了这个变通方法类,恢复到 .NET 1.1 未处理的异常行为是不必要的,它具有出色的稳定性.

With this workaround class, reverting to .NET 1.1 unhandled exception behavior is unnecessary and it works with excellent stability.

这是我的第一个贡献,如果我做得不对,请原谅.我希望它可以帮助某人.

This is my first contribution, so please excuse me if I'm not doing it right. I hope it helps someone.

这篇关于在 .Net 2.0 中关闭 SerialPort 时出现 ObjectDisposedException的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆