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

时间:2010-07-18 08:46:52

标签: c# winforms exception splash-screen

我遇到了一个奇怪的问题,就是我显示一个泼溅形式,导致InvalidAsynchronousStateException被抛出。

首先,这里是Main {}的代码,我在其中启动了splash表单:

[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获得的数字,查看'#Exceps Thrown'计数器。)这些异常似乎被运行时捕获并吞没,因为它们确实 不会波及并导致应用程序本身出错。至少我无论如何都无法确定。

我发现当系统触发UserPreferenceChanged事件时抛出异常。由于发现了这一点,我可以生成异常 随意改变背景图片或屏幕保护程序等。

我没有在代码中的任何地方明确订阅此事件,但我理解(通过Google的强大功能)所有顶级控件和表单都订阅 自动进行此活动。

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

现在,如果我停止启动splash表单线程,则异常消失。运行线程,它回来了。所以,似乎事情没有取消订阅,这可能导致后续的异常?

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

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

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

进一步的研究引导我this Microsoft article,其中包含以下评论:

  

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

嗯,因为它的外表而感到内疚。请注意,我的应用程序并未冻结或做任何不愉快的事情。

目前,这更像是一种好奇心,但是我很想知道这里可能会有一些隐藏的恶作剧在等待将来咬人。

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

有什么想法吗?

2 个答案:

答案 0 :(得分:2)

是的,您正在使用SystemEvents类运行。该类创建一个监听系统事件的隐藏窗口。特别是UserPreferenceChanged事件,许多控件使用该事件来知道他们何时需要重新绘制自己,因为系统颜色已经改变。

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

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

我认为调用Thread.SetApartmentState()应该可以解决您的问题。不是100%肯定,这些UI线程交互非常难以分析,我还没有弄错。请注意,.NET已经有非常solid support的启动画面。它肯定会得到这样的细节。

答案 1 :(得分:1)

我能够模拟你的问题,我可以提供一个解决方案,但可能有更好的选择,因为这是我第一次碰到这个。

避免异常的一个选择是不要关闭启动画面,而是隐藏它。像这样的东西

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