【问题标题】:Exception when using secondary UI message pump for a splash screen将辅助 UI 消息泵用于启动画面时出现异常
【发布时间】:2010-07-18 08:46:52
【问题描述】:

我在显示启动表单的方式上遇到了一个奇怪的问题,导致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。

在一些应用程序运行了 5 个小时的测试期间,我注意到异常的数量偶尔会增加一到两个。 (PerfMon 获得的数字,查看 '# of Exceps Throw' 计数器。)这些异常似乎被运行时捕获和吞噬,因为它们确实 不会激起涟漪并导致应用程序本身出现任何问题。至少我无法确定。

我发现当系统触发 UserPreferenceChanged 事件时会引发异常。既然发现了这一点,我就可以生成异常 随意改变背景图片或屏保等

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

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

现在,如果我停止运行启动表单线程,异常就会消失。运行线程,它回来了。那么,似乎没有取消订阅该事件,这可能导致后续异常?

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

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

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

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

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

嗯,从外观上看是有罪的。请注意,我的应用并没有冻结或做任何不愉快的事情。

目前,这更像是一种好奇心,但我担心将来这里可能会有一些隐藏的讨厌鬼等着咬。

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

有什么想法吗?

【问题讨论】:

  • 有趣;如果你使用ShowDialog作为splash,错误还会出现吗?
  • @Marc:是的。在 Debug 和 Release 版本上也试过这个。你能重现它吗?
  • 很遗憾,我目前还不能尝试。

标签: c# winforms exception splash-screen


【解决方案1】:

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

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

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

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

【讨论】:

  • 感谢您的回答。不幸的是, SetApartmentState(ApartmentState.STA) 的建议没有任何效果,因为仍然抛出异常。也许这个问题实际上比它最初出现的要隐蔽一些。我对解决方法的想法越多,我就越不满意。但是,您提供的链接很有趣,值得仔细查看。
  • 我刚刚点击了那个链接,意识到我刚刚告诉了你一些你已经知道的事情。嗯,WindowsFormsApplicationBase 一定能解决你的问题。
【解决方案2】:

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

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

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

【讨论】:

  • 谢谢,这确实可行,我可以使用这个选项。我很高兴你能重现这个问题。 (这甚至是一个问题吗?)关于它为什么会发生的任何想法?我陷入了寻找线索的反射器中。麻烦的是,我不知道我在看什么。 :)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-08-30
  • 1970-01-01
  • 2014-03-07
  • 2013-09-20
  • 1970-01-01
相关资源
最近更新 更多