2

我在显示启动表单的方式上遇到了一个奇怪的问题,这会导致抛出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 Thrown”计数器。)这些异常似乎被运行时捕获和吞噬,因为它们不会引起波动并导致应用程序本身出现任何问题。至少无论如何我都无法确定。

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

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

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

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

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

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

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

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

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

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

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

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

有什么想法吗?

4

2 回答 2

2

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

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

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

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

于 2010-07-18T16:24:52.317 回答
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);  
    }  
}
于 2010-07-18T08:59:47.990 回答