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

ManagementObjectSearcher导致onclick处理程序出现重新进入问题

主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()的后续调用无论如何都会处理激活,因此在可重入的情况下忽略激活应该无关紧要。