关于获取锁的说明
本文关键字:说明 获取 于获取 | 更新日期: 2023-09-27 18:20:45
我已经用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);
}
}
如文档所述,Monitor.Wait
将释放与其调用的监视器。因此,当您尝试在OnComplete
中获取锁时,将不会有另一个线程持有锁。
当监视器出现脉冲(或呼叫超时)时,它会在返回之前重新获取它。
来自文档:
释放对象上的锁并阻塞当前线程,直到它重新获取锁为止。
我写了一篇关于这方面的文章:等待和脉冲解密
发生的事情远不止眼前所见!
请记住:
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
和Wait
时,它确实会发挥作用,而带有显式Enter
和Exit
调用的版本会更清晰。就我个人而言,如果我要使用Pulse
或Wait
,我更喜欢使用它们而不是lock
;我发现lock
停止了使代码更干净,并开始使其不透明。
我倾向于避免这种风格,但是,正如Jon已经说过的,Monitor.Wait
释放了它调用的监视器,所以在这一点上没有锁定。
但IMHO的例子有点缺陷。通常,问题是,如果Monitor.Pulse
在Monitor.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方法来向AutoResetEvents发出信号,并且没有线程在等待,则在线程调用WaitOne、WaitAny或WaitAll之前,AutoResetEvent将保持在发出信号的状态。AutoResetEvent释放该线程并返回到无信号状态。