在Invoke / BeginInvoke期间锁会发生什么? (事件调度)

时间:2015-12-14 20:16:57

标签: c# .net multithreading

  

只是提前提出问题(请不要评论糟糕的架构或如何修改 - 假设这就是它):

     
      
  1. "锁定"使用Invoke / BeginInvoke
  2. 时应用语句   
  3. 以下代码是否会导致死锁?
  4.   

假设我需要在GUI线程上更新以下BindingList:

var AllItems = new BindingList<Item>();

我想确保对它的所有更新都是同步的。 假设我有以下子程序进行一些计算,然后在BindingList中插入一个新条目:

private void MyFunc() {
  lock(locker) {
    ... //do some calculations with AllItems

    AddToArray(new Item(pos.ItemNo));

    ... //update some other structures with the contents of AllItems
    }
}

AddToArray看起来像:

private void AddToArray (Item pitem)
{
   DoInGuiThread(() =>
     {
       lock (locker)
         {
           AllItems.Add(pitem);
         }
      });
 }

DoInGuiThread看起来像:

 private void DoInGuiThread(Action action) {
   if(InvokeRequired) {
       BeginInvoke(action);
    } else {
       action.Invoke();
    }
 }

2 个答案:

答案 0 :(得分:2)

锁定一直持续到lock块,你当前的代码不会导致死锁,但它也无法正常工作。

以下是事件序列:

  
      
  1. 在后台主题中,您调用MyFunc
  2.   
  3. 对对象locker
  4. 的后台线程进行锁定   
  5. 后台线程将“使用AllItems进行一些计算”
  6.   
  7. 后台主题调用来自AddToArray传递MyFunc
  8. pitem DoInGuiThread   
  9. 后台线程从AddToArray
  10. 调用BeginInvoke   
  11. 后台线程从DoInGuiThread调用A,线程没有阻塞,我将使用B来表示后台线程   并且BeginInvoke表示UI线程,这两个都发生在   同一时间。

  12.   
  13. A)lock (locker)从它的通话中返回,因为它是非阻塞的   B)UI命中DoInGuiThread并阻止,因为锁定由。{   后台主题​​。

  14.   
  15. A)AddToArray返回。
      B)UI仍处于锁定状态,等待后台线程释放锁定。
  16.   
  17. A)pitem返回。
      B)UI仍处于锁定状态,等待后台线程释放锁定。
  18.   
  19. A)后台线程将“使用AllItems的内容更新其他一些结构”(注意,AllItems尚未添加到   locker
      B)UI仍处于锁定状态,等待后台线程释放锁定。
  20.   
  21. A)后台线程释放对象locker的锁定   B)UI线程获取对象MyFunc
  22. 的锁定   
  23. A)pitem返回。
      B)AllItems已添加到MyFunc
  24.   
  25. A)致电locker的人继续运行代码
      B)UI线程释放对象MyFunc
  26. 的锁   
  27. A)致电AddToArray的人继续运行代码
      B)UI线程返回到消息泵以处理新消息,并且不再被用户“锁定”。
  28.   

你看到了这个问题吗? MyFunc会返回,但在AddToArray结束之前,对象才会添加到数组中,因此Invoke之后的代码将不会包含数组中的项目。

解决此问题的“通常”方法是使用BeginInvoke而不是Invoke,但这会导致发生死锁。这是事件序列,最多6个步骤是相同的​​,将被跳过。

  
      
  1. 后台线程从DoInGuiThread
  2. 调用Invoke   
  3. A)lock (locker)等待B返回消息泵   B)用户界面点击Invoke并阻止,因为锁是由...持有的   背景线程。
  4.   
  5. A)Invoke等待B返回消息泵   B)UI仍处于锁定状态,等待后台线程释放   锁。
  6.   
  7. A)Invoke等待B返回消息泵   B)UI仍处于锁定状态,等待后台线程释放   锁。
  8.   
  9. A)URL url = new URL(urlString); HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection(); urlConnection.setRequestMethod("GET"); urlConnection.connect(); // Read the input stream into a String InputStream inputStream = urlConnection.getInputStream(); StringBuffer buffer = new StringBuffer(); if (inputStream == null) { // Nothing to do. return null; } BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream)); String line; while ((line = reader.readLine()) != null) { buffer.append(line + "\n"); } if (buffer.length() == 0) { return null; } String stringResult = buffer.toString(); 等待B返回消息泵   B)UI仍然被锁定,等待后台线程   释放锁。
  10.         

    (这永远重复)

答案 1 :(得分:1)

有两种不同的方式可能会下降。

  1. 您正在GUI线程上完成所有这些工作
  2. 您正在其他线程上启动此调用链
  3. 让我们先解决第一个问题。

    在这种情况下,不会有问题。您在MyFunc中取消锁定,调用AddToArray调用DoInGuiThread传递代理人。 DoInGuiThread会注意到不需要调用并调用委托。在调用AllItems.Add之前,允许在现在持有锁的同一线程上执行的委托再次进入锁。

    这里没问题。

    现在,第二种情况,你在其他一些线程上启动这个调用链。

    MyFunc首先获取锁定,调用AddToArray调用DoInGuiThread传递委托。由于DoInGuiThread现在检测到它需要调用它,因此调用传递委托的BeginInvoke

    此委托通过消息的方式在GUI线程上排队。在这里,事情再次发生分歧。让我们说GUI线程当前很忙,所以它不能在短时间内处理消息(在这种情况下意味着&#34;足以让其余的解释展开&#34; ;)

    完成工作的

    DoInGuiThread返回。邮件尚未处理。 DoInGuiThread返回AddToArray,现在返回MyFunc,释放锁定。

    当最终处理消息时,没有人拥有锁,因此允许被调用的委托进入锁。

    现在,如果消息最终在另一个线程设法完全退出锁之前被处理,那么现在在GUI线程上执行的委托只需要等待。

    换句话说,GUI线程会阻塞委托内部,等待锁被释放,因此可以通过代理中的代码输入。