C# 解释WCF源代码中奇怪的同步

C# 解释WCF源代码中奇怪的同步,c#,wcf,synchronization,try-catch,C#,Wcf,Synchronization,Try Catch,在查看System.ServiceModel.Channels.BufferManager的源代码时,我注意到以下方法: void TuneQuotas() { if (areQuotasBeingTuned) return; bool lockHeld = false; try { try { } finally { lockHeld = Monitor.TryEnter(

在查看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()
,如果其他线程已经在运行,则其他线程不等待。我应该注意的是,删除的代码不是try-protected

我正试图理解上述方法的优点,而不仅仅是这样做:

你知道为什么他们会为这一切烦恼吗?我怀疑他们使用
finally
块的方式是为了防止线程中止场景,但我仍然不明白这一点,因为即使使用所有这些代码,
TuneQuotas()
也会被永久锁定,如果一个线程没有一直到最后设置
areQuotasBeingTunes=false
,出于这样或那样的原因。那么,这种模式有什么我不知道的地方吗

编辑: 作为旁注,该方法似乎存在于.NET 4.0中,我使用运行在framework 4上的代码确认了该方法(尽管我无法确认该方法的内容是否与我在web上找到的内容相同):

哪些产出:

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

我将补充一些意见:

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

直到.NET 4.0,由
锁生成的代码中基本上存在一个bug。它将生成类似于以下内容的内容:

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);
遭受这个问题和更多的痛苦。此代码被中断和
退出
不被调用的窗口基本上是整个代码块——任何异常(而不仅仅是
Thread.Abort
中的一个)

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

try{}
finally
{
  lockHeld = Monitor.TryEnter(tuningLock);
}
最后
块基本上在IL中生成一个受约束的执行区域。受约束的执行区域不能被任何东西中断。因此,将
TryEnter
放在上面的
finally
块中可以确保
lockhold
可靠地保持
锁的状态

该代码块包含在
try
/
finally
块中,该块的
finally
语句调用
监视器。如果
tuningLock
为true,则退出
。这意味着
Enter
try
块之间没有可中断的点

FWIW,该方法仍在.NET3.5中,并且在WCF3.5源代码(而不是.NET源代码)中可见。我还不知道4.0里有什么;但我会想象它会是一样的;即使部分结构的推动力不再存在,也没有理由改变工作代码

有关用于生成的
lock
的详细信息,请参阅

你知道为什么他们会为这一切烦恼吗

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

事实证明,如果对象已经锁定(如果未锁定,
TryEnter
仍然非常快,没有问题),那么
Monitor.TryEnter
是一个花费不菲的调用。因此,除了第一个线程外,所有线程都将经历缓慢

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


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

当然,这看起来像是一个值得提交给每日WTF的文件…你从哪里得到这个“源代码”?如果您使用Reflector、ILSpy或JustDecompile等工具通过反射来查看它,请记住,它显示的是优化IL的表示,它可能不是按照它显示的方式编写的。它可能不是用C#event写的…@PeterRitchie我在这里找到的,这可能指向下载.net 4.5 src的女士发布的src。虽然我不太关心src是否准确,而是尽量理解它。真的!这确实是一个严重的错误。也许正因为这个原因,这个方法在.NET4中被删除了。嗯,这基本上回答了这个问题。你怎么知道它被移除了?下载的源代码?Reflector在其汇编列表中显示了3.0和4.0版本。谷歌也只发现3.5中的方法看起来仍然存在于.NET4中。请看更新。有趣的是,它已经移动到我没有看到的DurableInstance。代码不同,但您发现的缺陷仍然存在。感谢您的详细解释。正如在Q中提到的,我怀疑finally是用于线程中止的,但我发现令人困惑的是它没有提供线程中止(或异常)的完全保护,因为
areQuotasBeingTunes=false
不是finally。您认为这可以通过在
are之后将(do WORK)部分移动到try块中来解决吗。
if(!Monitor.TryEnter(tuningLock)) return;
//
// DO WORK...
//
Monitor.Exit(tuningLock);
try{}
finally
{
  lockHeld = Monitor.TryEnter(tuningLock);
}