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

查看:140
本文介绍了在.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.

我的问题是在窗体关闭时发生的-我(随机地,不幸地)得到了ObjectDisposedException尝试关闭COM端口时。这是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: 

我发现了存在类似问题的人的帖子,并尝试了变通方法[here] [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 块中),因此我尝试删除了这一行,但仍会定期抛出异常-在第二个<$ c $中进行记录c> try 块出现,然后发生异常。

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.

有什么想法吗?

更新-添加完整的类:

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


推荐答案

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

编辑: TL; DR:
这是解决方法的症结所在。请参阅下面的说明。
打开时不要忘记使用 SerialPortFixer 以及SerialPort。
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
    该问题由

  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(port);
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天全站免登陆