C# 为什么';避免代码死锁

C# 为什么';避免代码死锁,c#,wpf,mvvm,locking,task-parallel-library,C#,Wpf,Mvvm,Locking,Task Parallel Library,请解释一下为什么这个代码不会导致死锁 static Object listlock = new Object(); void StartAsync() { System.Threading.Tasks.Task.Factory.StartNew(() => { lock(listlock) base.OnPropertyChanged("MyList"); }); } public Ob

请解释一下为什么这个代码不会导致死锁

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);
    }
 }
静态对象列表锁=新对象();
void StartAsync()
{
System.Threading.Tasks.Task.Factory.StartNew(()=>
{
锁(列表锁)
基于财产变更(“MyList”);
});
}
公共可观测集合MyList
{
得到
{
锁(列表锁)
返回新的ObservableCollection(_myObjectList);
}
}
一些背景细节:
程序正在使用MVVM模式,并且MyList绑定到WPF UI上的Datagrid
\u myObjects只是对象的随机列表。


是因为OnPropertyChange
只是通知UI它必须从MyList获取新数据并返回而不关心UI是否实际获取数据吗?
我知道OnPropertyChanged是在单独的线程上调用的,但UI存在于单个线程上(不是吗O.O)因此,得到通知的线程也是获得数据的线程。我原以为锁不会因此而释放?

要在单个锁上发生死锁,需要两个线程,第一个线程抓住锁,然后等待第二个线程抓住同一个锁。否则不会出现死锁(即,
lock
语句中没有等待其他线程,或者只涉及一个线程)

未来读者注意——以下不是WPF案例中发生的情况——请参见svick的答案。只是在同一线程上有两个锁的无死锁的一般示例:


一种可能的情况是,OnPropertyChanged的侦听器调用
MyList
,以响应同一线程上的同步通知(调用
MyList
时签出调用堆栈)。在这种情况下,嵌套的
lock
不会执行任何操作,因为请求锁的线程已经持有它。

要在单个锁上发生死锁,需要两个线程,第一个线程抓住锁,然后等待第二个线程抓住同一个锁。否则不会出现死锁(即,
lock
语句中没有等待其他线程,或者只涉及一个线程)

未来读者注意——以下不是WPF案例中发生的情况——请参见svick的答案。只是在同一线程上有两个锁的无死锁的一般示例:


一种可能的情况是,OnPropertyChanged的侦听器调用
MyList
,以响应同一线程上的同步通知(调用
MyList
时签出调用堆栈)。在这种情况下,嵌套的
lock
什么也不做,因为请求锁的线程已经持有它。

这里的关键实现是
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
        }
    });
    
    但事实上,它的作用类似于(唯一的区别是第一行):


    这里的关键实现是
    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
        }
    });
    
    但事实上,它的作用类似于(唯一的区别是第一行):


    我不是100%确定,但我认为property changed事件只是为wpf绑定引擎添加了一个dispatcher作业,以便稍后在另一个dispatcher作业中检索它。因此,是的,您的锁被释放,因为当事件被引发时,它还没有调用getter。如果您仍然在同一调用堆栈中,则可以在调试器中检查这一点。同样,我也不确定实际情况是否如此。我不是100%确定,但我认为property changed事件只是为wpf绑定引擎添加了一个dispatcher作业,以便稍后在另一个dispatcher作业中检索它。因此,是的,您的锁被释放,因为当事件被引发时,它还没有调用getter。如果您仍然在同一调用堆栈中,则可以在调试器中检查这一点。我也不确定事实是否如此。@svick,你的回答似乎非常接近现实。出于历史原因保留这一条。@svick,你的答案似乎非常接近现实。出于历史原因保留这一条。那么我当时的假设是半正确的?OnPropertyChanged简单地告诉UI获取新数据,然后返回,因此正如您所说的那样