代码之家  ›  专栏  ›  技术社区  ›  Andy

将辅助UI消息泵用于启动屏幕时出现异常

  •  2
  • Andy  · 技术社区  · 14 年前

    我在展示splash表单时遇到了一个奇怪的问题,这会导致 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。

    在一些测试中,应用程序运行了5个小时,我注意到异常的数量偶尔会增加一两个。 (PerfMon获得的数字,查看'#of Exceps Thrown'计数器。)这些异常似乎被运行时捕获并吞并,因为它们确实如此 不会产生涟漪,导致应用程序本身出错。至少没什么我能确定的。

    我发现异常是在系统触发UserPreferenceChanged事件时引发的。既然发现了这一点,我就可以生成异常

    自动添加到此事件。

    现在,如果我停止splash form线程的运行,异常就会消失。运行线程,它回来了。所以,似乎有什么东西没有从事件中取消订阅,而这可能导致了随后的异常?

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

    进一步的研究使我找到了 this Microsoft article

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

    嗯,从外表看是有罪的。请注意,我的应用程序并没有冻结或做任何不正常的事情,虽然。

    目前,这更像是一种好奇,而不是其他任何东西,但我意识到,这里可能有一些隐藏的恶习,等待着咬在未来。

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

    有什么想法吗?

    2 回复  |  直到 14 年前
        1
  •  2
  •   Community CDub    7 年前

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

    问题是,创建窗口的初始化代码对调用它的线程的单元状态非常敏感。在您的例子中,这是错误的,您没有调用Thread.SetApartmentState()来切换到STA。那是 非常

    请注意,您的解决方案实际上并不是修复程序,系统事件将在错误的线程上引发。你的splash线程而不是你程序的UI线程。当一个实际的系统事件被触发时,您仍然会得到随机的和非常难以诊断的故障。最臭名昭著的是,当用户锁定工作站时,程序会在再次解锁时死锁。

    我认为调用Thread.SetApartmentState()应该可以解决您的问题。不是100%确定,这些UI线程交互很难分析,我还没有搞错。请注意,.NET已经有很多 solid support 用于启动屏幕。这样的细节肯定是对的。

        2
  •  1
  •   Chris Taylor    14 年前

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

    避免异常的一个方法是不关闭启动屏幕,而只是隐藏它。像这样的

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