在调用/开始调用期间锁会发生什么?(事件调度)

本文关键字:调用 什么 调度 事件 开始 | 更新日期: 2023-09-27 18:26:49

只是为了把问题放在前面(请不要评论糟糕的架构或如何修改 - 假设这就是它(:

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

假设我有以下绑定列表,我需要在 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的后台线程进行锁定
  3. 后台线程将"使用 AllItems 进行一些计算">
  4. 后台线程从传入MyFunc传入pitem调用AddToArray
  5. 后台线程从AddToArray调用DoInGuiThread
  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 之后的代码将不包含数组中的项。

解决此问题的"通常"方法是使用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,你调用AddToArrayDoInGuiThread传入一个委托。 DoInGuiThread会注意到不需要调用并调用委托。在现在持有锁的同一线程上执行的委托被允许在调用 AllItems.Add 之前再次进入锁。

所以这里没有问题。

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

MyFunc首先取锁,调用AddToArray,该DoInGuiThread传递委托。由于DoInGuiThread现在检测到它需要调用它BeginInvoke传入委托

此委托通过消息的方式在 GUI 线程上排队。这就是事情再次分歧的地方。假设 GUI 线程当前很忙,因此它在短时间内无法处理消息(在此上下文中,这意味着"足以让本解释的其余部分展开"(。

DoInGuiThread ,完成了它的工作,回来了。邮件尚未处理。 DoInGuiThread返回到AddToArray现在返回到释放锁MyFunc

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

现在

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

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