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 一個在使用

相關文章
相關標籤/搜索