繁体   English   中英

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

[英]What happens to a lock during an Invoke/BeginInvoke? (event dispatching)

只是要预先提出问题(请不要评论不良体系结构或如何修改-假设是这样):

  1. 使用Invoke / BeginInvoke时,“ lock”语句如何应用
  2. 以下代码会导致死锁吗?

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

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();
    }
 }

直到您离开lock块,该锁才会被保持,您当前的代码不会导致死锁,但是它也无法正常工作。

这是事件的顺序:

  1. 在后台线程上,您调用MyFunc
  2. 对象locker的后台线程被locker
  3. 后台线程将“对AllItems进行一些计算”
  4. 后台线程从MyFunc调用AddToArray调用pitem
  5. 后台线程调用DoInGuiThreadAddToArray
  6. 后台线程从DoInGuiThread调用BeginInvoke ,该线程不会阻塞,我将使用A表示后台线程,使用B表示UI线程,这两者都是同时发生的。

  7. A) BeginInvoke从它的调用返回,因为它是非阻塞的。
    B)UI命中lock (locker)并阻塞,因为该锁由后台线程持有。

  8. A) DoInGuiThread返回。
    B)UI仍被锁定,等待后台线程释放锁。
  9. A) AddToArray返回。
    B)UI仍被锁定,等待后台线程释放锁。
  10. A)后台线程将“使用AllItems的内容更新其他一些结构”(注意, pitem尚未添加到AllItems
    B)UI仍被锁定,等待后台线程释放锁。
  11. A)后台线程释放对象locker
    B)UI线程获取对象locker
  12. A) MyFunc返回。
    B)将pitem添加到AllItems
  13. A) MyFunc继续运行代码
    B)UI线程释放对象locker
  14. A) MyFunc继续运行代码
    B)UI线程返回到消息泵以处理新消息,并且不再看上去被用户“锁定”。

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

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

  1. 后台线程从DoInGuiThread调用Invoke
  2. A) Invoke等待B返回消息泵。
    B)UI命中lock (locker)并阻塞,因为该锁由后台线程持有。
  3. A) Invoke等待B返回消息泵。
    B)UI仍被锁定,等待后台线程释放锁。
  4. A) Invoke等待B返回消息泵。
    B)UI仍被锁定,等待后台线程释放锁。
  5. A) Invoke等待B返回消息泵。
    B)UI仍被锁定,等待后台线程释放锁。

(这将永远重复)

这种情况可能会以两种不同的方式发生。

  1. 您正在GUI线程上进行所有这些操作
  2. 您正在其他线程上启动此调用链

让我们先处理第一个。

在这种情况下,不会有问题。 您在MyFunc了锁定,然后调用AddToArray ,后者调用DoInGuiThread传入委托的DoInGuiThread DoInGuiThread将注意到不需要调用,并调用委托。 在现在持有锁的同一线程上执行的委托,在调用AllItems.Add之前,可以再次输入锁。

所以这里没问题。

现在,在第二种情况下,您可以在其他线程上启动此调用链。

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

该委托通过消息在GUI线程上排队。 这是事情再次发生分歧的地方。 假设GUI线程当前正忙,因此它一小段时间将无法处理消息(在这种情况下,这意味着“足以让其余的解释得以展开”)。

DoInGuiThread完成工作后返回。 该消息尚未处理。 DoInGuiThread返回到AddToArray ,后者现在返回到MyFunc ,从而释放锁定。

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

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

换句话说,GUI线程将在委托内部阻塞,等待释放锁,以便可以由委托中的代码输入该锁。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM