在 dotnet 中有一個特殊的類,這個類能夠做到附加屬性一樣的功能,也就是給某個物件附加一個屬性,當這個物件被回收的時候,自然解除附加的屬性的物件的參考,本文就來聊聊這個類的底層原理
小伙伴都知道弱快取是什么,弱快取的核心是弱參考,也就是我雖然拿到一個物件,但是我沒有給這個物件添加依賴參考,也就是這個物件不會記錄被弱參考的參考,而 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 對他的強參考
換句話說,只要 key 的值存在,那么 value 一定不會回收
這個功能純使用 WeakReference 是做不到的,需要 GC 的支持,而在 dotnet core 里面提供 GC 支持的對接的是 DependentHandle 結構體
那么 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) 代碼,這部分屬于更底的一層了,在功能上就是實作上面的需求,而實作上為了性能優化,代碼可讀性還是渣了一些
要實作這個功能需要在 GC 層里面寫上一大堆的代碼,但使用上現在僅有 ConditionalWeakTable 一個在使用
轉載請註明出處,本文鏈接:https://www.uj5u.com/net/9659.html
標籤:.NET Core
