在查看System.ServiceModel.Channels.BufferManager的源代码时,我注意到了这个方法:
void TuneQuotas()
{
if (areQuotasBeingTuned)
return;
bool lockHeld = false;
try
{
try { }
finally
{
lockHeld = Monitor.TryEnter(tuningLock);
}
// Don't bother if another thread already has the lock
if (!lockHeld || areQuotasBeingTuned)
return;
areQuotasBeingTuned = true;
}
finally
{
if (lockHeld)
{
Monitor.Exit(tuningLock);
}
}
//
// DO WORK... (code removed for brevity)
//
areQuotasBeingTuned = false;
}
显然,他们只希望一个线程运行TuneQuotas()
,而其他线程如果已经被另一个线程运行则不等待。我应该注意,删除的代码没有尝试保护。
我试图理解上面这个方法的优点而不仅仅是这样做:
void TuneQuotas()
{
if(!Monitor.TryEnter(tuningLock)) return;
//
// DO WORK...
//
Monitor.Exit(tuningLock);
}
为什么他们可能会为此烦恼?我怀疑他们使用finally
块的方式是防止线程中止场景,但我仍然没有看到这一点,因为即使使用所有这些代码,TuneQuotas()
也会被锁定好的如果由于某种原因,一个线程没有一直到最后设置areQuotasBeingTunes=false
。那么我错过了这种模式的一些很酷的东西吗?
修改 作为旁注,似乎该方法存在于.NET 4.0中,我确认使用在框架4上运行的此代码(尽管我无法确认该方法的内容与我在Web上找到的内容没有变化):< / p>
var buffMgr = BufferManager.CreateBufferManager(1, 1);
var pooledBuffMgrType = buffMgr.GetType()
.GetProperty("InternalBufferManager")
.GetValue(buffMgr, null)
.GetType();
Debug.WriteLine(pooledBuffMgrType.Module.FullyQualifiedName);
foreach (var methodInfo in pooledBuffMgrType
.GetMethods(BindingFlags.Instance | BindingFlags.NonPublic))
{
Debug.WriteLine(methodInfo.Name);
}
输出:
C:\Windows\Microsoft.Net\assembly\GAC_MSIL\System.Runtime.DurableInstancing\v4.0_4.0.0.0__3 1bf3856ad364e35\System.Runtime.DurableInstancing.dll
ChangeQuota
DecreaseQuota
FindMostExcessivePool
FindMostStarvedPool
FindPool
IncreaseQuota
TuneQuotas
Finalize
MemberwiseClone
答案 0 :(得分:1)
我会添加一些评论:
void TuneQuotas()
{
if (areQuotasBeingTuned)
return; //fast-path, does not require locking
bool lockHeld = false;
try
{
try { }
finally
{
//finally-blocks cannot be aborted by Thread.Abort
//The thread could be aborted after getting the lock and before setting lockHeld
lockHeld = Monitor.TryEnter(tuningLock);
}
// Don't bother if another thread already has the lock
if (!lockHeld || areQuotasBeingTuned)
return; //areQuotasBeingTuned could have switched to true in the mean-time
areQuotasBeingTuned = true; //prevent others from needlessly trying to lock (trigger fast-path)
}
finally //ensure the lock being released
{
if (lockHeld)
{
Monitor.Exit(tuningLock);
}
}
//
// DO WORK... (code removed for brevity)
//
//this might be a bug. There should be a call to Thread.MemoryBarrier,
//or areQuotasBeingTuned should be volatile
//if not, the write might never reach other processor cores
//maybe this doesn't matter for x86
areQuotasBeingTuned = false;
}
您提供的简单版本无法防范某些问题。至少它不是异常安全的(锁定不会被释放)。有趣的是,“复杂”版本也没有。
此方法已从.NET 4中删除。
答案 1 :(得分:1)
在.NET 4.0之前,代码中存在一个由lock
声明生成的错误。它会产生类似于以下内容的东西:
Monitor.Enter(lockObject)
// see next paragraph
try
{
// code that was in the lock block
}
finally
{
Monitor.Exit(lockObject);
}
这意味着如果Enter
和try
之间发生异常,则永远不会调用Exit
。正如我们所提到的,这可能是由Thread.Abort
引起的。
你的例子:
if(!Monitor.TryEnter(tuningLock)) return;
//
// DO WORK...
//
Monitor.Exit(tuningLock);
遇到这个问题等等。此代码被中断且Exit
未被调用的窗口基本上是整个代码块 - 除了Thread.Abort
之外的任何异常(<{1}})。
我不知道为什么大多数代码是用.NET编写的。但是,我猜测这段代码的编写是为了避免Enter
和try
之间出现异常的问题。我们来看一些细节:
try{}
finally
{
lockHeld = Monitor.TryEnter(tuningLock);
}
Finally
块基本上在IL中生成约束执行区域。约束的执行区域不能被任何东西打断。因此,将TryEnter
放在上面的finally
块中会确保lockHeld
可靠地保持lock
的状态。
该代码块包含在try
/ finally
块中,如果finally
为真,则Monitor.Exit
语句调用tuningLock
。这意味着Enter
和try
块之间没有任何可以中断的点。
FWIW,此方法仍在.NET 3.5中,并且在WCF 3.5源代码(不是.NET源代码)中可见。我还不知道4.0中有什么;但我想它会是一样的;即使部分结构的推动力不再存在,也没有理由改变工作代码。
有关lock
使用生成内容的详细信息,请参阅http://blogs.msdn.com/b/ericlippert/archive/2007/08/17/subtleties-of-c-il-codegen.aspx
答案 2 :(得分:0)
为什么他们可能会为此烦恼?
经过一些测试后,我认为看到一个原因(如果不是原因):他们可能会为此烦恼,因为它的速度要快得多!
结果Monitor.TryEnter
是昂贵的调用,如果对象已经锁定(如果它未锁定,TryEnter
仍然非常快 - 没有问题)。因此除了第一个线程之外的所有线程都将经历缓慢。
我认为这不会那么重要;从那时起,每个线程都会尝试只使用一次锁定然后继续前进(不像他们坐在那里,尝试循环)。但是,我写了一些代码用于比较,它表明TryEnter
(已经锁定)的成本很高。实际上,在我的系统上,每次调用大约需要0.3毫秒,而不需要附加调试器,这比使用简单的布尔检查慢几个数量级。
所以我怀疑,这可能出现在微软的测试结果中,所以他们通过添加快速布尔检查来优化上面的代码。但那只是我的猜测..