我在保护一段代码方面遇到了一个奇怪的问题。我的应用程序是托盘应用程序。我在我的类(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";
}
}
}
答案 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()
的调用将负责激活,因此忽略重入案例中的激活并不重要。