gpt4 book ai didi

c# - 带管理等待的 Gui 重入

转载 作者:行者123 更新时间:2023-11-30 13:51:55 27 4
gpt4 key购买 nike

我在使用 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 的消息会被抽走。还有一种方法可以避免在进入/退出方法时不使用某些 bool 指示符而进行泵送吗?

最佳答案

如果您将 MessageBox.Show 调用替换为 Debugger.Break 并附加一个在中断命中时启用 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# - 带管理等待的 Gui 重入,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/3650571/

27 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com