使用辅助 UI 消息泵作为启动屏幕时出现异常

发布于 2024-09-10 12:06:16 字数 1759 浏览 2 评论 0原文

我在显示启动表单的方式上遇到了一个奇怪的问题,这会导致 InvalidAsynchronousStateException 被抛出。

首先,这是 Main{} 的代码,我在其中启动了启动窗体:

[STAThread]
static void Main(string[] args)
{
    Application.EnableVisualStyles();
    Application.SetCompatibleTextRenderingDefault(false);

    Thread splash = new Thread(new ThreadStart(ShowSplash));
    splash.Start();

     Application.Run(new MainForm());
}

static void ShowSplash()
{
    using (SplashForm splash = new SplashForm())
    {
        Application.Run(splash);
    }
}

我正在使用 .NET2.0 和 Win XP。

在一些测试中,应用程序运行了几个小时,我注意到异常的数量偶尔会增加一两个。 (通过 PerfMon 获得的数字,查看“抛出的异常数”计数器。)这些异常似乎被运行时捕获并吞掉,因为它们确实 不会产生涟漪并导致应用程序本身出现任何问题。至少我无法确定任何事情。

我发现当系统触发 UserPreferenceChanged 事件时会引发异常。自从发现这一点后,我可以生成异常 我自己并

没有在代码中的任何地方明确订阅此事件,但我了解(通过 Google 的力量)所有顶级控件和表单都会订阅 自动到此事件。

我仍然不确定为什么这个事件首先被触发,因为它似乎是在应用程序整夜运行时发生的,但我想这是另一个有待解决的谜团。

现在,如果我停止启动窗体线程的运行,异常就会消失。运行线程,它会回来。那么,似乎有些东西没有取消订阅该事件,这可能会导致随后的异常?

有趣的是,如果我用默认的开箱即用表单替换启动表单,问题仍然存在:

static void ShowSplash()
{
    using (Form splash = new Form())
    {
        Application.Run(splash);
    }
}

显示此表单时,任何 UserPreferenceChanged 事件都不会导致任何异常。一旦表单关闭并且线程退出,就会抛出异常。

进一步的研究让我看到了这篇 Microsoft 文章,其中包含以下评论:

常见原因是闪屏 在辅助 UI 线程上创建或 在工作人员上创建的任何控件 线程。

嗯,看起来有罪。请注意,我的应用程序没有冻结或执行任何异常操作。

目前,这更多的是出于好奇,但我担心将来可能会有一些隐藏的坏事等着咬人。

对我来说,由 Application.Run 启动的表单或消息泵在终止时似乎没有正确清理。

有什么想法吗?

I have encountered an odd issue with the way I am showing a splash form, that causes an InvalidAsynchronousStateException to be thrown.

First of all, here is the code for Main{} where I start the splash form:

[STAThread]
static void Main(string[] args)
{
    Application.EnableVisualStyles();
    Application.SetCompatibleTextRenderingDefault(false);

    Thread splash = new Thread(new ThreadStart(ShowSplash));
    splash.Start();

     Application.Run(new MainForm());
}

static void ShowSplash()
{
    using (SplashForm splash = new SplashForm())
    {
        Application.Run(splash);
    }
}

I am using .NET2.0, with Win XP.

During some testing where the app was left running for may hours, I noticed that the number of exceptions would occasionally increase by one or two.
(Numbers obtained by PerfMon, viewing '# of Exceps Thrown' counter.) These exceptions seem to be caught and swallowed by the runtime, because they do
not ripple up and cause anything to go wrong in the app itself. At least nothing that I can determine anyway.

I have discovered that the exception is thrown when the UserPreferenceChanged event is fired by the system. Since finding this out, I can generate the exception
at will by changing the background picture or screen saver, etc.

I am not explicitly subscribing to this event myself anywhere in code, but I understand (via the power of Google) that all top level controls and forms subscribe
to this event automatically.

I still have not determined why this event is being fired in the first place, as it appears to happen while the app is running over night, but I guess that is another mystery to be solved.

Now, if I stop the splash form thread from running, the exception disappears. Run the thread, it comes back. So, it appears that something is not unsubscribing from the event, and this is causing the subsequent exception perhaps?

Interestingly, if I substitute my splash form with a default, out of the box Form, the problem still remains:

static void ShowSplash()
{
    using (Form splash = new Form())
    {
        Application.Run(splash);
    }
}

While this form is being displayed, any UserPreferenceChanged events do not cause any exceptions. As soon as the form is closed, and the thread exits, exceptions will be thrown.

Further research has lead me to this Microsoft article, that contains the following comment:

Common causes are a splash screens
created on a secondary UI thread or
any controls created on worker
threads.

