当你没有什么可调用的时候,如何在 STA 线程上调用一个方法?
How to invoke a method on the STA thread when you have nothing to invoke on?
这种情况很难解释。我有一个 Windows Forms 应用程序,它使用带有上下文菜单的通知图标。该应用程序可以配置为在不显示任何表单的情况下启动,或者用户可以关闭表单,只留下通知图标。从通知图标的上下文菜单条中选择“显示”选项将创建(如果关闭)/恢复(如果最小化)主窗体。现在一切正常,因为从菜单生成的事件在 STA 线程上 运行ning。问题是单实例实现。一个通知图标类型的应用程序真的应该只 运行ning 一个实例,否则托盘中会有多个通知图标,这将是一团糟。这是使用命名的 EventWaitHandle 完成的,还包括检测对 运行 第二个实例的尝试,当这种情况发生时,它会向主要 运行ning 实例发出信号,然后 restores/creates 主要形式:
public static bool InitSingleInstance(this Control control, string handleName, Action? NewInstanceAttempt = null)
{
EventWaitHandle ewh = new(false, EventResetMode.ManualReset, handleName, out bool isNew);
if (isNew)
{
Task.Run(() =>
{
while (!control.IsDisposed)
{
ewh.WaitOne();
ewh.Reset();
NewInstanceAttempt?.Invoke();
}
});
}
else
{
Task.Run(() =>
{
EventWaitHandle.SignalAndWait(ewh, ewh, 100, true);
}).Wait();
}
return isNew;
}
我遇到的问题是循环在另一个线程中等待来自第二个实例 运行 的信号,因此需要委托给 STA 线程以 restore/create 形式。我使用 UserControl 来包含 NotifyIcon 和 Menu(因此我可以在设计器中编辑它们)并且这是在 Program.cs 中创建的,而不是 Application.Run(new Form()) 但实际上这个控件从来没有显示自己所以它永远不会得到分配给它的句柄我可以调用所以当第二个实例是 运行.
时我得到一个异常
public partial class NotifyIconAndMenu : UserControl
{
private Form? mainForm = null;
private FormWindowState mainFormState = FormWindowState.Normal;
private readonly Func<Form> NewForm;
public NotifyIconAndMenu(Func<Form> newForm)
{
NewForm = newForm;
if(!this.InitSingleInstance("FOOBFOOB387846", NewInstanceAttempt))
return;
InitializeComponent();
Application.Run();
}
private void ShowMainForm()
{
if (mainForm == null || mainForm.IsDisposed)
{
mainForm = NewForm();
mainForm.Visible = true;
mainForm.Resize += MainForm_Resize;
}
mainForm.WindowState = mainFormState;
}
private void Quit()
{
Dispose();
Application.Exit();
}
private void MainForm_Resize(object? sender, EventArgs e)
{
if (mainForm != null && mainForm.WindowState != FormWindowState.Minimized)
mainFormState = mainForm.WindowState;
}
private void NewInstanceAttempt() => Invoke(ShowMainForm); // << exception
private void IconMenu_ItemClicked(object sender, ToolStripItemClickedEventArgs e)
{
if (e.ClickedItem == MI_Show) ShowMainForm();
else
if (e.ClickedItem == MI_Quit) Quit();
}
}
也许有更好的方法来做到这一点?我确实想到 运行 在等待锁定对象的构造函数中设置一个循环,并从另一个线程发出脉冲。像这样
public NotifyIconAndMenu(Func<Form> newForm)
{
NewForm = newForm;
if (!this.InitSingleInstance("NotIconDemo387846", NewInstanceAttempt))
return;
InitializeComponent();
lock (this)
{
while (!IsDisposed)
{
Monitor.Wait(this);
ShowMainForm();
}
}
}
private void NewInstanceAttempt()
{
lock(this)
{
Monitor.Pulse(this);
}
}
但这看起来很乱。
编辑:确实不会工作,因为它会锁定 STA 线程。
我是这样解决的:
public partial class NotifyIconAndMenu : UserControl
{
private Form? mainForm = null;
private FormWindowState mainFormState = FormWindowState.Normal;
private readonly Func<Form> NewForm;
public NotifyIconAndMenu(Func<Form> newForm)
{
NewForm = newForm;
if (!this.InitSingleInstance("GROWPLENTYOFCHEESE4444", NewInstanceAttempt))
return;
InitializeComponent();
FormShowLoop();
Application.Run();
}
private async void FormShowLoop()
{
while (!IsDisposed)
{
await Task.Run(() =>
{
lock (this)
{
Monitor.Wait(this);
}
});
if(!IsDisposed)
ShowMainForm();
}
}
private void ShowMainForm()
{
if (mainForm == null || mainForm.IsDisposed)
{
mainForm = NewForm();
mainForm.Resize += MainForm_Resize;
mainForm.Visible = true;
}
mainForm.WindowState = mainFormState;
}
private void Pulse()
{
lock (this)
{
Monitor.Pulse(this);
}
}
private void Quit()
{
Dispose();
Pulse();
Application.Exit();
}
private void MainForm_Resize(object? sender, EventArgs e)
{
if (mainForm != null && mainForm.WindowState != FormWindowState.Minimized)
mainFormState = mainForm.WindowState;
}
private void NewInstanceAttempt() => Pulse();
private void IconMenu_ItemClicked(object sender, ToolStripItemClickedEventArgs e)
{
if (e.ClickedItem == MI_Show) Pulse();
else
if (e.ClickedItem == MI_Quit) Quit();
}
}
这种情况很难解释。我有一个 Windows Forms 应用程序,它使用带有上下文菜单的通知图标。该应用程序可以配置为在不显示任何表单的情况下启动,或者用户可以关闭表单,只留下通知图标。从通知图标的上下文菜单条中选择“显示”选项将创建(如果关闭)/恢复(如果最小化)主窗体。现在一切正常,因为从菜单生成的事件在 STA 线程上 运行ning。问题是单实例实现。一个通知图标类型的应用程序真的应该只 运行ning 一个实例,否则托盘中会有多个通知图标,这将是一团糟。这是使用命名的 EventWaitHandle 完成的,还包括检测对 运行 第二个实例的尝试,当这种情况发生时,它会向主要 运行ning 实例发出信号,然后 restores/creates 主要形式:
public static bool InitSingleInstance(this Control control, string handleName, Action? NewInstanceAttempt = null)
{
EventWaitHandle ewh = new(false, EventResetMode.ManualReset, handleName, out bool isNew);
if (isNew)
{
Task.Run(() =>
{
while (!control.IsDisposed)
{
ewh.WaitOne();
ewh.Reset();
NewInstanceAttempt?.Invoke();
}
});
}
else
{
Task.Run(() =>
{
EventWaitHandle.SignalAndWait(ewh, ewh, 100, true);
}).Wait();
}
return isNew;
}
我遇到的问题是循环在另一个线程中等待来自第二个实例 运行 的信号,因此需要委托给 STA 线程以 restore/create 形式。我使用 UserControl 来包含 NotifyIcon 和 Menu(因此我可以在设计器中编辑它们)并且这是在 Program.cs 中创建的,而不是 Application.Run(new Form()) 但实际上这个控件从来没有显示自己所以它永远不会得到分配给它的句柄我可以调用所以当第二个实例是 运行.
时我得到一个异常public partial class NotifyIconAndMenu : UserControl
{
private Form? mainForm = null;
private FormWindowState mainFormState = FormWindowState.Normal;
private readonly Func<Form> NewForm;
public NotifyIconAndMenu(Func<Form> newForm)
{
NewForm = newForm;
if(!this.InitSingleInstance("FOOBFOOB387846", NewInstanceAttempt))
return;
InitializeComponent();
Application.Run();
}
private void ShowMainForm()
{
if (mainForm == null || mainForm.IsDisposed)
{
mainForm = NewForm();
mainForm.Visible = true;
mainForm.Resize += MainForm_Resize;
}
mainForm.WindowState = mainFormState;
}
private void Quit()
{
Dispose();
Application.Exit();
}
private void MainForm_Resize(object? sender, EventArgs e)
{
if (mainForm != null && mainForm.WindowState != FormWindowState.Minimized)
mainFormState = mainForm.WindowState;
}
private void NewInstanceAttempt() => Invoke(ShowMainForm); // << exception
private void IconMenu_ItemClicked(object sender, ToolStripItemClickedEventArgs e)
{
if (e.ClickedItem == MI_Show) ShowMainForm();
else
if (e.ClickedItem == MI_Quit) Quit();
}
}
也许有更好的方法来做到这一点?我确实想到 运行 在等待锁定对象的构造函数中设置一个循环,并从另一个线程发出脉冲。像这样
public NotifyIconAndMenu(Func<Form> newForm)
{
NewForm = newForm;
if (!this.InitSingleInstance("NotIconDemo387846", NewInstanceAttempt))
return;
InitializeComponent();
lock (this)
{
while (!IsDisposed)
{
Monitor.Wait(this);
ShowMainForm();
}
}
}
private void NewInstanceAttempt()
{
lock(this)
{
Monitor.Pulse(this);
}
}
但这看起来很乱。 编辑:确实不会工作,因为它会锁定 STA 线程。
我是这样解决的:
public partial class NotifyIconAndMenu : UserControl
{
private Form? mainForm = null;
private FormWindowState mainFormState = FormWindowState.Normal;
private readonly Func<Form> NewForm;
public NotifyIconAndMenu(Func<Form> newForm)
{
NewForm = newForm;
if (!this.InitSingleInstance("GROWPLENTYOFCHEESE4444", NewInstanceAttempt))
return;
InitializeComponent();
FormShowLoop();
Application.Run();
}
private async void FormShowLoop()
{
while (!IsDisposed)
{
await Task.Run(() =>
{
lock (this)
{
Monitor.Wait(this);
}
});
if(!IsDisposed)
ShowMainForm();
}
}
private void ShowMainForm()
{
if (mainForm == null || mainForm.IsDisposed)
{
mainForm = NewForm();
mainForm.Resize += MainForm_Resize;
mainForm.Visible = true;
}
mainForm.WindowState = mainFormState;
}
private void Pulse()
{
lock (this)
{
Monitor.Pulse(this);
}
}
private void Quit()
{
Dispose();
Pulse();
Application.Exit();
}
private void MainForm_Resize(object? sender, EventArgs e)
{
if (mainForm != null && mainForm.WindowState != FormWindowState.Minimized)
mainFormState = mainForm.WindowState;
}
private void NewInstanceAttempt() => Pulse();
private void IconMenu_ItemClicked(object sender, ToolStripItemClickedEventArgs e)
{
if (e.ClickedItem == MI_Show) Pulse();
else
if (e.ClickedItem == MI_Quit) Quit();
}
}