为什么代码不会死锁?
本文关键字:死锁 代码 为什么 | 更新日期: 2023-09-27 18:05:58
谁能给我解释一下为什么这段代码不会导致死锁?
static Object listlock = new Object();
void StartAsync()
{
System.Threading.Tasks.Task.Factory.StartNew(() =>
{
lock(listlock)
base.OnPropertyChanged("MyList");
});
}
public ObservableCollection<MyObjects> MyList
{
get
{
lock(listlock)
return new ObservableCollection<MyObjects>(_myObjectList);
}
}
背景:
该程序正在使用MVVM Pattern,并且MyList在WPF UI上绑定到Datagrid。
_myObjects只是一个随机的对象列表。
是因为OnPropertyChange只是通知UI,它必须从MyList中获取新数据,只是返回而不关心UI是否实际获取数据?
我知道OnPropertyChanged是在一个单独的线程上调用的,但UI存在于一个线程上(不是它O.o),所以得到通知的线程也是得到数据的线程。我还以为那把锁就不能被释放了呢?
这里的关键实现是PropertyChanged
的处理程序确实在UI线程上调度了一些访问MyList
的代码,但是它不等待它完成。
所以,一个可能的事件顺序是这样的:
-
StartAsync()
获取了一个后台线程的锁。 -
MyList
的PropertyChanged
升高 -
PropertyChanged
的处理器调度UI线程访问MyList
的代码。 -
PropertyChanged
的处理程序返回。 - 锁被释放
- UI线程试图访问
MyList
. UI线程在没有等待的情况下获取锁,因为没有其他线程拥有它。 - …
另一种说法是:我认为您期望的是PropertyChanged
的处理程序做这样的事情:
Dispatcher.Invoke(() =>
{
if (e.PropertyName == "MyList")
{
var newList = model.MyList;
// set newList as the current value of some binding
}
});
但实际上,它是这样做的(唯一的区别是第一行):
Dispatcher.BeginInvoke(() =>
{
if (e.PropertyName == "MyList")
{
var newList = model.MyList;
// set newList as the current value of some binding
}
});
对于在单个锁上发生死锁,您需要两个线程以这样的方式,第一个线程获取锁,并等待第二个线程获取相同的锁。否则你不会得到死锁(即没有等待其他线程在 lock
语句中,或者只有一个线程参与)。
对未来读者的注意-下面不是WPF情况下发生的情况-参见svik的回答。在同一个线程上有两个锁而没有死锁的通用示例:
一个可能的情况是OnPropertyChanged的监听器调用MyList
以响应同一线程上的同步通知(当MyList
被调用时检查调用堆栈)。在这种情况下,嵌套的lock
什么也不做,因为请求锁的线程已经持有它了。