ManagementObjectSearcher导致onclick处理程序出现重新进入问题
本文关键字:新进入 问题 程序 导致 onclick 处理 ManagementObjectSearcher | 更新日期: 2023-09-27 18:01:09
我在保护一段代码时遇到了一个奇怪的问题。我的应用程序是一个托盘应用程序。我在类(ApplicationContext(中创建了一个NotifyIcon。我已经为NotifyIcon对象分配了一个气球单击处理程序和一个双击处理程序。还有一个上下文菜单,但我并没有显示所有代码。只有重要的部分。
public class SysTrayApplicationContext: ApplicationContext
{
private NotifyIcon notifyIcon;
private MainForm afDashBoardForm;
public SysTrayApplicationContext()
{
this.notifyIcon = new NotifyIcon();
this.notifyIcon.BalloonTipClicked += notifyIcon_BalloonTipClicked;
this.notifyIcon.MouseDoubleClick += notifyIcon_MouseDoubleClick;
// ... more code
}
两个处理程序都启动或创建/显示我的表单:
private void notifyIcon_MouseDoubleClick(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Left)
{
openDashboard();
}
}
private void notifyIcon_BalloonTipClicked(object sender, EventArgs e)
{
openDashboard();
}
private void openDashboard()
{
if (dashBoardForm != null)
{
log.Debug("Dashboard form created already, so Activate it");
dashBoardForm.Activate();
}
else
{
log.Debug("Dashboard form does not exist, create it");
dashBoardForm = new MainForm();
dashBoardForm.Show();
}
}
上面的代码有问题。可能超过1。问题:可以显示2个仪表板表单,这不是我想要的。如果用户在气球消息显示时双击托盘图标,则会导致openDashboard中出现竞争条件。我可以很容易地复制这个。因此,我在openDashboard代码中的代码周围添加了一个锁,令我惊讶的是,这并没有阻止2个仪表板表单的显示。我应该无法创建2个MainForms。我哪里错了?
这是带有锁定语句的更新代码:
private void openDashboard()
{
lock (dashBoardFormlocker)
{
if (dashBoardForm != null)
{
log.Debug("Dashboard form created already, so Activate it");
dashBoardForm.Activate();
}
else
{
log.Debug("Dashboard form does not exist, create it");
dashBoardForm = new MainForm();
dashBoardForm.Show();
}
}
}
注意:锁对象已添加到类中,并在构造函数中初始化。
private object dashBoardFormlocker;
更新:显示更多代码。代码就是这样开始的:
static void Main()
{
if (SingleInstance.Start())
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
XmlConfigurator.Configure();
// For a system tray application we don't want to create
// a form, we instead create a new ApplicationContext. The Run method takes
Application.Run(new SysTrayApplicationContext());
SingleInstance.Stop();
SingleInstance.Dispose();
}
}
}
更新2:提供更多代码以提高的清晰度
public partial class MainForm : Form
{
public MainForm()
{
log.Trace("MainForm constructor...");
InitializeComponent();
// ... code not shown
this.label_OSVersion.Text = getOSFriendlyName();
// .. more code
}
private string getOSFriendlyName()
{
try
{
string result = string.Empty;
var mgmtObj = (from x in new ManagementObjectSearcher("SELECT Caption FROM Win32_OperatingSystem").Get().OfType<ManagementObject>()
select x.GetPropertyValue("Caption")).FirstOrDefault();
result = mgmtObj != null ? mgmtObj.ToString() : string.Empty;
OperatingSystem os = Environment.OSVersion;
String sp = os.ServicePack ?? string.Empty;
return !string.IsNullOrWhiteSpace(result) ? result + sp : "Unknown";
}
catch (System.Exception ex)
{
log.Error("Error trying to get the OS version", ex);
return "Unknown";
}
}
}
主UI线程必须始终泵送一个消息循环,以支持来自COM组件的通信。因此,当您从UI线程执行阻塞操作(如锁定或加入线程(时(EDIT:根据Peter Duniho的修复进行编辑(,UI线程将进入"可警报"状态,允许COM调度特定类型的消息,这反过来可能会导致像您的场景中那样的重入问题。看看这个问题的答案(为什么在UI线程上输入锁会触发OnPaint事件?(,可以得到更准确的解释。
从ManagementObjectSearcher.Get
的源代码来看,有一个锁(在Initialize
内部(,由于您是从窗体的构造函数调用它的,因此当窗体的构造函数尚未完成时,它可能会导致第二个事件触发。对dashBoardFormlocker
变量的赋值只发生在构造函数完成之后,所以这可以解释为什么它在第二个条目中为null。
这个故事的寓意是永远不要在UI线程上执行阻塞操作。
如果没有一个好的、最小的、complete代码示例来可靠地再现问题,就不可能确切地知道问题是什么。但回答者tzachs的猜测似乎是合理的。如果是这样的话,你可以通过改变你的方法来解决你的问题,如下所示:
private bool _dashboardOpen;
private void openDashboard()
{
if (_dashboardOpen)
{
if (dashBoardForm != null)
{
log.Debug("Dashboard form created already, so Activate it");
dashBoardForm.Activate();
}
}
else
{
log.Debug("Dashboard form does not exist, create it");
_dashboardOpen = true;
dashBoardForm = new MainForm();
dashBoardForm.Show();
}
}
这样,任何试图打开窗口的重新进入者都会被检测到。请注意,在实际激活之前,您仍然需要检查null
;您无法激活尚未实际完成创建的窗口。对Show()
的后续调用无论如何都会处理激活,因此在可重入的情况下忽略激活应该无关紧要。