dotnet ConditionalWeakTable 的底层原理

在 dotnet 中有一个特殊的类,这个类可以作到附加属性同样的功能。也就是给某个对象附加一个属性,当这个对象被回收的时候,天然解除附加的属性的对象的引用。本文就来聊聊这个类的底层原理html

小伙伴都知道弱缓存是什么,弱缓存的核心是弱引用。也就是我虽然拿到一个对象,可是我没有给这个对象添加依赖引用,也就是这个对象不会记录被弱引用的引用。而 ConditionalWeakTable 也是一个弱缓存只是有些特殊的是关联的是其余对象。使用方法请看 .NET/C# 使用 ConditionalWeakTable 附加字段(CLR 版本的附加属性,也可用用来看成弱引用字典 WeakDictionary) - walterlv缓存

这个类通常用来作弱缓存字典,只要 Key 没有被回收,而 value 就不会被回收。若是 key 被回收,那么 value 将会减去一个依赖引用。而字典对于 key 是弱引用性能优化

经过阅读 runtime 的源代码,能够看到实际上这个类的核心须要 DependentHandle 结构体的支持,由于依靠 key 定住 value 须要 CLR 的 GC 支持。什么是依靠 key 定住 value 的功能?这里的定住是 Pin 的翻译,意思是若是 key 存在内存,那么将会给 value 添加一个引用,此时的 value 将不会被回收。而若是 key 被回收了,此时的 value 将失去 key 对他的强引用ide

换句话说,只要 key 的值存在,那么 value 必定不会回收函数

这个功能纯使用 WeakReference 是作不到的,须要 GC 的支持,而在 dotnet core 里面提供 GC 支持的对接的是 DependentHandle 结构体post

那么 DependentHandle 的功能又是什么?这个结构体提供传入 object primary, object? secondary 构造函数,做用就是当 primary 没有被回收的时候,给 secondary 添加一个引用计数。在 primary 回收的时候,解除对 secondary 的引用。而这个结构体自己对于 primary 是弱引用的,对于 secondary 仅在 primary 没有被回收时是强引用,当 primary 被回收以后将是弱引用性能

恰好利用 GC 的只要对象至少有一个引用就不会被回收的功能,就能作到 ConditionalWeakTable 提供附加属性的功能优化

下面代码是 DependentHandle 结构体的代码,能够看到大量的方法都是须要 GC 层的支持,属于 CLR 部分的注入方法翻译

internal struct DependentHandle
    {
        private IntPtr _handle;

        public DependentHandle(object primary, object? secondary) =>
            // no need to check for null result: nInitialize expected to throw OOM.
            _handle = nInitialize(primary, secondary);

        public bool IsAllocated => _handle != IntPtr.Zero;

        // Getting the secondary object is more expensive than getting the first so
        // we provide a separate primary-only accessor for those times we only want the
        // primary.
        public object? GetPrimary() => nGetPrimary(_handle);

        public object? GetPrimaryAndSecondary(out object? secondary) =>
            nGetPrimaryAndSecondary(_handle, out secondary);

        public void SetPrimary(object? primary) =>
            nSetPrimary(_handle, primary);

        public void SetSecondary(object? secondary) =>
            nSetSecondary(_handle, secondary);

        // Forces dependentHandle back to non-allocated state (if not already there)
        // and frees the handle if needed.
        public void Free()
        {
            if (_handle != IntPtr.Zero)
            {
                IntPtr handle = _handle;
                _handle = IntPtr.Zero;
                nFree(handle);
            }
        }

        [MethodImpl(MethodImplOptions.InternalCall)]
        private static extern IntPtr nInitialize(object primary, object? secondary);

        [MethodImpl(MethodImplOptions.InternalCall)]
        private static extern object? nGetPrimary(IntPtr dependentHandle);

        [MethodImpl(MethodImplOptions.InternalCall)]
        private static extern object? nGetPrimaryAndSecondary(IntPtr dependentHandle, out object? secondary);

        [MethodImpl(MethodImplOptions.InternalCall)]
        private static extern void nSetPrimary(IntPtr dependentHandle, object? primary);

        [MethodImpl(MethodImplOptions.InternalCall)]
        private static extern void nSetSecondary(IntPtr dependentHandle, object? secondary);

        [MethodImpl(MethodImplOptions.InternalCall)]
        private static extern void nFree(IntPtr dependentHandle);
    }

而核心实现的入口是在 gchandletable.cpp 的 OBJECTHANDLE GCHandleStore::CreateDependentHandle(Object* primary, Object* secondary) 代码,这部分属于更底的一层了,在功能上就是实现上面的需求,而实现上为了性能优化,代码可读性仍是渣了一些code

要实现这个功能须要在 GC 层里面写上一大堆的代码,但使用上如今仅有 ConditionalWeakTable 一个在使用

相关文章
相关标签/搜索