はじめに
本記事はC# WPF.NetFlameworkを対象とした記事となります。非同期処理を実装するシステムにて、適切に非同期処理を破棄する終了処理のベストプラクティスを模索した時の記録(2020/9/13時点)を個人用
にまとめます。
非同期処理について
foreground / background thread 概要
マネージドスレッドは以下の二つに分類されます。
種類 | 説明 |
---|---|
foreground thread | マネージド実行環境を維持できる。 |
background thread | マネージド実行環境を維持できない。つまりすべてのフォアグラウンドが停止すると、システムによって全てのバックグラウンドスレッドが停止させられる。 |
これはUIスレッドで非同期処理を実行した場合、非同期処理実行中にUIスレッドを停止したときの動作の違いを意味します。「background thread」はUIスレッド終了に伴い終了しますが、「foreground thread」は終了しません。
foreground / background thread 判定方法
次に特定の非同期処理がどちらに該当するのかを確認します。
方法としては、実際に非同期処理実行中にUIスレッドを停止してみて確認することもできるかもしれませんが、Thread.IsBackgroundというプロパティを参照する
のが最も容易です。
しかし、Threadオブジェクトを生成せずに非同期処理を実行する場合は、非同期処理内で以下の記述をすることでThread.IsBackgroundの規定値を確認することができます。
Console.WriteLine(Thread.CurrentThread.IsBackground);
ちなみにThreadオブジェクトを生成した際の規定値は*False
、Task.Run()にて非同期処理を実行する場合はTure
*が規定値となります。
Applicationの終了について
ここまでUIスレッドと非同期スレットとの関係性についての注意事項を述べましたが、本題であるApplication終了時の関係性について触れていきます。先に結論を言うと、Applicationが終了するとマネージドスレッドの種類に関係なく、非同期スレッドは終了します。
では、Applicationの終了について詳しく見ていきます。
Window.Close()
基本的には、WPFアプリケーションテンプレートで作成したMainWindow.xaml.csにてClose()メソッドを呼び出すことでApplicationは終了
します。ただしApp.xamlもしくはApp.xaml.csを編集した場合は、それに限りません。
厳密にいうとApplication.MainWindowに設定されたWindowインスタンスがClose()されたときに、Application.ShutdownModeがOnLastWindowCloseに設定されていたかどうかによります。
Application.ShutdownMode
Application.ShutdownModeが規定値であるOnLastWindowCloseに設定されていた場合、Application.MainWindowに設定されたWindowインスタンスの*Close()*時にApplicationは終了されます。しかしOnExplicitShutdownに設定されていた場合は、Close()=Applicationの終了とはなりません。
※結果的にAppicationが終了することはありますが、Applicationが生きたままとなる危険性があります。
したがって後者の場合は、Close()ではなく明示的にApplication.Shutdown()メソッドを呼び出す必要があります。
Application.MainWindow
ここでApplication.MainWindowにWindowインスタンスを設定する方法を見ていきます。Application.MainWindowにセットするには、App.xaml
で行う方法とApp.xaml.cs
で行う方法の2通りあります。
テンプレートからWPFアプリケーションを作成した場合はApp.xaml
のApplicationのStartupUriにて記述されています。
<Application x:Class="HelloWorld.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
StartupUri="MainWindow.xaml">
<Application.Resources>
</Application.Resources>
</Application>
続いて、App.xaml.csでMainWindowを設定する方法を提示します。
<Application x:Class="HelloWorld.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Startup="AppStartup">
<Application.Resources>
</Application.Resources>
</Application>
public partial class App : Application
{
void AppStartup(object sender, StartupEventArgs e)
{
MainWindow window = new MainWindow();
window.Show();
}
}
コチラの方法ではApp.xaml
のStartupUriにて直接xamlを指定せずに、App.xaml.cs
のStartupイベント上にてWindowインスタンスを生成します。
public partial class App : Application
{
void App_Startup(object sender, StartupEventArgs e)
{
// Windowsインスタンスを生成した際に自動でそのインスタンスが
// Application.MainWindowにセットされる。
Window firstWindow = new MainWindow();
Window secondWindow = new MainWindow();
firstWindow.ShowDialog();
// 移行の処理は実行されません。
secondWindow.ShowDialog();
}
}
まとめ
以上より、非同期処理を実装したアプリケーションは、以下に留意して実装することを結論としました。
- 非同期処理は理由のない限り、*「background thread」*で処理させる。
- マネージドスレッドの種類を問わず、Applicationを終了させることで非同期スレッドも終了させることができる。
- Application.ShutdownModeやAppication.MainWindowが編集されることを考慮して、基本的には*Close()ではなく明示的にApplication.ShutDown()*を使用してApplication終了処理を記述する。