我有一个在Windows Server AppFabric SDK中找到的Microsoft.ApplicationServer.Caching.DataCache对象的扩展方法,如下所示:
using System; using System.Collections.Generic; using Microsoft.ApplicationServer.Caching; namespace Caching { public static class CacheExtensions { private static Dictionarylocks = new Dictionary (); public static T Fetch (this DataCache @this, string key, Func func) { return @this.Fetch(key, func, TimeSpan.FromSeconds(30)); } public static T Fetch (this DataCache @this, string key, Func func, TimeSpan timeout) { var result = @this.Get(key); if (result == null) { lock (GetLock(key)) { result = @this.Get(key); if (result == null) { result = func(); if (result != null) { @this.Put(key, result, timeout); } } } } return (T)result; } private static object GetLock(string key) { object @lock = null; if (!locks.TryGetValue(key, out @lock)) { lock (locks) { if (!locks.TryGetValue(key, out @lock)) { @lock = new object(); locks.Add(key, @lock); } } } return @lock; } } }
目的是让开发人员编写代码,说"先通过尝试缓存来获取一些数据.如果在缓存中不可用,则执行指定的函数,将结果放入缓存中以供下一个调用者使用,然后返回结果".像这样:
var data = dataCache.Fetch("key", () => SomeLongRunningOperation());
执行潜在长时间运行的函数的锁定限制调用单个线程,但仅限于同一台机器上的单个进程.您将如何扩展此模式以使锁定分布以防止多个进程/机器立即执行该函数?
AppFabric拥有自己的分布式锁定机制,您可以通过GetAndLock/PutAndUnlock
一系列方法访问它.如果您的项目已锁定,则正常Get
调用仍将成功并返回最后一个值,但进一步GetAndLock
调用将抛出异常.在您的客户端第一次请求缓存对象的情况下,您仍然可以锁定密钥,即使它尚不存在(它更像是一个预留而不是一个实体锁).
public static T Fetch(this DataCache @this, string key, Func func, TimeSpan timeout) { var result = @this.Get(key); if (result == null) ( DataCacheLockHandle handle; // We need a timespan to allow func time to run TimeSpan funcTimespan = New TimeSpan(0,1,0); try { // Lock the key // If something goes wrong here it will unlock at the end of funcTimespan var result = @this.GetAndLock(key, funcTimespan, handle); if (result == null) { // Still no value so go and run func result = func(); @this.PutAndUnlock(key, result, handle, timeout); } else { // There's a value now so we'll unlock the key and reset it's timeout @this.Unlock(key, handle, timeout); } } catch (DataCacheException ex) { if (ex.ErrorCode == DataCacheErrorCode.ObjectLocked) { // Another process has locked the key so func must be running right now // We'll return null to the client result = null; } } if (result == null) { return null; } else { return (T)result; } ) }