ManagementObjectSearcher导致onclick处理程序

时间:2015-07-06 13:48:18

标签: c# multithreading winforms

我在保护一段代码方面遇到了一个奇怪的问题。我的应用程序是托盘应用程序。我在我的类(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。我在哪里错了?

这是带有lock语句的更新代码:

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

2 个答案:

答案 0 :(得分:3)

主UI线程必须始终泵送消息循环以支持COM组件的通信。 因此,当您从UI线程执行阻塞操作(如锁定或加入线程)时(编辑:基于Peter Duniho的修复编辑),UI线程将进入“可警告”状态,允许COM调度某些类型的消息,转向会导致像您的场景中出现的重入问题。 请查看此问题的答案(Why did entering a lock on a UI thread trigger an OnPaint event?),以获得更准确的解释。

查看ignore: ['.git/', 'node_modules/', 'bower_components/', '.sass-cache/'], 的源代码,有一个锁(在ManagementObjectSearcher.Get内),并且因为你从表单的构造函数中调用它,它可能导致第二个事件在表单的时候触发构造函数还没有完成。 Initialize变量的赋值仅在构造函数完成后发生,这样才能解释为什么它在第二个条目上为null。

故事的寓意永远不会阻止UI线程上的操作。

答案 1 :(得分:1)

如果没有a good, minimal, complete code example可靠地再现问题,就无法确定问题是什么。但是the guess by answerer 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()的调用将负责激活,因此忽略重入案例中的激活并不重要。