読者です 読者をやめる 読者になる 読者になる

ファイル I/O でブロックされているスレッドを殺したくなったらどうするか?

.NET Vista

やっていてふと思ったのですが、タイムアウト付きのExistsメソッドを使っても、タイムアウトするまでアプリケーションがフリーズしてしまいます。

Exists自体を別スレッドに任せた方がよいのでしょうか?

普通はどのように設計するものなのでしょう。

スタンダードな手法等があれば、御教授よろしくおねがいします。

答え

例えば,Windows XPWindows Server 2003,およびそれ以前の Windows を避ける

実は奥が深い I/O Cancel

実はこれは結構奥が深い問題です.ただまあ上で質問されている方はそこまで深い話が必要というわけでもないような気もしますが.というわけで以下は一般教養としてどうぞ.

致命的でないケース

従来の Windows では,OS API レベルでの I/O Cancel モデルが不十分だったため,その上に位置する .NET や各種 CUI ツールのレイヤではある意味でどうしようもない事態が発生していました.
問題は大まかに分けて,致命的なのと致命的でないのがあります.致命的でないのは,API レベルで I/O をキャンセルする仕組みが不十分なため,今回採り上げられているタイムアウト付き File.Exists のような仕組みの実装で困るという問題でした.
Windows Vista カーネルの内部 : 第 1 部』で Mark Russinovich 氏が例に挙げているのは,コマンドプロンプトでの以下のコマンドです.

net view \\nonexistentmachine

コマンドプロンプトからこのコマンドを実行したときに,Ctrl-C によるキャンセルができるようになったのは,なんと Windows Vista 以降とのことです.
開発者向け資料である『Windows Vista での Win32 I/O キャンセル サポート』には,もう少し詳細にシナリオの分析が行われています.

アプリケーションのキャンセル サポート

ファイル操作では、アプリケーションのスレッドをブロックして、処理の継続を中断できます。適切に設計され、正常に応答するアプリケーションは、少なくとも 2 つのスレッドから構成されます。このようなアプリケーションのスレッドでは、ユーザー インタフェース (UI) のサービスを行い、未応答の原因となり得る API を直接的にも間接的にも呼び出しません。操作をブロックする場合は、1 つ以上の "ワーカー" スレッドが起動されます。

このワーカー スレッドを利用することにより、アプリケーションで、[Cancel] や [Stop] ボタン、または処理状況インジケータを使用したキャンセルをサポートできます。多くの場合、ユーザーが操作を中止するときは、ワーカー スレッドを無視しても問題はありません (ワーカー スレッドに対して操作を行わない)。たとえば、ドメイン ネーム サービス (DNS) リゾルバの呼び出しや、他のネットワーク呼び出しが、最終的にタイム アウトになると、ワーカー スレッドはワーカー スレッド自身をクリーン アップします。

ただし、能動的な I/O キャンセルのサポートが必要になる場合もあります。その例として、非常に低速なデバイスに対する OpenFile 呼び出しの停止が挙げられます。この呼び出しを直ちに再実行するとき、場合によっては、別のパラメータの使用が必要になります。このような状況が発生した場合、ユーザーが必要とする唯一の対処方法は、アプリケーションの終了です (前に説明したように、アプリケーションが予期しない動作をしてしまう可能性があります)。このことからも、要求のキャンセル機能をユーザーに提供することが、非常に期待されていることは明らかです。前にも説明しましたが、これを実現するには、Windows とそのドライバ群がキャンセルを完全にサポートする必要があります。このサポートでは、同期操作のキャンセルも対象となります。

致命的なケース

より致命的なのは,プロセスクリーンアップ時にドライバへの I/O 完了待ちが発生してしまった場合でした.

アプリケーション終了時の障害

Windows アプリケーションは、ユーザーがアプリケーションを閉じたときに (タイトル バーの [X] をクリックした場合など)、正常に終了しないことがあります。このような場合、アプリケーションのウィンドウは閉じますが、よく調べると、アプリケーションのプロセスがプロセス リストに残ったままになっています。このようなプロセスは、"ゾンビ" プロセスと呼ばれます。ゾンビ プロセスは強制終了できません。ゾンビ プロセスによってアプリケーションの再起動ができない場合があり、予期しない動作が行われることもあります。復旧するには、コンピュータの再起動が必要になる場合があります。

マイクロソフトでは、このような動作に関する事例報告を多数受けており、その原因は障害のあるドライバによるものと推測していました。また、根本的な原因を特定し、その解決方法を明確にするために、信頼のできるデータを調査していました。このようなデータを収集するために、特殊な機能を備えた Windows XP のバージョンを開発しました。このバージョンは、カーネル データの収集とレポートを行います。このデータには、スタック プロセスを待機するドライバ要求に関するデータも含まれています。特別なテクニカル ベータ プログラムを通じて、このバージョンを、マイクロソフトの内部ユーザー、および選抜した顧客に対して展開しました。

報告されたレポートを調べると、弊社で推測していた原因だけでなく、終了時の障害やアプリケーションからの応答がないという問題は、Windows やドライバに、ドライバでの作成要求をキャンセルする機能がないということも原因であることが確認されました。このような障害を解決するために、Windows を修正する必要があったのです。

.NET でどうするか?

残念ながら,.NET Framework はまだ Vista 以降の I/O Cancel API を前提とした I/O モデルをサポートしていません.
実際に自分でそういったラッパーを書く場合は,『Windows Vista での Win32 I/O キャンセル サポート』に一通り目を通し,Vista で I/O Cancel API が追加された背景事情と動機を先に把握しておくと良いでしょう.