Hmm, guilty as charged by the looks of it. Note that my app is not freezing or doing anything untoward though.

At the moment, this is more of a curiosity than anything else, but I am conecerned that there may be some hidden nasties here waiting to bite in the future.

To me, it looks like the form or the message pump started by Application.Run is not cleaning up properly when it terminates.

Any thoughts?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

挖鼻大婶 2024-09-17 12:06:16

是的,您与 SystemEvents 类发生了冲突。该类创建一个监听系统事件的隐藏窗口。特别是 UserPreferenceChanged 事件,许多控件使用该事件来知道何时需要重新绘制自己,因为系统颜色已更改。

问题是,创建窗口的初始化代码对调用它的线程的单元状态非常敏感。在您的情况下这是错误的,您没有调用 Thread.SetApartmentState() 来切换到 STA。这对于显示 UI 的线程非常非常重要。

请注意,您的解决方法实际上并不是修复,系统事件将在错误的线程上引发。您的启动线程而不是程序的 UI 线程。当实际的系统事件被触发时,您仍然会遇到随机情况并且极难诊断故障。最臭名昭著的是,当用户锁定工作站时,再次解锁时程序会死锁。

我认为调用 Thread.SetApartmentState() 应该可以解决您的问题。不能 100% 确定,这些 UI 线程交互非常难以分析,而且我还没有弄错。请注意,.NET 已经对闪屏提供了非常可靠的支持。它确实得到了这样的细节。

Yes, you are running afoul with the SystemEvents class. That class creates a hidden window that listens to system events. Particularly the UserPreferenceChanged event, a lot of controls use that event to know when they need to repaint themselves because the system colors were changed.

Problem is, the initialization code that creates the window is very sensitive to the apartment state of the thread that calls it. Which in your case is wrong, you didn't call Thread.SetApartmentState() to switch to STA. That's very important for threads that display a UI.

Beware that your workaround isn't actually a fix, the system events will be raised on the wrong thread. Your splash thread instead of the UI thread of your program. You'll still get random and extremely hard to diagnose failure when an actual system event gets fired. Most infamously when the user locks the workstation, the program deadlocks when it is unlocked again.

I think calling Thread.SetApartmentState() should fix your problem. Not 100% sure, these UI thread interactions are very difficult to analyze and I haven't gotten this wrong yet. Note that .NET already has very solid support for splash screens. It definitely gets details like this right.

滴情不沾 2024-09-17 12:06:16

我能够模拟您的问题,并且可以提供解决方法,但可能有更好的选择,因为这是我第一次遇到这种情况。

避免异常的一种选择是不实际关闭启动屏幕,而只是将其隐藏。像这样的事情

public partial class SplashForm : Form
{
  public SplashForm()
  {
    InitializeComponent();
  }

  // Not shown here, this is wired to the FormClosing event!!!
  private void SplashForm_FormClosing(object sender, FormClosingEventArgs e)
  {      
    e.Cancel = true;
    this.Hide();
  }
}

那么重要的是,您需要在后台线程上运行启动屏幕,以确保应用程序不会被启动屏幕线程保持活动状态。所以你的代码看起来像这样

[STAThread]  
static void Main(string[] args)  
{  
    Application.EnableVisualStyles();  
    Application.SetCompatibleTextRenderingDefault(false);  

    Thread splash = new Thread(new ThreadStart(ShowSplash));          
    splash.IsBackground = true;
    splash.Start();  

     Application.Run(new MainForm());  
}  

static void ShowSplash()  
{  
    using (SplashForm splash = new SplashForm())  
    {  
        Application.Run(splash);  
    }  
}

I was able to simulate your problem and I can offer a work around, but there might be a better option out there since this was the first time I had come across this.

One option to avoid the exception is to not actuall close the splash screen, but rather just hide it. Something like this

public partial class SplashForm : Form
{
  public SplashForm()
  {
    InitializeComponent();
  }

  // Not shown here, this is wired to the FormClosing event!!!
  private void SplashForm_FormClosing(object sender, FormClosingEventArgs e)
  {      
    e.Cancel = true;
    this.Hide();
  }
}

Then it will be important that you make the thread that you run the splash screen on a background thread to ensure that the application is not kept alive by the splash screen thread. So your code would look something like this

[STAThread]  
static void Main(string[] args)  
{  
    Application.EnableVisualStyles();  
    Application.SetCompatibleTextRenderingDefault(false);  

    Thread splash = new Thread(new ThreadStart(ShowSplash));          
    splash.IsBackground = true;
    splash.Start();  

     Application.Run(new MainForm());  
}  

static void ShowSplash()  
{  
    using (SplashForm splash = new SplashForm())  
    {  
        Application.Run(splash);  
    }  
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文