使用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);
        }
    }
}

现在,如果您开始单击notifyicon,则会弹出消息,指示重新进入。
我知道为什么STA中的托管等待应该为某些窗口泵送消息的原因。但是我不确定为什么notifyicon的消息会被发送。还有没有一种方法可以避免在进入/退出方法时不使用某些 bool 指示符的抽水?

最佳答案

如果您将Debugger.Break替换为MessageBox.Show调用,并在中断发生时启用调试器,并且启用了 native 调试,则可以看到发生了什么。调用堆栈如下所示:

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;
    }

问题解决了。

关于c# - 桂再入与托管等待,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/3650571/

10-09 04:47