之前写了一篇《对不能用using的成对操作,快速扩展IDisposable的方法》提到了如何快速的把销毁操作用闭包的形式封装为IDisposable,并且实现了一个ReaderWriteerLockSlimHelper。
对于没有using的RWLock似乎已经很好用了, 但是我仍嫌弃其不够简单。
资源读写锁的应用,绝大多数是针对某批特定的资源。如果为这样的资源做一个封装,资源的引用需要被隐藏在封装内,封装外不通过读锁不可以访问资源。显然ReaderWriteerLockSlimHelper却无法做到这一点。
我们在上文中已经建立了一个 Disposable对象,取得该对象意味着我们可以加锁,将其销毁我们就可以解锁,其生存期与整个加锁周期吻合。对于读写锁来说具有这种行为的对象是一种作为读写钥匙/令牌的存在。解锁后的资源引用放在这个令牌上作为属性暴露出来再合适不过了。
以此为考量,我们先设计一个实现IDisposable,读写锁的令牌
/// <summary>
/// 读写资源的令牌
/// </summary>
public abstract class LockToken<T> : IDisposable
{
protected LockableObjectContainer<T> _container;
/// <summary>
/// 被锁住的资源引用
/// </summary>
public T Value { get; set; }
/// <summary>
/// 读写锁的原钥匙,对其销毁即为解锁
/// </summary>
protected IDisposable _innerTicket;
/// <summary>
/// 创建读写资源的令牌
/// </summary>
/// <param name="value">解锁的资源引用</param>
/// <param name="innerTicket">锁的真实引用</param>
/// <param name="beforeDispose">在解锁前需要做的操作 如把资源的新值覆盖回锁住的资源</param>
/// <param name="afterDispose">在解锁后需要做的操作 如写日志</param>
internal LockToken(LockableObjectContainer<T> container, IDisposable innerTicket, Action<LockToken<T>> beforeDispose, Action<LockToken<T>> afterDispose)
{_container=container;
_innerTicket = innerTicket;
Value = container._value;
this._disposeAction =
() =>
{
try
{
if (beforeDispose != null) beforeDispose(this);
this.Value = default(T);
_innerTicket.Dispose();
if (afterDispose != null) afterDispose(this);
}
catch
{
}
};
}
#region IDisposable Members
/// <summary>
/// 令牌销毁时要做的操作
/// </summary>
Action _disposeAction;
/// <summary>
/// 销毁
/// </summary>
public void Dispose()
{
_disposeAction();
}
#endregion
}
读写时,所做的操作略有不同。写令牌在销毁时要把令牌上的新引用/值 覆盖到容器内
依此我们可以做两个不同的令牌子类
class ReadLockToken<T> : LockToken<T>
{
public ReadLockToken(LockableObjectContainer<T> container, ReaderWriterLockSlim _lock) :
base(container, _lock.CreateLockScope(LockType.Read), null, null)
{
}
}
class WriteLockToken<T> : LockToken<T>
{
public WriteLockToken(LockableObjectContainer<T> container, ReaderWriterLockSlim _lock)
: base
(
container,
_lock.CreateLockScope(LockType.Write),
lt =>
{
//在解锁前 把新值保存给原对象
lt._container._value =lt.Value;
},
null
)
{
}
}
这些做好后,编写容器就很容易了
public class LockableObjectContainer<T>
{
internal protected T _value;
System.Threading.ReaderWriterLockSlim _lock = new System.Threading.ReaderWriterLockSlim();
public LockableObjectContainer(T value)
{
_value = value;
}
public LockToken<T> GetReadToken()
{
return new ReadLockToken<T>(this, _lock);
}
public LockToken<T> GetWriteToken()
{
return new WriteLockToken<T>(this, _lock);
}
}
上面的内容很枯燥
写读写锁的时候很方便
static LockableObjectContainer <Dictionary <Type ,IFactoryContainer > > planConnectionGetters
= new LockableObjectContainer<Dictionary<Type, IFactoryContainer>>
(new Dictionary<Type, IFactoryContainer>());
。。。。。。。。。。。。。。。。。。。。。。。。。
using (var token =planConnectionGetters.GetReadToken())
{
var dic = token.Value;
if (dic.TryGetValue(contractType , out channelFactory ))
{
return channelFactory.GetChannel (uri);
}
}
using (var token = planConnectionGetters.GetWriteToken ())
{
var dic = token.Value;
if (!dic.TryGetValue(contractType, out channelFactory))
{
Type t = typeof(FactoryContainer<>);
channelFactory = Activator.CreateInstance(t.MakeGenericType(contractType)) as IFactoryContainer;
dic.Add(contractType, channelFactory);
}
}
如果容器经常保存字典等常用集合对象 我们也可以这样做一些扩展方法
public static TValue GetOrCreateValue<TKey, TValue>
(
this LockableObjectContainer<IDictionary<TKey, TValue>> container,
TKey key,
Predicate <TValue > valueChecker,
Func<TValue> valueFactory
)
{
TValue val=default (TValue);
using (var token = container.GetReadToken())
{
var dic = token.Value;
if (dic.TryGetValue(key, out val))
{
if (valueChecker (val))
return val;
}
}
using (var token = container.GetWriteToken())
{
var dic = token.Value;
if (dic.TryGetValue(key, out val))
{
if (valueChecker(val))
return val;
}
val=valueFactory();
dic.Add(key,val) ;
}
return val;
}
------------------------------------------------------------
TQueue q;
q = instanceQueues.GetOrCreateValue(instance, _ => true, () => new TQueue());
return q;
日常操作中有很多需要打开/关闭 加锁/解锁的成对操作
有时候一些操作原生支持 IDisposable
Monitor 可以用Lock(){} 但是ReadWriteLock 就难了。 还有WCF Channel等)。
这种情况就要用 try/catch/finally,很是丑
封装成IDisposable可能很烦,因为多一个对象要多好多文档。
虽然AOP可能解决一些问题, 但是又没办法精确定位 scrope.
还是 IDisposable +using最爽
所以写了一个缺省实现。
/// <summary>
/// 销毁帮手,生成可以支持using的自定义IDisposable实例
/// <remarks>感谢网友@doggo对于 +=OnDispose功能的測試,由於不完善這裡決定取消該功能</remarks>
/// </summary>
public struct Disposable : IDisposable
{
/// <summary>
/// 创建销毁帮手实例
/// </summary>
/// <param name="onCreate">创建时要做的操作</param>
/// <param name="onDispose">销毁是要做的操作</param>
public Disposable(Action onCreate, Action onDispose)
{
OnDispose = onDispose;
onCreate();
}
/// <summary>
/// 销毁时要做的操作
/// </summary>
private Action OnDispose
{
get ;set;
}
////// <summary>
////// 销毁时要做的操作 支持+=/Addhandler附加操作 (撤銷)
////// </summary>
//////public event Action OnDispose ;
#region IDisposable 成员
void IDisposable.Dispose()
{
OnDispose();
OnDispose = null;
}
#endregion
}
思路是用一个扩展方法,给一个无dispose 能力的对象 建立一个IDisposable的引用。
由于onCreate onDispose是闭包 额外的参数也是非必要的。
这里提供一个简易读写锁的实现。 大家可以参考
public static class ReaderWriteerLockSlimHelper
{
/// <summary>
/// 为读写锁创建支持using的IDisposable帮手
/// </summary>
/// <param name="instance">读写锁实例</param>
/// <param name="lockType">加锁类型 读/写</param>
/// <returns>帮手实例</returns>
public static IDisposable CreateDisposable(this ReaderWriterLockSlim instance, LockType lockType)
{
var kvp = LockDisposeDic[lockType];
return new Disposable(() => kvp.Key(instance), () => kvp.Value(instance));
}
/// <summary>
/// 读写的不同操作字典
/// </summary>
static Dictionary<LockType, KeyValuePair<Action<ReaderWriterLockSlim>, Action<ReaderWriterLockSlim>>> LockDisposeDic = new Dictionary<LockType, KeyValuePair<Action<ReaderWriterLockSlim>, Action<ReaderWriterLockSlim>>>()
{
{
LockType.Read,
new KeyValuePair<Action<ReaderWriterLockSlim>,Action<ReaderWriterLockSlim>>
(
ins=>ins.EnterReadLock(),
ins=>ins.ExitReadLock()
)
},
{
LockType.Write,
new KeyValuePair<Action<ReaderWriterLockSlim>,Action<ReaderWriterLockSlim>>
(
ins=>ins.EnterWriteLock(),
ins=>ins.ExitWriteLock()
)
}
};
}
public enum LockType
{
Read,
Write
}
实际使用起来就是爽。 这是一个在需要并发访问的队列中 加入对象的方法。
/// <summary>
/// 加入有序队列。
/// </summary>
/// <param name="item">加入的项目</param>
public void Enqueue(TValue item)
{
using (_lock.CreateDisposable(LockType.Write))
{
Queue<TValue> enqueueTarget;
var key=_keySelector(item);
if (!_items.ContainsKey(key))
{
var sortValue = _sortValueSelector(item);
if (!_index.TryGetValue( _sortValueSelector(item) ,out enqueueTarget ))
{
enqueueTarget = new Queue<TValue>();
_index.Add(sortValue, enqueueTarget);
}
enqueueTarget.Enqueue(item);
_items.Add(key, item);
}
else
{
throw new InvalidOperationException("this Item already in queue");
}
}
}
个人工作分享。希望能够帮助大家提高工作效率
附: 容易漏调结束行为的实现 和try catch finally 实现 大家比较下
出异常会死锁的错误成对掉用
/// <summary>
/// 加入有序队列。
/// </summary>
/// <param name="item">加入的项目</param>
public void Enqueue1(TValue item)
{
_lock.EnterWriteLock();
Queue<TValue> enqueueTarget;
var key = _keySelector(item);
if (!_items.ContainsKey(key))
{
var sortValue = _sortValueSelector(item);
if (!_index.TryGetValue(_sortValueSelector(item), out enqueueTarget))
{
enqueueTarget = new Queue<TValue>();
_index.Add(sortValue, enqueueTarget);
}
enqueueTarget.Enqueue(item);
_items.Add(key, item);
}
else
{
throw new InvalidOperationException("this Item already in queue");
}
_lock.ExitReadLock(); //不但可能中途退出 还可能像这样写错解锁方法
}
正确而麻烦的try catch finally
/// <summary>
/// 加入有序队列。
/// </summary>
/// <param name="item">加入的项目</param>
public void Enqueue2(TValue item)
{
_lock.EnterWriteLock();
try
{
Queue<TValue> enqueueTarget;
var key = _keySelector(item);
if (!_items.ContainsKey(key))
{
var sortValue = _sortValueSelector(item);
if (!_index.TryGetValue(_sortValueSelector(item), out enqueueTarget))
{
enqueueTarget = new Queue<TValue>();
_index.Add(sortValue, enqueueTarget);
}
enqueueTarget.Enqueue(item);
_items.Add(key, item);
}
else
{
throw new InvalidOperationException("this Item already in queue");
}
}
catch (Exception ex)
{
throw ex;
}
finally
{
_lock.ExitWriteLock();
}
}
*貌似是炫耀博?*
卑鄙本周新入一台 inter i7 平台 gts250 12g内存的主机 自己折腾用。
win7 评分
cpu 内存7.5
显示7.9
硬盘5.8
----------出问题了----------------
仆自以为很爽 ,但是杯子和牙刷总是伴随存在的。
不想在安装win7 64后出现 最大化最小化停靠等aero动画速度缓慢的症状。
仔细观察 是把原来半秒左右的动画 逐桢以慢动作的方式延长到了一秒多。
后来开发wpf程序练习,发现checkbox标准动画 从unchecked状态到checked状态动画也要1秒多。心想这下坏了,wpf要废?
马上找一个silverlight 应用 cool.pptv.com 发现也是动画奇慢。
---------用经验解决-----------------------
经观察wpf和sl的动画慢归满 动作却非常平滑,毫无丢桢。8个cpu监视窗也在5%以下。
看起来是时钟频率有些问题。
sl wpf 和aero 一定是参考了一个不正确的时钟 导致播放动画的用时计算出错。
只因为aero是定桢动画,而wpf于sl是dx加速的即时生成 才有显示上的差别。
i7太新了,有些功能win7还不能正确的支持吧?
google 10分钟无解。还是得自己想办法。
打开bios 查看可疑的项目逐个设置观察,在cell设置中 cpu找到一个intel turbo boost的功能。
turbo让我想起儿时486sx 33。这个机器在前面版上有个turbo按键。按下去,面板上的频率数字就会从16变成33 khz,有时候在16khz状态下打开的dos游戏,如果按下turbo,就会突然变快。这招玩韩国街霸的时候很有用。
恩。。。似乎是和时钟频率相关。
关掉
f10
保存。
重新进入系统 sl/wpf/aero
all pass.
后来问硬件达人,说这是个叫做“睿频”的技术 在空闲时候自动降频,在需要的时候还可能有超频。
我想,这个技术也许不错,物尽其用嘛。但用它跑时间敏感的应用,比如秒表啊,性能计数啊,网络游戏啊 blabla..,我是不大敢。还是关上保险点。
鉴于之前google无果,特发于博上,希望后来者能够参考。
希望win7 x64 和intel 早点互通有无 随时把最新的时钟频率更新给用户态。
public virtual bool CanExecute(object parameter)
{
bool hasEnabledCommandsThatShouldBeExecuted = false;
ICommand[] commandList;
lock (this.registeredCommands)
{
commandList = this.registeredCommands.ToArray();
}
foreach (ICommand command in commandList)
{
if (this.ShouldExecute(command))
{
if (!command.CanExecute(parameter))
{
return false;
}
hasEnabledCommandsThatShouldBeExecuted = true;
}
}
return hasEnabledCommandsThatShouldBeExecuted;
}

