ウィザードと呼ぶ WPF ダイアログを起動する Winforms アプリケーションがあります。ウィザードの目的は、多数のテキスト ファイルを開き、その内容をデータベースに保存することです。これらのファイルを DB に保存するのに必要な時間は、15 秒から 60 秒以上です。ウィザード UI の応答性を高めるために、テキスト ファイルをデータベースに保存するプロセスは BackgroundWorker スレッドで実行されます。残念ながら、Winforms ホスト アプリケーションの一部のレガシー コードが原因で、ウィザードは 80 ~ 90% の時間完全に応答しません。そのため、ウィザードは独自のワーカー スレッドで起動されます。
// UI を維持するためにウィザードを別のスレッドに配置する //エクスポート プロセス中の応答性 Thread t = new Thread(LaunchBatchExportWizardView); t.SetApartmentState(ApartmentState.STA); t.Name = "WizardThread"; t.Start();
要約すると、Winforms ホストをサポートするメイン スレッド、ウィザード スレッド、および BackgroundWorker スレッドの 3 つの主要なスレッドがあります。ユーザーは、CancelAsync メッセージを BackgroundWorker スレッドに送信するウィザードのボタンをクリックして、このエクスポート プロセスを一時停止できます。BackgroundWorker_DoWork イベント ハンドラー内で、このキャンセル メッセージを確認し、見つかった場合は処理を中止して戻ります。これにより、BackgroundWorker_RunWorkerCompleted イベント ハンドラーがトリガーされ、Thread.CurrentThread.Name を使用して制御が返されたスレッドの ID を確認できます。
ユーザーが初めて BackgroundWorker プロセスを一時停止すると、制御がウィザード スレッドに返されます。ユーザーがエクスポート プロセスを再開する場合は、RunWorkerAsync を呼び出します。これにより、BackgroundWorker_DoWork イベント ハンドラーが開始されます。観察から、このハンドラーは以前に使用したものと同じスレッドを使用せず、代わりにスレッド プールから新しいスレッドを使用することがわかります。デバッグの目的で、このスレッドに名前を付けます。
if(Thread.CurrentThread.Name == null) { Thread.CurrentThread.Name = "MyBackgroundWorkerThread" + "_" + _threadCounter.ToString(); _threadCounter++; }
最初の BackgroundWorker スレッドの名前は MyBackgroundWorkerThread_1、2 番目のスレッドの名前は MyBackgroundWorkerThread_2 などです。
後でユーザーがプロセスを再度一時停止することを決定した場合、制御は BackgroundWorker_RunWorkerCompleted イベント ハンドラーのウィザード スレッドに返されず (初回の場合と同様)、代わりに新しいスレッドに返されます。ただし、これは致命的ではなく、ユーザーは引き続き再開でき、エクスポート プロセスは中断したところから再開されます。
ただし、データベースへの接続が失われた場合にカスタム警告ダイアログを起動しようとすると、問題が発生します。ウィザードがデータベースと通信できない場合、明らかに、ウィザードはその主要な任務を遂行できません。1 回以上の一時停止/再開サイクルの後にこのダイアログを起動すると、「多くの UI 要素がこれを必要とするため、呼び出しスレッドは STA でなければなりません」というメッセージとともに例外がスローされます。この要件のため、ウィザード スレッドは明示的に STA に設定されているため (上記の関連コードを参照)、一時停止/再開サイクルの前にデータベース接続が失われた場合でも、すべて正常に機能します。この新しいスレッドは明らかに STA ではないため、例外がスローされます。
私が試したオプションの 1 つは、DatabaseConnectivityLoss ダイアログを起動したい時点でウィザード スレッドにいるかどうかをテストすることでした。ウィザード スレッドは明示的に名前が付けられたスレッドであるため (上記のコードを参照)、現在のスレッドの name プロパティが null かどうかをテストするだけです。
if (Thread.CurrentThread.Name == null) { if (Application.Current == null) { 新しいアプリ(); } スレッド t = 新しいスレッド (LaunchDatabaseConnectivityLossDialog); t.SetApartmentState(ApartmentState.STA); t.Name = "NewStaThread"; t.Start(); }
これは、前述の例外なしでダイアログを起動するために正常に機能しますが、接続が復元された後にエクスポート プロセスを再開しようとすると、アプリケーションがハングします。
2 番目に試みたのは、SynchronizationContext 変数を設定して、ウィザード スレッドへの参照を保持することでした。この参照を使用して、いつでもどのスレッドが最新であるかに関係なく、ウィザード スレッドで DatabaseConnectivityLoss ダイアログを起動できることを理解していました。これを行うには、ウィザードのコンストラクターで次の変数を設定します。
_synchronizationContext = SynchronizationContext.Current; if (_synchronizationContext == null) {//ウィザードは Winforms アプリの子であるため、私の場合は常に true です _synchronizationContext = 新しい SynchronizationContext(); }
ただし、後でこの SynchronizationContext を使用してコードをウィザード スレッドに強制的に戻そうとすると、失敗します。
`_synchronizationContext.Send(テスト、null); private void Test (オブジェクト プレースホルダー) { Debug.WriteLine(Thread.CurrentThread.Name); }`
Thread.CurrentThread.Name は通常 null を返し、それ以外の場合は "NewStaThread" を返します。この動作が断続的であることを暗示したくありません。さまざまなバリエーションを試し、さまざまな状況でさまざまな場所からこのメソッドを呼び出しただけです。
同期コンテキストはウィザード スレッドへの参照を保持するはずであり、Send メソッドを呼び出すとコールバック メソッドがウィザード スレッドで実行されるはずだというのが私の印象でした。
私の仮定のどれが無効か、あるいは解決策を提案してくれる人はいますか?
概念的には、アプリケーションを強制的に DoWork ハンドラーからウィザード スレッドに戻すか、代わりに、データベース接続損失ダイアログを起動する前に強制的にウィザード スレッドに戻す必要があると思います。匿名スレッドを開始する前に実行する必要がある STA に設定するには遅すぎるまで、匿名スレッドにアクセスできないことを理解しています。