トップ回答者
プロセス終了時のバックグラウンドスレッドの挙動について

質問
-
.NET Framework(というかCLR)では、フォアグラウンドスレッドがすべて終了したときにプロセスが終了されますが、その時の実行中のバックグラウンドスレッドの挙動について、詳細な情報がある場所、あるいは何らかの書籍に記載されているなど、どなたかご存じないでしょうか?
バックグラウンド処理を行っているスレッドで、プロセス終了時に強制終了させられる場合に、何らかの後始末を行いたいことがあるのですが、見た感じは即座に強制終了しているように見え、何もできません(細かい調査まではまだできていませんが)。
try-finallyでのfinallyブロックも実行されていないようです。
制約された実行領域 (CER: Constrained Execution Region) を使った場合などまでは未確認ですが、この方法だとできることが非常に限られるので、ちょっと目的には難しそうです。
※後始末といっても、やや複雑な処理も行いたいため
ProcessExitイベントをハンドリングするなどで終了タイミングは取れるので、これで何とかするのが筋なのかなという気もするのですが、どちらにしてもバックグラウンドスレッドの終了時の挙動がわからないと状態が破壊されたり競合が発生する可能性があるため、知る必要があるという感じです。
※ちなみに、各種CLRのバージョンで挙動が異なるならそれも知りたいのですが、優先は現在のCLR4.0(4.5かな?)です。
--追記
すみません、いまさらですが質問の書き方が不十分だった気がするので一応補足はしておきます。
プロセス終了時にバックグラウンドスレッドが強制終了されることはもちろんわかっていて、その強制終了という動作の詳細な挙動が知りたかった、というのが主旨ということです。
- 編集済み なちゃ 2016年2月24日 6:45
回答
-
しかし、今下記ページの「スレッドのプロパティ」というところをを見てみると、
マルチスレッド アプリケーション (C# および Visual Basic)
https://msdn.microsoft.com/ja-jp/library/ck8bc5c6.aspx----
スレッドがバックグランウンド スレッドであるかどうかを示す、ブール型を取得または設定します。バックグラウンド スレッドは、フォアグラウンド スレッドと似ていますが、プロセスの終了を防止しません。プロセスに属するフォアグラウンド スレッドがすべて終了すると、共通言語ランタイムは、まだ動作しているバックグラウンド スレッドの Abort メソッドを呼び出してプロセスを終了します。
----
と記載があり、Abortすると書かれている感じですね…うーん…
.NET 1.1の頃の動作のようですね。
https://msdn.microsoft.com/ja-jp/library/h339syd0%28v=vs.80%29.aspx
.NET 2.0のこの記事にはThreadAbortExceptionが発生しない旨のメモがありますが、.NET 1.1の
https://msdn.microsoft.com/ja-jp/library/h339syd0%28v=vs.71%29.aspx
にはその記述は存在していません。
またThread.IsBackgroundの解説にも、
https://msdn.microsoft.com/ja-jp/library/system.threading.thread.isbackground%28v=vs.71%29.aspx
https://msdn.microsoft.com/ja-jp/library/system.threading.thread.isbackground%28v=vs.80%29.aspx
- 回答としてマーク なちゃ 2016年2月26日 3:12
-
単にIsBackgroundなThreadはWaitForSingleObjectで待たずに、ExitProcessを呼び出すだけですね。ちょっと実験しましたが、Kernel32のExitProcessにBreakPointを仕掛けて、止まった時点では、IsBackgroundなManaged Threadは、まだ生きているようでした(VS2015/NET4.5.2)。
なので、(そのスレッドは)いきなりプロセス終了にともなってWindowsが始末するものかとおもわれます。
まあ、終了処理を実装するばあい、一般論として「終了される側に都合のよいタイミング・悪いタイミング」というものが絶対に存在するはずです。
スレッド側に通知を出して応答を待つしか無いと思いますが。
たとえばスレッドがReadFileしている最中とか、なにかのロックをとっている最中とかに終了処理のタイミングがくるとか嫌な予感しかしません。
jzkey
- 回答としてマーク なちゃ 2016年2月26日 3:13
-
プロセス終了時にバックグラウンドスレッドが強制終了されることはもちろんわかっていて、その強制終了という動作の詳細な挙動が知りたかった、というのが主旨ということです。
ドキュメントで満足できないとのことであれば、直接のソースコードではありませんが、githubでソース公開されているのでそれを追いかけてみてはどうでしょうか?
とりあえず、Thread.IsBackgroundのsetterはSetBackgroubdNative()を呼び出していて、SetBackgroundNativeはネイティブコード上ではThreadNative::SetBackground()とされていて、ThreadNative::SetBackground()は…this->GetInternal()->SetBackground()を呼び出していて…撒かれてしまいました。ちなみにThreadNative::SetBackground()の宣言には
// Indicate whether this thread should run in the background. Background threads
// don't interfere with the EE shutting down. Whereas a running non-background
// thread prevents us from shutting down (except through System.Exit(), of course)
// WARNING : only GC calls this with bRequiresTSL set to FALSE.
void SetBackground(BOOL isBack, BOOL bRequiresTSL=TRUE);とコメントがありました。
まぁ実際に求められているのはこのEE shutting downのシーケンス辺りを追いかけることでしょうか。
- 回答としてマーク なちゃ 2016年2月26日 3:13
すべての返信
-
バックグラウンド処理を行っているスレッドで、プロセス終了時に強制終了させられる場合に、何らかの後始末を行いたいことがあるのですが、見た感じは即座に強制終了しているように見え、何もできません(細かい調査まではまだできていませんが)。
try-finallyでのfinallyブロックも実行されていないようです。
最初はThreadAbortExceptionが飛んできたりするのかと思ってcatchやfinallyでハンドリングできるか試してみたのですが、ダメでした(と思います)。
しかし、今下記ページの「スレッドのプロパティ」というところをを見てみると、
マルチスレッド アプリケーション (C# および Visual Basic)
https://msdn.microsoft.com/ja-jp/library/ck8bc5c6.aspx----
スレッドがバックグランウンド スレッドであるかどうかを示す、ブール型を取得または設定します。バックグラウンド スレッドは、フォアグラウンド スレッドと似ていますが、プロセスの終了を防止しません。プロセスに属するフォアグラウンド スレッドがすべて終了すると、共通言語ランタイムは、まだ動作しているバックグラウンド スレッドの Abort メソッドを呼び出してプロセスを終了します。
----
と記載があり、Abortすると書かれている感じですね…うーん…
- 編集済み なちゃ 2016年2月24日 2:11
-
既に確認されているかもしれませんが、Thread.IsBackgroundにある
プロセスに属するすべてのフォア グラウンド スレッドが終了すると、共通言語ランタイムは、プロセスを終了します。残りのバック グラウンド スレッドが停止され、完了しません。
またフォアグラウンド スレッドとバックグラウンド スレッドにも記載があります。の通りかと思います。Environment.FailFastも同様かと思います。
これについてはCERですが、CriticalFinalizerObjectクラスの派生クラスを作りFinalize()メソッドをオーバーライドします。その場合、GCスレッドで確実に当該メソッドが呼ばれます。
ところでEnvironment.Exit()を呼ばれた場合はどうなるんだろう?
-
しかし、今下記ページの「スレッドのプロパティ」というところをを見てみると、
マルチスレッド アプリケーション (C# および Visual Basic)
https://msdn.microsoft.com/ja-jp/library/ck8bc5c6.aspx----
スレッドがバックグランウンド スレッドであるかどうかを示す、ブール型を取得または設定します。バックグラウンド スレッドは、フォアグラウンド スレッドと似ていますが、プロセスの終了を防止しません。プロセスに属するフォアグラウンド スレッドがすべて終了すると、共通言語ランタイムは、まだ動作しているバックグラウンド スレッドの Abort メソッドを呼び出してプロセスを終了します。
----
と記載があり、Abortすると書かれている感じですね…うーん…
.NET 1.1の頃の動作のようですね。
https://msdn.microsoft.com/ja-jp/library/h339syd0%28v=vs.80%29.aspx
.NET 2.0のこの記事にはThreadAbortExceptionが発生しない旨のメモがありますが、.NET 1.1の
https://msdn.microsoft.com/ja-jp/library/h339syd0%28v=vs.71%29.aspx
にはその記述は存在していません。
またThread.IsBackgroundの解説にも、
https://msdn.microsoft.com/ja-jp/library/system.threading.thread.isbackground%28v=vs.71%29.aspx
https://msdn.microsoft.com/ja-jp/library/system.threading.thread.isbackground%28v=vs.80%29.aspx
- 回答としてマーク なちゃ 2016年2月26日 3:12
-
ありがとうございます。
対象バージョンがVisual Studio 2015のドキュメントだったので最新の内容と思い込んでいましたが、Abortされるのは1.1のころで、現在は何のハンドリングできずにただ強制終了されてしまう、という雰囲気ですね。
なんとなくやっぱりハンドリングは無理っぽそうですので、そういう前提で考えようと思います。
CERに関しては、最初にも書きましたが多分制約が厳しくて難しそうな感じです。
あとは、CLRの動作に関する詳細情報とか書籍とかで、何か動作の詳細にかかわることで、もし知っているとか見かけた人がいらっしゃったら参考までに情報いただければと思います。
※これは既出の資料とかの、単に「停止され完了しない」という仕様表現レベルでなく、その詳細動作について何か情報があれば引き続き参考に知りたいという意味です。
-
単にIsBackgroundなThreadはWaitForSingleObjectで待たずに、ExitProcessを呼び出すだけですね。ちょっと実験しましたが、Kernel32のExitProcessにBreakPointを仕掛けて、止まった時点では、IsBackgroundなManaged Threadは、まだ生きているようでした(VS2015/NET4.5.2)。
なので、(そのスレッドは)いきなりプロセス終了にともなってWindowsが始末するものかとおもわれます。
まあ、終了処理を実装するばあい、一般論として「終了される側に都合のよいタイミング・悪いタイミング」というものが絶対に存在するはずです。
スレッド側に通知を出して応答を待つしか無いと思いますが。
たとえばスレッドがReadFileしている最中とか、なにかのロックをとっている最中とかに終了処理のタイミングがくるとか嫌な予感しかしません。
jzkey
- 回答としてマーク なちゃ 2016年2月26日 3:13
-
すみません、目的の情報に繋がる内容が載っているかわかりませんが
個人的にスレッドについてコード付きでよくまとまっている気になるサイトがあったのでご紹介します。(英語ですが・・)Threading in C# Joseph Albahari
最後の更新が2011-4-27となっているので古い情報だったりするかもしれませんが・・・
-
プロセス終了時にバックグラウンドスレッドが強制終了されることはもちろんわかっていて、その強制終了という動作の詳細な挙動が知りたかった、というのが主旨ということです。
ドキュメントで満足できないとのことであれば、直接のソースコードではありませんが、githubでソース公開されているのでそれを追いかけてみてはどうでしょうか?
とりあえず、Thread.IsBackgroundのsetterはSetBackgroubdNative()を呼び出していて、SetBackgroundNativeはネイティブコード上ではThreadNative::SetBackground()とされていて、ThreadNative::SetBackground()は…this->GetInternal()->SetBackground()を呼び出していて…撒かれてしまいました。ちなみにThreadNative::SetBackground()の宣言には
// Indicate whether this thread should run in the background. Background threads
// don't interfere with the EE shutting down. Whereas a running non-background
// thread prevents us from shutting down (except through System.Exit(), of course)
// WARNING : only GC calls this with bRequiresTSL set to FALSE.
void SetBackground(BOOL isBack, BOOL bRequiresTSL=TRUE);とコメントがありました。
まぁ実際に求められているのはこのEE shutting downのシーケンス辺りを追いかけることでしょうか。
- 回答としてマーク なちゃ 2016年2月26日 3:13
-
すみません、返信が遅くなりました。
jzkeyさん、佐祐理さん、いろいろ調べていただいてありがとうございます。
マネージ側の世界で何らかの後始末が行われているのかと最初は思っていたのですが(実際.NET1.1に頃はAbortを行っていたということでしたが)、少なくとも現在は、マネージレベルで何かするというよりも、OSのプロセス終了のシーケンスにそのまま乗っていると考えるのがよさそうですね。
アンマネージの世界は全然詳しくないので細かく追うことは難しそうですが(すみません)、少なくともマネージのレイヤの話ではなさそうだというのがよくわかりました。
ありがとうございました。
あとは若干の疑問としては、AppDomainを独自で作成して、それがUnloadされたときとかにどうなるかですかね(こちらはプロセス終了のシーケンスには入らないので、マネージ側でどうにかしている?)
まあこれとかは、そもそも見える範囲でのスレッドの挙動の確認もしていませんので、また少し調べてみようと思います。
kenjinoteさん、ありがとうございます。
そのサイトは(今回のこととは別ですが)いろいろ参考にしたこともあります。 -
あとは若干の疑問としては、AppDomainを独自で作成して、それがUnloadされたときとかにどうなるかですかね(こちらはプロセス終了のシーケンスには入らないので、マネージ側でどうにかしている?)
私の挙げた最初の記事に書かれてますよ?
ただし、System.AppDomain.Unload(System.AppDomain) メソッドがアプリケーション ドメインをアンロードしたためにスレッドが停止する場合、フォアグラウンドとバックグラウンドの両方のスレッドで ThreadAbortException がスローされます。
-
>私の挙げた最初の記事に書かれてますよ?
ただし、System.AppDomain.Unload(System.AppDomain) メソッドがアプリケーション ドメインをアンロードしたためにスレッドが停止する場合、フォアグラウンドとバックグラウンドの両方のスレッドで ThreadAbortException がスローされます。
すみません、見落としてました。
思い切り書いてましたね。ありがとうございます。すっきりしました。
-
マネージ側の世界で何らかの後始末が行われているのかと最初は思っていたのですが(実際.NET1.1に頃はAbortを行っていたということでしたが)、少なくとも現在は、マネージレベルで何かするというよりも、OSのプロセス終了のシーケンスにそのまま乗っていると考えるのがよさそうですね。
YesともNoとも言えないような…。フォアグラウンドスレッドが複数存在する状況でEnvironemt.Exit()やMain()メソッドからのリターンなどが行われた場合、フォアグラウンドスレッドに対してThreadAbortExceptionを投げ、バックグラウンドスレッドに対しては投げない、という判別は行っているわけです。その後もThreadAbortExceptionが握り潰されていないかを検出して再スローしますし、その後、FinalizerThreadも後始末もしますし。
気にされているのはマネージスレッドの動作ですか? マネージスレッドの元になっているネイティブスレッドの生存期間ですか? それともバックグラウンドスレッドで参照していた.NETオブジェクトの後始末ですか? そこにもよるかと思います。
-
気にされているのはマネージスレッドの動作ですか? マネージスレッドの元になっているネイティブスレッドの生存期間ですか? それともバックグラウンドスレッドで参照していた.NETオブジェクトの後始末ですか? そこにもよるかと思います。
何というか、フレームワークレベルでは直接関与していないような、要はユーザが勝手に作成したバックグラウンドスレッドに対する、マネージ領域での制御がどうなっているか、というようなニュアンスでした。
後始末というのはある程度いろんな事柄が含まれてくるのですが、一例として簡単に上げると、ログ出力の非同期ライターみたいなもので、出力要求時はキューにだけ書き込み、別のバックグラウンドスレッドでファイル書き込みを行うようなものがあります(出力要求時のレイテンシを最小限にするための制御)。
また、前提として、「独立性の高いライブラリのため、使う側はそれを意識しなくてもできるだけ問題が発生しないようにしたい」というのがあります。
こういうものの、ファイル書き込み処理側のスレッドに関して、以下のような問題が出てきます。
・フォアグラウンドスレッドを使うわけにはいかない(使う側が意識しないとプロセスが終了しなくなってしまう)
・データがある時だけスレッドを起動すればそういう問題はないが、当然そんなことをすると非効率的になりすぎ、スレッドを分ける意味がなくなる(こういう制御、効率的かつ長時間フォアグラウンドスレッドが残らないようにするのは意外と簡単ではない)。
・最後に出力をフラッシュ(書き込みスレッドでの書き込み完了待機)する呼び出しを強制すれば問題ないが、前提事項からそういうのはなし
・そうすると、書き込みスレッドはバックグラウンドにするしかない(スレッドプールのスレッドでも同様)
・当然プロセスはいつ終了するかわからないので、キューにデータが残ることもあるし、キューから取り出した瞬間に終了することもあるため、プロセス終了時のハンドリングでキューデータを確認しても、すでに取り出し済みのデータが消える可能性もある
とまあこういったことから、終了時のスレッドの挙動、終了するタイミング、等々を詳細に知りたいな、ということでした。
回答いただいたことか想定できる挙動だと、たとえばプロセス終了時のハンドリングは、スレッド終了よりも先に来そうですので、プロセス終了時のハンドリングでバックグラウンドスレッドでは正常な終了を通知し、その後ハンドリングした側で残ったデータをフラッシュする、などの方法がありそうだということがわかりました。