2

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?

4

2 に答える 2

2

はい、SystemEvents クラスに違反しています。そのクラスは、システム イベントをリッスンする隠しウィンドウを作成します。特に UserPreferenceChanged イベントでは、多くのコントロールがこのイベントを使用して、システム カラーが変更されたために自分自身を再描画する必要があることを認識します。

問題は、ウィンドウを作成する初期化コードが、それを呼び出すスレッドのアパートメント状態に非常に敏感であることです。あなたの場合は間違っています.Thread.SetApartmentState()を呼び出してSTAに切り替えませんでした。これは、UI を表示するスレッドにとって非常に重要です。

回避策は実際には修正ではないことに注意してください。システム イベントは間違ったスレッドで発生します。プログラムの UI スレッドではなく、スプラッシュ スレッド。実際のシステムイベントが発生した場合でも、ランダムで診断が非常に難しい障害が発生します。最も悪名高いのは、ユーザーがワークステーションをロックすると、再びロックを解除したときにプログラムがデッドロックすることです。

Thread.SetApartmentState() を呼び出すと問題が解決すると思います。100% 確実ではありませんが、これらの UI スレッドの相互作用を分析するのは非常に難しく、私はまだこれを誤解していません。.NET では、スプラッシュ スクリーンがすでに非常にしっかりとサポートされていることに注意してください。このような詳細は間違いなく正しく取得されます。

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