为什么代码不会死锁?

本文关键字:死锁 代码 为什么 | 更新日期: 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,并且MyListWPF UI上绑定到Datagrid
_myObjects只是一个随机的对象列表。

是因为OnPropertyChange只是通知UI,它必须从MyList中获取新数据,只是返回而不关心UI是否实际获取数据?
我知道OnPropertyChanged是在一个单独的线程上调用的,但UI存在于一个线程上(不是它O.o),所以得到通知的线程也是得到数据的线程。我还以为那把锁就不能被释放了呢?

为什么代码不会死锁?

这里的关键实现是PropertyChanged的处理程序确实在UI线程上调度了一些访问MyList的代码,但是它不等待它完成

所以,一个可能的事件顺序是这样的:

  1. StartAsync()获取了一个后台线程的锁。
  2. MyListPropertyChanged升高
  3. PropertyChanged的处理器调度UI线程访问MyList的代码。
  4. PropertyChanged的处理程序返回。
  5. 锁被释放
  6. UI线程试图访问MyList .
  7. 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什么也不做,因为请求锁的线程已经持有它了。