博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
C# MemoryCache GCHandle
阅读量:6276 次
发布时间:2019-06-22

本文共 16246 字,大约阅读时间需要 54 分钟。

MemoryCache在项目中用了很久,感觉比较简单,以前也看过里面的源代码,主要借用MemoryCacheStore来完成数据的存储,里面是线程安全的,MemoryCacheStore借用Hashtable来实现存储,如果已经有数据了,就把以前的删除然后在添加 

我们来看看MemoryCache的实现:

public class MemoryCache : ObjectCache, IEnumerable, IDisposable {    private static readonly TimeSpan OneYear = new TimeSpan(365, 0, 0, 0);    private static object s_initLock = new object();    private static MemoryCache s_defaultCache;    private static CacheEntryRemovedCallback s_sentinelRemovedCallback = new CacheEntryRemovedCallback(SentinelEntry.OnCacheEntryRemovedCallback);    private GCHandleRef
[] _storeRefs; private int _storeCount; private int _disposed; private MemoryCacheStatistics _stats; private string _name; private PerfCounters _perfCounters; private bool _configLess; EventHandler _onAppDomainUnload; UnhandledExceptionEventHandler _onUnhandledException; private MemoryCache() { _name = "Default"; Init(null); } public MemoryCache(string name, NameValueCollection config = null) { if (name == null) { throw new ArgumentNullException("name"); } if (name == String.Empty) { throw new ArgumentException(R.Empty_string_invalid, "name"); } if (String.Equals(name, "default", StringComparison.OrdinalIgnoreCase)) { throw new ArgumentException(R.Default_is_reserved, "name"); } _name = name; Init(config); } private void Init(NameValueCollection config) { _storeCount = Environment.ProcessorCount; _storeRefs = new GCHandleRef
[_storeCount]; InitDisposableMembers(config); } public override object Get(string key, string regionName = null) { return GetInternal(key, regionName); } private object GetInternal(string key, string regionName) { if (regionName != null) { throw new NotSupportedException(R.RegionName_not_supported); } if (key == null) { throw new ArgumentNullException("key"); } MemoryCacheEntry entry = GetEntry(key); return (entry != null) ? entry.Value : null; } internal MemoryCacheEntry GetEntry(String key) { if (IsDisposed) { return null; } MemoryCacheKey cacheKey = new MemoryCacheKey(key); MemoryCacheStore store = GetStore(cacheKey); return store.Get(cacheKey); } public override void Set(string key, object value, CacheItemPolicy policy, string regionName = null) { if (regionName != null) { throw new NotSupportedException(R.RegionName_not_supported); } if (key == null) { throw new ArgumentNullException("key"); } DateTimeOffset absExp = ObjectCache.InfiniteAbsoluteExpiration; TimeSpan slidingExp = ObjectCache.NoSlidingExpiration; CacheItemPriority priority = CacheItemPriority.Default; Collection
changeMonitors = null; CacheEntryRemovedCallback removedCallback = null; if (policy != null) { ValidatePolicy(policy); if (policy.UpdateCallback != null) { Set(key, value, policy.ChangeMonitors, policy.AbsoluteExpiration, policy.SlidingExpiration, policy.UpdateCallback); return; } absExp = policy.AbsoluteExpiration; slidingExp = policy.SlidingExpiration; priority = policy.Priority; changeMonitors = policy.ChangeMonitors; removedCallback = policy.RemovedCallback; } if (IsDisposed) { if (changeMonitors != null) { foreach (ChangeMonitor monitor in changeMonitors) { if (monitor != null) { monitor.Dispose(); } } } return; } MemoryCacheKey cacheKey = new MemoryCacheKey(key); MemoryCacheStore store = GetStore(cacheKey); store.Set(cacheKey, new MemoryCacheEntry(key, value, absExp, slidingExp, priority, changeMonitors, removedCallback, this)); } internal MemoryCacheStore GetStore(MemoryCacheKey cacheKey) { // Dev10 865907: Math.Abs throws OverflowException for Int32.MinValue int hashCode = cacheKey.Hash; if (hashCode < 0) { hashCode = (hashCode == Int32.MinValue) ? 0 : -hashCode; } int idx = hashCode % _storeCount; return _storeRefs[idx].Target; } private void InitDisposableMembers(NameValueCollection config) { bool dispose = true; try { try { _perfCounters = new PerfCounters(_name); } catch { // ignore exceptions from perf counters } for (int i = 0; i < _storeCount; i++) { _storeRefs[i] = new GCHandleRef
(new MemoryCacheStore(this, _perfCounters)); } _stats = new MemoryCacheStatistics(this, config); AppDomain appDomain = Thread.GetDomain(); EventHandler onAppDomainUnload = new EventHandler(OnAppDomainUnload); appDomain.DomainUnload += onAppDomainUnload; _onAppDomainUnload = onAppDomainUnload; UnhandledExceptionEventHandler onUnhandledException = new UnhandledExceptionEventHandler(OnUnhandledException); appDomain.UnhandledException += onUnhandledException; _onUnhandledException = onUnhandledException; dispose = false; } finally { if (dispose) { Dispose(); } } } private void OnAppDomainUnload(Object unusedObject, EventArgs unusedEventArgs) { Dispose(); } private void OnUnhandledException(Object sender, UnhandledExceptionEventArgs eventArgs) { // if the CLR is terminating, dispose the cache. // This will dispose the perf counters (see Dev10 680819). if (eventArgs.IsTerminating) { Dispose(); } } public void Dispose() { if (Interlocked.Exchange(ref _disposed, 1) == 0) { // unhook domain events DisposeSafeCritical(); // stats must be disposed prior to disposing the stores. if (_stats != null) { _stats.Dispose(); } if (_storeRefs != null) { foreach (var storeRef in _storeRefs) { if (storeRef != null) { storeRef.Dispose(); } } } if (_perfCounters != null) { _perfCounters.Dispose(); } GC.SuppressFinalize(this); } }}

MemoryCacheStore的实现:

internal sealed class MemoryCacheStore : IDisposable {    const int INSERT_BLOCK_WAIT = 10000;    const int MAX_COUNT = Int32.MaxValue / 2;    private Hashtable _entries;    private Object _entriesLock;    private CacheExpires _expires;    private CacheUsage _usage;    private int _disposed;    private ManualResetEvent _insertBlock;    private volatile bool _useInsertBlock;    private MemoryCache _cache;    private PerfCounters _perfCounters;    internal MemoryCacheStore(MemoryCache cache, PerfCounters perfCounters) {        _cache = cache;        _perfCounters = perfCounters;        _entries = new Hashtable(new MemoryCacheEqualityComparer());        _entriesLock = new Object();        _expires = new CacheExpires(this);        _usage = new CacheUsage(this);        InitDisposableMembers();    }    internal MemoryCacheEntry Get(MemoryCacheKey key) {        MemoryCacheEntry entry = _entries[key] as MemoryCacheEntry;        // has it expired?        if (entry != null && entry.UtcAbsExp <= DateTime.UtcNow) {            Remove(key, entry, CacheEntryRemovedReason.Expired);            entry = null;        }        // update outside of lock        UpdateExpAndUsage(entry);        return entry;    }    internal void Set(MemoryCacheKey key, MemoryCacheEntry entry) {        if (_useInsertBlock && entry.HasUsage()) {            WaitInsertBlock();        }        MemoryCacheEntry existingEntry = null;        bool added = false;        lock (_entriesLock) {            if (_disposed == 0) {                existingEntry = _entries[key] as MemoryCacheEntry;                if (existingEntry != null) {                    existingEntry.State = EntryState.RemovingFromCache;                }                entry.State = EntryState.AddingToCache;                added = true;                _entries[key] = entry;            }        }        CacheEntryRemovedReason reason = CacheEntryRemovedReason.Removed;        if (existingEntry != null) {            if (existingEntry.UtcAbsExp <= DateTime.UtcNow) {                reason = CacheEntryRemovedReason.Expired;            }            RemoveFromCache(existingEntry, reason, delayRelease:true);        }        if (added) {            AddToCache(entry);        }        // Dev10 861163: Call Release after the new entry has been completely added so         // that the CacheItemRemovedCallback can take a dependency on the newly inserted item.        if (existingEntry != null) {            existingEntry.Release(_cache, reason);        }    }    private void AddToCache(MemoryCacheEntry entry) {        // add outside of lock        if (entry != null) {            if (entry.HasExpiration()) {                _expires.Add(entry);            }            if (entry.HasUsage()                && (!entry.HasExpiration() || entry.UtcAbsExp - DateTime.UtcNow >= CacheUsage.MIN_LIFETIME_FOR_USAGE)) {                _usage.Add(entry);            }            entry.State = EntryState.AddedToCache;            entry.CallNotifyOnChanged();            if (_perfCounters != null) {                _perfCounters.Increment(PerfCounterName.Entries);                _perfCounters.Increment(PerfCounterName.Turnover);            }        }    }    internal void UpdateExpAndUsage(MemoryCacheEntry entry, bool updatePerfCounters = true) {        if (entry != null) {            if (entry.InUsage() || entry.SlidingExp > TimeSpan.Zero) {                DateTime utcNow = DateTime.UtcNow;                entry.UpdateSlidingExp(utcNow, _expires);                entry.UpdateUsage(utcNow, _usage);            }            // DevDiv #67021: If this entry has an update sentinel, the sliding expiration is actually associated            // with that sentinel, not with this entry. We need to update the sentinel's sliding expiration to            // keep the sentinel from expiring, which in turn would force a removal of this entry from the cache.            entry.UpdateSlidingExpForUpdateSentinel();            if (updatePerfCounters && _perfCounters != null) {                _perfCounters.Increment(PerfCounterName.Hits);                _perfCounters.Increment(PerfCounterName.HitRatio);                _perfCounters.Increment(PerfCounterName.HitRatioBase);            }        }        else {            if (updatePerfCounters && _perfCounters != null) {                _perfCounters.Increment(PerfCounterName.Misses);                _perfCounters.Increment(PerfCounterName.HitRatioBase);            }        }    } }

可见MemoryCache和MemoryCacheStore的实现都非常好理解。我们以web程序为例, 所有的数据都存在MemoryCacheStore的Hashtable中,但是不同的请求如何共享这个MemoryCacheStore数据了,一般我们采用static变量来实现,static变量跟着进程走,里面的线程共享它,我们来看看MemoryCache,它不是用静态变量,而是采用GCHandle来实现的,里面封装的GCHandleRef:

internal class GCHandleRef
: IDisposable where T : class, IDisposable { GCHandle _handle; T _t; [SecuritySafeCritical] [PermissionSet(SecurityAction.Assert, Unrestricted = true)] public GCHandleRef(T t) { _handle = GCHandle.Alloc(t); } public T Target { [SecuritySafeCritical] [PermissionSet(SecurityAction.Assert, Unrestricted = true)] get { try { T t = (T)_handle.Target; if (t != null) { return t; } } catch (InvalidOperationException) { // use the normal reference instead of throwing an exception when _handle is already freed } return _t; } } [SecuritySafeCritical] [PermissionSet(SecurityAction.Assert, Unrestricted = true)] public void Dispose() { Target.Dispose(); // Safe to call Dispose more than once but not thread-safe if (_handle.IsAllocated) { // We must free the GC handle to avoid leaks. // However after _handle is freed we no longer have access to its Target // which will cause AVs and various race conditions under stress. // We revert to using normal references after disposing the GC handle _t = (T)_handle.Target; _handle.Free(); } } }

在MemoryCache的MemoryCache构造函数里面会调用Init方法,里面会初始化_storeRefs数组(  _storeRefs = new GCHandleRef<MemoryCacheStore>[_storeCount];),最主要的是还调用InitDisposableMembers方法,在InitDisposableMembers方法里面给每个_storeRefs初始化一个实例,这些实例不会赔GC自动回收,而是在AppDomain的DomainUnload和UnhandledException事件里回收,回收也采用了原子锁

我们在使用c#托管代码时,内存地址和GC回收不是我们关心的,CLR已经给我们进行了暗箱操作。但是有时候我们想使用类似C语言那种方式直接对内存进行操作,或者涉及到非托管代码的调用,此时就需要保护内存地址,防止GC垃圾回收机制将内存收回。因为一旦内存被CLR回收掉,直接造成非托管代码再次访问这块内存时失效,导致程序崩溃。

C#中直接操作内存主要有以下三种方法:

1、GCHandle类用于提供用于从非托管内存访问托管对象的方法。下面通过程序进行介绍:

//托管的内存区域

Int16[] Mangement_Mem = new Int16[4]{ 4, 3, 2, 1 };
GCHandle gch = GCHandle.Alloc(Mangement_Mem,GCHandleType.Normal);
/*
为托管内存Mangement_Mem分配GCHandle句柄,它保护Mangement_Mem对象不被垃圾回收。但是此时Mangement_Mem在内存中的地址可能会改变,不管内存如何改变,其对象的的句柄的整数表示即gch值是不变的,因此可以将其值传给非托管函数中去使用。当不再需要 GCHandle时,必须通过Free将其释放,此后GC垃圾处理器可能才会对其回收
*/
/*
GCHandle.Alloc(Mangement_Mem,GCHandleType.Normal)作用类似如下:
GC.KeepAlive(Mangement_Mem);
从Mangement_Mem句柄表现形式再次转化为句GCHandle对象
IntPtr Ptr_Mem = GCHandle.ToIntPtr(gch);
GCHandle handle = GCHandle.FromIntPtr(Ptr_Mem);
*/
//获取该GCHandle对象表示的实际对象。
Int16[] array = (Int16[]) handle.Target;
GCHandle.Alloc(Mangement_Mem,GCHandleType.Normal);GCHandle.Alloc函数的第二个形参,除了有GCHandleType.Normal 外,还有Pinned。但Normal不会固定其地址,只是保证内存不被GC回收。而Pinned可以将地址固定住,Pinned后这将防止垃圾回收器移动内存地址。

2、 Marshal

C#中提供了一个方法集,这些方法用于分配非托管内存、复制非托管内存块、将托管类型转换为非托管类型,此外还提供了在与非托管代码交互时使用的其他杂项方法。也只有c++.net才有托管,非托管的概念,纯的C++没有这个概念。java可以认为所有东西都是托管的。这就是通过marshal类实现。

Marshal可以实现结构体和字节序之间的转化。具体可以搜索一下网上的资料。

3、通过fixe固定地址。将我们申请的资源通过关键字进行固定,达到使CLR不使用垃圾回收机制操作我们保护的内存。

class StudentInfo    {        public string Name { set; get; }    }    class Program    {        private const int OptionsMask = 0xFFFF;        static void Main(string[] args)        {            var a = new StudentInfo { Name = "Gavin" };            var b = ObjectToByte(a);            var c = ByteToObject(ref a, b);            Console.ReadKey();        }        ///         /// 将结构体转换成字节数组        ///         ///         /// 
public static byte[] ObjectToByte
(T obj) { //得到结构体的大小 int size = Marshal.SizeOf
(obj); //创建byte数组 byte[] bytes = new byte[size]; //分配结构体大小的内存空间 IntPtr structPtr = Marshal.AllocHGlobal(size); //将结构体拷到分配好的内存空间 Marshal.StructureToPtr(obj, structPtr, false); //从内存空间拷到byte数组 Marshal.Copy(structPtr, bytes, 0, size); //释放内存空间 Marshal.FreeHGlobal(structPtr); //返回byte数组 return bytes; } ///
/// 将字节数组转换成结构体 /// ///
///
///
///
public static int ByteToObject
(ref T _struct, byte[] buffer) { try { if ((buffer != null) && (buffer.Length > 0)) { GCHandle pinned = GCHandle.Alloc(buffer, GCHandleType.Pinned); try { _struct = (T)Marshal.PtrToStructure(pinned.AddrOfPinnedObject(), typeof(T)); return buffer.Length; } finally { pinned.Free(); } } else return 0; } catch { return -1; } } }

 

转载地址:http://sqyva.baihongyu.com/

你可能感兴趣的文章
(转)第三方支付参与者
查看>>
程序员修炼之道读后感2
查看>>
DWR实现服务器向客户端推送消息
查看>>
js中forEach的用法
查看>>
Docker之功能汇总
查看>>
!!a标签和button按钮只允许点击一次,防止重复提交
查看>>
Maven学习总结(15)——Maven 项目中pom.xml详解
查看>>
我的友情链接
查看>>
SCCM 2012系列9 补丁分发上
查看>>
运维监控利器Nagios之:安装nagios
查看>>
维持数据服务系统稳定运行的三架马车
查看>>
解决思科认证学员与高校网络及信息安全专业实验限制的解决方案
查看>>
AD&Exchange2010 简单安装部署(一)
查看>>
最大的错误是不敢犯错
查看>>
【linux教程】inotify用法简介及结合rsync实现主机间的文件实时同步
查看>>
书摘—拆掉思维里的墙
查看>>
ORA-01078: failure in processing system parameters
查看>>
授权特定普通域用户将计算机加入域
查看>>
【REACT NATIVE 系列教程之十三】利用LISTVIEW与TEXTINPUT制作聊天/对话框&&获取组件实例常用的两种方式...
查看>>
虚拟化系列-Windows server 2012 网络管理
查看>>