Skip to main content
 首页 » 编程设计

c#之在 Invoke/BeginInvoke 期间锁会发生什么情况 (事件派发)

2024年02月05日32cmt

Just to put the questions upfront (please no comments about the bad architecture or how to revise - suppose this is what it is):

  1. How does the "lock" statement apply when using Invoke/BeginInvoke
  2. Could the following code result in a deadlock?

假设我有以下需要在 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(); 
    } 
 } 

请您参考如下方法:

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

这是事件的顺序:

  1. On a background thread you call MyFunc.
  2. A lock is taken for the background thread for the object locker
  3. The background thread will "do some calculations with AllItems"
  4. The background thread calls AddToArray from MyFunc passing in pitem
  5. The background thread calls DoInGuiThread from AddToArray
  6. The background thread calls BeginInvoke from DoInGuiThread, the thread does not block, I will use A to signify the background thread and B to signify the UI thread, both of these are happening at the same time.

  7. A) BeginInvoke returns from it's call because it is non blocking.
    B) The UI hits lock (locker) and blocks because the lock is held by the background thread.

  8. A) DoInGuiThread returns.
    B) The UI is still locked up, waiting for the background thread to release the lock.
  9. A) AddToArray returns.
    B) The UI is still locked up, waiting for the background thread to release the lock.
  10. A) The background thread will "update some other structures with the contents of AllItems" (note, pitem has not yet been added to AllItems)
    B) The UI is still locked up, waiting for the background thread to release the lock.
  11. A) The background thread releases the lock for the object locker
    B) The UI thread takes the lock for the object locker
  12. A) MyFunc returns.
    B) pitem is added to AllItems
  13. A) Whoever called MyFunc continues to run code
    B) The UI thread releases the lock for the object locker
  14. A) Whoever called MyFunc continues to run code
    B) The UI thread returns to the message pump to process new messages and no longer appears to be "locked up" by the user.

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

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

  1. The background thread calls Invoke from DoInGuiThread
  2. A) Invoke waits for B to return to the message pump.
    B) The UI hits lock (locker) and blocks because the lock is held by the background thread.
  3. A) Invoke waits for B to return to the message pump.
    B) The UI is still locked up, waiting for the background thread to release the lock.
  4. A) Invoke waits for B to return to the message pump.
    B) The UI is still locked up, waiting for the background thread to release the lock.
  5. A) Invoke waits for B to return to the message pump.
    B) The UI is still locked up, waiting for the background thread to release the lock.

(This repeats forever)