GC、Dispose、Unmanaged Resource 和 Managed Resource

0 阅读5分钟

1.遇到的问题

跑代码时使用using时出现问题

using (OpenFileDialog openFileDialog = new OpenFileDialog())

报错
using 语句中使用的类型必须可隐式转换为“System.IDisposable“
代码是昨天写在winform中直接复制过来的,在WPF中运用报错。查看了OpenFileDialog的继承和实现

winform:中OpenFileDialog 继承链:OpenFileDialogFileDialogCommonDialogComponentMarshalByRefObject, IComponent, IDisposableobject
winform是通过using System.ComponentModel来实现继承IDisposable。

    public event EventHandler Disposed
        {
            add
            {
                Events.AddHandler(EventDisposed, value);
            }
            remove
            {
                Events.RemoveHandler(EventDisposed, value);
            }
        }

        ~Component()
        {
            Dispose(disposing: false);
        }

        public void Dispose()
        {
            Dispose(disposing: true);
            GC.SuppressFinalize(this);
        }

        protected virtual void Dispose(bool disposing)
        {
            if (!disposing)
            {
                return;
            }

            lock (this)
            {
                if (site != null && site.Container != null)
                {
                    site.Container.Remove(this);
                }

                if (events != null)
                {
                    ((EventHandler)events[EventDisposed])?.Invoke(this, EventArgs.Empty);
                }
            }
        }

WPF中OpenFileDialogFileDialogCommonDialog Microsoft.Win32下的公共抽象类CommonDialog并未继承IDisposable。其下 private sealed class VistaDialogEvents继承了IDisposable

             void IDisposable.Dispose()
            {
                _dialog.Unadvise(_eventCookie);
            }

以上代码:dialog是COM组件,属于非托管资源,调用Unadvise()释放资源,_eventCookie 是注册事件时的Handle类似号码牌,通过这个标识来Unadvise()相应的资源。

using 语句是 try-finally 块的语法糖,确保 Dispose 一定会被调用。使用using语句中调用了非托管的资源file(OS Handle),GC没办法得知其存在(只能得知存储在堆上的资源),所以没办法释放它,必须使用Disposable()进行释放。

// 语法糖
using (var resource = new MyResource())
{
    // 使用资源
}

// 编译器实际生成的代码
var resource = new MyResource();
try
{
    // 使用资源
}
finally
{
    if (resource != null)
    {
        ((IDisposable)resource).Dispose();
    }
}

当类实现了 IDisposable,使用时务必包裹在 using 块中,反之亦然。查看了OpenFileDialog类的继承和实现,并未实现释放句柄的接口IDisposable,所以使用using时报错了。
解决方法:手动实现Dispose()?

            //finally
            //{
            //    ((IDisposable)openFileDialog).Dispose();
            //}

报错了,无法将类型“Microsoft.Win32.OpenFileDialog”转换为“System.IDisposable” 是因为private的限制以及是显示实现IDisposable吗? 直接放弃Dispose() ,由GC的finalizer回收,运行成功。

2.Unmanaged Resource 和 Managed Resource、OS Handle

托管资源managed resource: 由CLR(Common Language Runtime )控制,储存在托管堆Managed Heap上,包括:字符串、集合、自定义类等,由GC(Garbage Collector)控制回收。回收时机是非确定的(None_deterministic),即系统空闲或内存不足时回收(前提是对象不再被引用)
非托管资源Unmanaged Resource:由操作系统OS控制,主要包括OS Handle、数据库(SQL等)网络连接(Socket:TCP/IP)、COM类,无法由GC自由释放,由Disposable手动释放,释放方式是确定性的。(Deterministic_CleanUp

OS Handle 系统句柄

OS Handle直接由操作系统内核管理,本质是个int或者IntPtr(指针)。主要是用来标识和管理资源(文件、进程、线程、window、socket)(类似号码牌),应用程序是不允许直接访问操作系统的核心资源的,想要进行交互,必须通过句柄间通过操作系统间接操作。

比喻:就像相亲,操作系统是中间人,应用程序(女)领一个号码牌,像中间人询问想要接触的号码牌(男),中间人通过号码牌找到程序要的男方,询问女想要的信息,然后中间人再把信息资源带回去给女方应用程序,然后他俩结束,收回应用程序中拥有的号码牌。(释放句柄——防止资源占用:其他人也要了解男方,以及资源堵塞:还有很多女方在排队呢)

危害

这些直接由操作系统控制的句柄,GC是不知道的,所以没办法进行释放,所以要通过Dispose()进行释放。未释放可能造成:
资源泄露(Handle leak): 创建的对象被GC回收了,但是Handle并没有被释放,GC并不知道,这时这个Handle就会一直被占用,每个进程打开的Handle数是有限制的(Window 16000),一旦超过这个数量,进程会崩溃。IOException: Too many open files 或 OutOfMemoryException

安全性(句柄回收攻击)

-   如果你手动管理 `IntPtr` 句柄,且在释放前 GC 运行了终结器,或者在释放后操作系统立即分配了相同的句柄值给另一个资源,可能会造成权限混淆或数据损坏。

可靠性(AppDomain 卸载)

-   在旧的 .NET Framework 中,普通的终结器(Finalizer)在某些极端情况(如 AppDomain 卸载、线程 abort)下可能不会被执行,导致句柄永远无法释放。

处理方法

SafeHandle:相当于将IntPtr和Dispose封装起来?

GC Dispose Finalizer

  • 职责:GC 只负责回收托管堆 (Managed Heap) 上的内存
  • 局限性:GC 无法 自动释放非托管资源。
  • 回收时机:非确定性 (Non-deterministic)。当内存不足或系统空闲时触发,你不知道它具体什么时候运行。
  • 代 (Generations) :0 代、1 代、2 代。对象存活越久,代越高,回收频率越低

Dispose():手动准确的释放所有资源(托管和非托管),由用户调用。
Finalizer:用戶並未手動關閉非托管資源,GC的托底機制,首先GC標記未被關閉的非托管资源,进入Finalizer队列,释放非托管资源,下一轮GC再回收内存

image.png

image.png

public class MyResource : IDisposable { private bool _disposed = false;

// 1. 非托管资源句柄
private IntPtr _handle; 
// 2. 托管资源
private Stream _stream; 

// 3. 公开 Dispose 方法 (供开发者调用)
public void Dispose()
{
    Dispose(true);
    // 4. 告诉 GC 不需要运行终结器了 (性能优化)
    GC.SuppressFinalize(this);
}

// 5. 终结器 (供 GC 调用,作为安全网)
~MyResource()
{
    Dispose(false);
}

// 6. 核心清理逻辑
protected virtual void Dispose(bool disposing)
{
    if (!_disposed)
    {
        if (disposing)
        {
            // 【 dispose=true 】: 由用户代码调用
            // 可以安全地释放托管资源
            _stream?.Dispose(); 
            _stream = null;
        }

        // 【 dispose=true/false 】: 都要释放非托管资源
        // 因为无论是用户手动释放,还是 GC 兜底,非托管资源都必须关掉
        if (_handle != IntPtr.Zero)
        {
            CloseHandle(_handle);
            _handle = IntPtr.Zero;
        }

        _disposed = true;
    }
}

}