在调用/开始调用期间锁会发生什么?(事件调度)
本文关键字:调用 什么 调度 事件 开始 | 更新日期: 2023-09-27 18:26:49
只是为了把问题放在前面(请不要评论糟糕的架构或如何修改 - 假设这就是它(:
- 使用 Invoke/BeginInvoke 时如何应用"lock"语句
- 以下代码会导致死锁吗?
假设我有以下绑定列表,我需要在 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
块,您当前的代码不会导致死锁,但它也无法正常工作。
以下是事件的顺序:
- 在后台线程上调用
MyFunc
.- 对对象
locker
的后台线程进行锁定- 后台线程将"使用 AllItems 进行一些计算">
- 后台线程从传入
MyFunc
传入pitem
调用AddToArray
- 后台线程从
AddToArray
调用DoInGuiThread
后台线程从
DoInGuiThread
调用BeginInvoke
,线程不阻塞,我会用A
来表示后台线程 和B
表示 UI 线程,这两者都发生在 同时。A(
BeginInvoke
从它的调用中返回,因为它是非阻塞的。
B( UI 命中lock (locker)
并阻止,因为锁由 后台线程。- A(
DoInGuiThread
回报。
B( UI 仍然处于锁定状态,等待后台线程释放锁定。- A(
AddToArray
回报。
B( UI 仍然处于锁定状态,等待后台线程释放锁定。- A(后台线程将"使用AllItems的内容更新一些其他结构"(注意,
pitem
尚未添加到AllItems
(
B( UI 仍然处于锁定状态,等待后台线程释放锁定。- A( 后台线程释放对象的锁
locker
B( UI 线程获取对象的锁locker
- A(
MyFunc
回报。
B(pitem
被添加到AllItems
- A( 无论谁调用
MyFunc
,都会继续运行代码
B( UI 线程释放对象的锁locker
- A( 无论谁调用
MyFunc
,都会继续运行代码
B( UI 线程返回到消息泵以处理新消息,并且不再显示为被用户"锁定"。
你看到问题了吗? AddToArray
返回,但对象直到MyFunc
结束才添加到数组中,因此 AddToArray
之后的代码将不包含数组中的项。
解决此问题的"通常"方法是使用Invoke
而不是BeginInvoke
但是这会导致死锁发生。这是事件的顺序,最多 6 步是相同的,将被跳过。
- 后台线程从
DoInGuiThread
调用Invoke
- A(
Invoke
等待 B 返回到消息泵。
B( UI 命中lock (locker)
并阻止,因为锁由 后台线程。- A(
Invoke
等待 B 返回到消息泵。
B( UI 仍处于锁定状态,等待后台线程释放 锁。- A(
Invoke
等待 B 返回到消息泵。
B( UI 仍处于锁定状态,等待后台线程释放 锁。- A(
Invoke
等待 B 返回到消息泵。
B(UI仍然被锁定,等待后台线程 松开锁。(这永远重复(
有两种不同的方式可能会下降。
- 您正在 GUI 线程上执行所有这些操作
- 您正在其他线程上启动此调用链
让我们先处理第一个。
在这种情况下,不会有问题。你把锁MyFunc
,你调用AddToArray
,DoInGuiThread
传入一个委托。 DoInGuiThread
会注意到不需要调用并调用委托。在现在持有锁的同一线程上执行的委托被允许在调用 AllItems.Add
之前再次进入锁。
所以这里没有问题。
现在,第二种情况,您在其他线程上启动此调用链。
MyFunc
首先取锁,调用AddToArray
,该DoInGuiThread
传递委托。由于DoInGuiThread
现在检测到它需要调用它BeginInvoke
传入委托
此委托通过消息的方式在 GUI 线程上排队。这就是事情再次分歧的地方。假设 GUI 线程当前很忙,因此它在短时间内无法处理消息(在此上下文中,这意味着"足以让本解释的其余部分展开"(。
DoInGuiThread
,完成了它的工作,回来了。邮件尚未处理。 DoInGuiThread
返回到AddToArray
现在返回到释放锁MyFunc
。
当消息最终被处理时,没有人拥有锁,因此被调用的委托被允许进入锁。
现在,如果消息在另一个线程设法完全返回锁之前被处理,那么现在在 GUI 线程上执行的委托只需要等待。
换句话说,GUI 线程将在委托内部阻塞,等待锁被释放,以便委托中的代码可以输入它。