C# ManagementObjectSearcher导致onclick处理程序出现重入问题

C# ManagementObjectSearcher导致onclick处理程序出现重入问题,c#,multithreading,winforms,C#,Multithreading,Winforms,我在保护一段代码时遇到了一个奇怪的问题。我的应用程序是一个托盘应用程序。我在类(ApplicationContext)中创建一个NotifyIcon。我已经为NotifyIcon对象分配了一个气泡单击处理程序和一个双击处理程序。还有一个上下文菜单,但我没有显示所有代码。只有重要的部分 public class SysTrayApplicationContext: ApplicationContext { private NotifyIcon notifyIcon; priva

我在保护一段代码时遇到了一个奇怪的问题。我的应用程序是一个托盘应用程序。我在类(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();
            }
    }
上面的代码有问题。可能不止一个。问题:可能会显示2个仪表板表单,这不是我想要的。若用户在显示气泡消息时双击托盘图标,则会导致openDashboard中出现竞速情况。我可以很容易地复制这个。因此,我在openDashboard代码中的代码周围添加了一个锁,令我惊讶的是,这并没有阻止显示2个仪表板表单。我应该不能创建2个主窗体。我哪里出了问题

以下是更新后的带有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";
        }
    }
}
public分部类MainForm:Form
{
公共表格(
{
Trace(“MainForm构造函数…”);
初始化组件();
//…未显示代码
this.label_OSVersion.Text=getOSFriendlyName();
//…更多代码
}
私有字符串getOSFriendlyName()
{
尝试
{
字符串结果=string.Empty;
var mgmtObj=(来自新ManagementObjectSearcher中的x(“从Win32_OperatingSystem中选择标题”).Get()of type()
选择x.GetPropertyValue(“标题”).FirstOrDefault();
结果=mgmtObj!=null?mgmtObj.ToString():string.Empty;
OperatingSystem os=Environment.OSVersion;
String sp=os.ServicePack??String.Empty;
return!string.IsNullOrWhiteSpace(result)?result+sp:“未知”;
}
catch(System.Exception-ex)
{
log.Error(“尝试获取操作系统版本时出错”,例如);
返回“未知”;
}
}
}

主UI线程必须始终泵送消息循环,以支持COM组件的通信。 因此,当您从UI线程执行阻止操作(如锁定或加入线程)时,(编辑:根据Peter Duniho的修复进行编辑),UI线程将进入“可警报”状态,允许COM发送特定类型的消息,这反过来可能会导致重入问题,如您的场景。 请看这个问题的答案()以获得更准确的解释

查看
ManagementObjectSearcher.Get
的源代码,其中有一个锁(在
Initialize
中),由于您从表单的构造函数调用它,因此在表单的构造函数尚未完成时,它可能会导致第二个事件触发。对
dashBoardFormlocker
变量的赋值仅在构造函数完成后发生,因此这可以解释为什么在第二个条目上为null


这个故事的寓意是永远不要在UI线程上执行阻塞操作。

主UI线程必须始终泵送消息循环,以支持COM组件的通信。 因此,当您从UI线程执行阻止操作(如锁定或加入线程)时,(编辑:根据Peter Duniho的修复进行编辑),UI线程将进入“可警报”状态,允许COM发送特定类型的消息,这反过来可能会导致重入问题,如您的场景。 请看这个问题的答案()以获得更准确的解释

查看
ManagementObjectSearcher.Get
的源代码,其中有一个锁(在
Initialize
中),由于您从表单的构造函数调用它,因此在表单的构造函数尚未完成时,它可能会导致第二个事件触发。对
dashBoardFormlocker
变量的赋值仅在构造函数完成后发生,因此这可以解释为什么在第二个条目上为null

这个故事的寓意是永远不要在UI线程上执行阻塞操作。

如果不可靠地再现问题,就不可能确定问题是什么。但这似乎是合理的。如果是这样,您可以通过将方法更改为如下所示来解决问题:

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

如果不可靠地再现问题,就不可能确定问题是什么。但这似乎是合理的。如果是这样,您可以通过将方法更改为如下所示来解决问题:

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

这其中的多个线程在哪里?表单(以及几乎所有标准的Windows UI组件)共享一个线程,因此
lock
不会像您期望的那样工作。我原以为balloon click会在另一个线程上运行。为什么会这样?它向消息传递/窗口系统内的消息队列发布
WM_CLICK
(或任何内容)。消息泵拾取它并触发相应的事件。此外,NotifyIcon是否被视为标准UI组件?此应用程序没有主窗体。表格是新的,稍后会更新。是的。它仍然在同一个平台上工作