在使用NotifyIcons时,我发现了一个可重入性问题。它真的很容易重现,只需将一个NotiftIcon放在窗体上,单击事件应该如下所示:
private bool reentrancyDetected;
private void notifyIcon1_MouseClick(object sender, MouseEventArgs e)
{
if (reentrancyDetected) MessageBox.Show("Reentrancy");
reentrancyDetected = true;
lock (thisLock)
{
//do nothing
}
reentrancyDetected = false;
}同时启动一个后台线程,这会引起一些争用:
private readonly object thisLock = new object();
private readonly Thread bgThread;
public Form1()
{
InitializeComponent();
bgThread = new Thread(BackgroundOp) { IsBackground = true };
bgThread.Start();
}
private void BackgroundOp()
{
while (true)
{
lock (thisLock)
{
Thread.Sleep(2000);
}
}
}现在,如果您开始单击通知图标,消息将弹出,指示可重入性。我知道STA中的托管等待应该为某些窗口发送消息的原因。但我不确定为什么notifyicon的消息会大量涌现。另外,有没有一种方法可以避免在进入/退出方法时不使用一些布尔指示器的抽水?
发布于 2010-09-06 19:42:31
如果将MessageBox.Show调用替换为Debugger.Break,并附加一个在命中中断时启用了本机调试的调试器,则可以看到发生了什么。调用堆栈如下所示:
WindowsFormsApplication3.exe!WindowsFormsApplication3.Form1.notifyIcon1_MouseClick(object sender = {System.Windows.Forms.NotifyIcon}, System.Windows.Forms.MouseEventArgs e = {X = 0x00000000 Y = 0x00000000 Button = Left}) Line 30 + 0x1e bytes C#
System.Windows.Forms.dll!System.Windows.Forms.NotifyIcon.OnMouseClick(System.Windows.Forms.MouseEventArgs mea) + 0x6d bytes
System.Windows.Forms.dll!System.Windows.Forms.NotifyIcon.WmMouseUp(ref System.Windows.Forms.Message m, System.Windows.Forms.MouseButtons button) + 0x7e bytes
System.Windows.Forms.dll!System.Windows.Forms.NotifyIcon.WndProc(ref System.Windows.Forms.Message msg) + 0xb3 bytes
System.Windows.Forms.dll!System.Windows.Forms.NotifyIcon.NotifyIconNativeWindow.WndProc(ref System.Windows.Forms.Message m) + 0xc bytes
System.Windows.Forms.dll!System.Windows.Forms.NativeWindow.Callback(System.IntPtr hWnd, int msg = 0x00000800, System.IntPtr wparam, System.IntPtr lparam) + 0x5a bytes
user32.dll!_InternalCallWinProc@20() + 0x23 bytes
user32.dll!_UserCallWinProcCheckWow@32() + 0xb3 bytes
user32.dll!_DispatchClientMessage@20() + 0x4b bytes
user32.dll!___fnDWORD@4() + 0x24 bytes
ntdll.dll!_KiUserCallbackDispatcher@12() + 0x2e bytes
user32.dll!_NtUserPeekMessage@20() + 0xc bytes
user32.dll!__PeekMessage@24() + 0x2d bytes
user32.dll!_PeekMessageW@20() + 0xf4 bytes
ole32.dll!CCliModalLoop::MyPeekMessage() + 0x30 bytes
ole32.dll!CCliModalLoop::PeekRPCAndDDEMessage() + 0x30 bytes
ole32.dll!CCliModalLoop::FindMessage() + 0x30 bytes
ole32.dll!CCliModalLoop::HandleWakeForMsg() + 0x41 bytes
ole32.dll!CCliModalLoop::BlockFn() - 0x5df7 bytes
ole32.dll!_CoWaitForMultipleHandles@20() - 0x51b9 bytes
WindowsFormsApplication3.exe!WindowsFormsApplication3.Form1.notifyIcon1_MouseClick(object sender = {System.Windows.Forms.NotifyIcon}, System.Windows.Forms.MouseEventArgs e = {X = 0x00000000 Y = 0x00000000 Button = Left}) Line 32 + 0x14 bytes C#相关的函数是CoWaitForMultipleHandles。它确保STA线程在不继续发送消息的情况下不会在同步对象上阻塞。这是非常不健康的,因为它很可能导致死锁。特别是在NotifyIcon的情况下,因为阻止通知消息将挂起托盘窗口,使得所有图标都不起作用。
您接下来看到的是COM模式循环,它因导致可重入性问题而臭名昭著。注意它是如何调用PeekMessage()的,这就是MouseClick事件处理程序再次被激活的方式。
这个调用堆栈非常令人震惊的是,没有证据表明lock语句会转换为调用CoWaitForMultipleHandles的代码。它在某种程度上是由Windows自己完成的,我相当确定CLR没有为它做任何规定。至少在SSCLI20版本中不是这样。这表明Windows实际上具有一些关于CLR如何实现Monitor类的内置知识。非常棒的东西,不知道他们是怎么做到的。我怀疑它修补了DLL入口点地址来审查代码。
无论如何,这些特殊的应对措施只有在NotifyIcon通知运行时才有效。解决方法是延迟事件处理程序的操作,直到回调完成。如下所示:
private void notifyIcon1_MouseClick(object sender, MouseEventArgs e) {
this.BeginInvoke(new MethodInvoker(delayedClick));
}
private void delayedClick() {
if (reentrancyDetected) System.Diagnostics.Debugger.Break();
reentrancyDetected = true;
lock (thisLock) {
//do nothing
}
reentrancyDetected = false;
}问题解决了。
发布于 2011-02-25 04:00:23
我也遇到过同样的问题,您实际上可以通过实现一个SynchronizationContext并将其设置为当前的一个来覆盖所有.NET等待调用的行为。
http://msdn.microsoft.com/en-us/library/system.threading.synchronizationcontext.aspx
如果您将IsWaitNotificationRequired属性设置为true,那么当框架需要执行等待调用时,框架将随时调用SynchronizationContext上的等待方法。
文档有点缺乏,但基本上wait的默认行为是调用CoWaitForMultipleHandles并返回结果。您可以在此处使用适当的标志执行自己的消息抽取和MsgWaitForMultipleObjects,以避免在等待期间调度WM_PAINT。
https://stackoverflow.com/questions/3650571
复制相似问题