获取锁的说明

时间:2012-01-17 14:04:42

标签: c# .net multithreading locking

我用C#编写了很长一段时间,但是这个锁定序列对我来说没有任何意义。我对锁定的理解是,一旦使用lock(object)获得锁定,代码就必须退出锁定范围以解锁对象。

这让我想到了手头的问题。我删除了下面的代码,该代码碰巧出现在我的代码中的动画类中。该方法的工作方式是将设置传递给方法并进行修改,然后传递给另一个重载方法。其他重载方法将所有信息传递给另一个线程来处理并以某种方式实际动画对象。动画完成后,另一个线程调用OnComplete方法。这实际上都是完美,但我不明白为什么

另一个线程能够调用OnComplete,获取对象的锁定并向原始线程发出信号,告知它应该继续。代码是否应该在此时冻结,因为对象被锁定在另一个线程上?

因此,在修复代码时不需要帮助,需要澄清其工作原理。非常感谢任何理解方面的帮助!

public void tween(string type, object to, JsDictionaryObject properties) {
    // Settings class that has a delegate field OnComplete.
    Tween.Settings settings = new Tween.Settings();
    object wait_object = new object();

    settings.OnComplete = () => {
        // Why are we able to obtain a lock when the wait_object already has a lock below?
        lock(wait_object) {
            // Let the waiting thread know it is ok to continue now.
            Monitor.Pulse(wait_object);
        }
    };

    // Send settings to other thread and start the animation.
    tween(type, null, to, settings);

    // Obtain a lock to ensure that the wait object is in synchronous code.
    lock(wait_object) {
        // Wait here if the script tells us to.  Time out with total duration time + one second to ensure that we actually DO progress.
        Monitor.Wait(wait_object, settings.Duration + 1000);
    }
}

4 个答案:

答案 0 :(得分:3)

如文档所述,Monitor.Wait 发布调用它的监视器。因此,当您尝试获取OnComplete中的锁定时,将不会成为另一个持有锁定的线程。

当显示器发出脉冲(或呼叫超时)时,它会在返回之前重新获取它。

来自文档:

  

释放对象的锁定并阻止当前线程,直到它重新获取锁定。

答案 1 :(得分:1)

我写了一篇关于此的文章:Wait and Pulse demystified

还有更多事情发生,而不是满足于眼睛!

答案 2 :(得分:1)

请记住:

lock(someObj)
{
  int uselessDemoCode = 3;
}

相当于:

Monitor.Enter(someObj);
try
{
  int uselessDemoCode = 3;
}
finally
{
  Monitor.Exit(someObj);
}

实际上,这种变体因版本而异。

很明显,我们可以把它搞乱:

lock(someObj)
{
   Monitor.Exit(someObj);
   //Don't have the lock here!
   Monitor.Enter(someObj);
   //Have the lock again!
}

你可能想知道为什么有人会这样做,好吧,我也是如此,这是一种使代码不那么清晰和不太可靠的愚蠢方式,但是当你想要使用Pulse和{时,它会发挥作用{1}},明确WaitEnter来电的版本更清晰。就个人而言,如果因为这个原因我要前往Exitlock,我更愿意使用Pulse。我发现Wait停止使代码更清晰并开始使其变得不透明。

答案 3 :(得分:1)

我倾向于避免这种风格,但正如Jon已经说过的那样,Monitor.Wait会释放它所调用的监视器,因此此时没有锁定。

但是这个例子有点瑕疵恕我直言。问题通常是,如果Monitor.PulseMonitor.Wait之前调用,则等待线程将永远不会发出信号。考虑到这一点,作者决定“玩安全”并使用指定超时的重载。因此,抛开不必要的获取和释放锁定,代码感觉不对。

为了更好地解释这一点,请考虑以下修改:

public static void tween()
{
    object wait_object = new object();

    Action OnComplete = () =>
    {
        lock (wait_object)
        {
            Monitor.Pulse(wait_object);
        }
    };

    // let's say that a background thread
    // finished really quickly here
    OnComplete();

    lock (wait_object)
    {
        // this will wait for a Pulse indefinitely
        Monitor.Wait(wait_object);
    }
}

如果在主线程中获取锁之前调用OnComplete,并且没有超时,我们将遇到死锁。在您的情况下,Monitor.Wait会暂停一段时间并在超时后继续,但您明白了。

这就是为什么我通常建议采用更简单的方法:

public static void tween()
{
    using (AutoResetEvent evt = new AutoResetEvent(false))
    {
        Action OnComplete = () => evt.Set();

        // let's say that a background thread
        // finished really quickly here
        OnComplete();

        // event is properly set even in this case
        evt.WaitOne();
    }
}

引用MSDN

  

Monitor类不维护指示已调用Pulse方法的状态。因此,如果在没有线程等待时调用Pulse,则调用Wait块的下一个线程就像从未调用过Pulse一样。如果两个线程正在使用Pulse和Wait进行交互,则可能导致死锁。

     

将此与AutoResetEvent类的行为进行对比:如果通过调用其Set方法发出AutoResetEvent信号,并且没有线程在等待,则AutoResetEvent将保持信号状态,直到线程调用WaitOne,WaitAny或WaitAll。 AutoResetEvent释放该线程并返回到无信号状态。