[英]What happens to a lock during an Invoke/BeginInvoke? (event dispatching)
只是要预先提出问题(请不要评论不良体系结构或如何修改-假设是这样):
- 使用Invoke / BeginInvoke时,“ lock”语句如何应用
- 以下代码会导致死锁吗?
假设我有以下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
块,该锁才会被保持,您当前的代码不会导致死锁,但是它也无法正常工作。
这是事件的顺序:
- 在后台线程上,您调用
MyFunc
。- 对象
locker
的后台线程被locker
- 后台线程将“对AllItems进行一些计算”
- 后台线程从
MyFunc
调用AddToArray
调用pitem
- 后台线程调用
DoInGuiThread
从AddToArray
后台线程从
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
将对象添加到数组中,因此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仍被锁定,等待后台线程释放锁。(这将永远重复)
这种情况可能会以两种不同的方式发生。
让我们先处理第一个。
在这种情况下,不会有问题。 您在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.