1

在查看 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 上运行的此代码确认了这一点(尽管我无法确认该方法的内容与我在网上找到的内容没有改变) :

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
4

3 回答 3

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 中删除。

于 2012-08-24T19:17:27.780 回答
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永远不会调用 。正如 usr 所提到的,这可能是由于Thread.Abort.

你的例子:

if(!Monitor.TryEnter(tuningLock)) return;
//
// DO WORK...
//
Monitor.Exit(tuningLock);

遭受这个问题等等。这段代码被中断Exit而不被调用的窗口基本上是整个代码块——任何异常(不仅仅是一个来自Thread.Abort)。

我不知道为什么大多数代码都是用 .NET 编写的。但是,我推测这段代码是为了避免 and 之间的异常问题而编写Entertry。让我们看一些细节:

try{}
finally
{
  lockHeld = Monitor.TryEnter(tuningLock);
}

Finally块基本上在 IL 中生成一个受约束的执行区域。受约束的执行区域不能被任何东西打断。因此,将 放在上面TryEnterfinally块中可确保lockHeld可靠地保持lock.

该代码块包含在try/finally块中,其finally语句调用Monitor.ExitiftuningLock为真。这意味着在Entertry块之间没有可以被中断的点。

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

于 2012-08-25T13:59:49.617 回答
0

任何想法为什么他们可能会为这一切而烦恼?

在运行了一些测试之后,我认为看到了一个原因(如果不是原因的话):他们可能会为这一切而烦恼,因为它要快得多!

事实证明,如果对象已经被锁定(如果它没有被锁定,仍然非常快——没有问题) ,这Monitor.TryEnter是一个昂贵的调用。因此,除第一个线程外,所有线程都将经历缓慢。TryEnter

我不认为这有那么重要。因为毕竟,每个线程都会尝试一次锁定然后继续前进(不像他们会坐在那里,循环尝试)。但是,我编写了一些代码进行比较,它表明TryEnter(当已经锁定时)的成本是显着的。事实上,在我的系统上,在没有附加调试器的情况下,每次调用大约需要 0.3 毫秒,这比使用简单的布尔检查慢了几个数量级。

所以我怀疑,这可能出现在微软的测试结果中,所以他们优化了上面的代码,添加了快速通道布尔检查。但这只是我的猜测..

于 2012-08-25T14:10:00.707 回答