syghの新フラグメント置き場

プログラミングTipsやコード断片の保管場所です。お絵描きもときどき載せます。

Directory.Existsメソッドのタイムアウト時間

.NETのSystem.IO.Directory.Exists()メソッドは指定ディレクトリの存在有無をチェックするメソッドですが、タイムアウト時間が設けられています。ローカルドライブのディレクトリにアクセスする場合は、よほど低速なシステムか膨大なストレージでないかぎり、メソッドの実行は一瞬(数ミリ秒オーダー)で終わりますが、チェック対象がネットワーク共有フォルダーなどの場合、アクセスできないときはシステム規定のタイムアウト時間が経過するまで延々とリトライを続けます。この動作はSystem.IO.File.Exists()も同様です。なので普通、これらのI/O処理はメインスレッドで直接実行したりせず、サブスレッドに処理を逃がして非同期で実行するのがセオリーです。今どきのC#であればTPLやasync/awaitを使うのが常套手段でしょう。

Windows向けの.NET 4.x実装では、Directory.Exists()は内部でWin32 Shell APIのPathFileExists()とPathIsDirectory()を使っているものと思われます。確かネットワークアクセスのタイムアウト時間はWindows OSのシステム設定に依存するはずで、既定値は20秒だったと思います。また、タイムアウト時間はアプリケーション側では指定できないはずです。無効なネットワーク共有フォルダーにアクセスして、意図的にタイムアウトさせて時間を計測するために下記のC#スクリプトを試してみましたが、環境によって約20-30秒と開きがあるようです。ときどき40秒近くかかることもありました。ネットワーク共有フォルダーへのアクセスのタイムアウト時間に関しては、おそらく名前解決など、システムのネットワーク構成にも依存するものと思われます。

Action<string> checkDir = (path) => { var sw = new Stopwatch(); sw.Start(); Print(Directory.Exists(path)); sw.Stop(); Print(sw.Elapsed); };
checkDir(@"\\1.2.3.4\hoge");

Action<string> checkFile = (path) => { var sw = new Stopwatch(); sw.Start(); Print(File.Exists(path)); sw.Stop(); Print(sw.Elapsed); };
checkFile(@"\\1.2.3.4\hoge\fuga.txt");

C#スクリプト

C#コードをスクリプトとして対話的に実行できる「C# Interactive」はVisual Studio 2015 Update 1で追加された機能ですが、こういったAPIの挙動を調査するのに重宝しています。PrintメソッドはInteractiveScriptGlobalsクラスの静的メソッドのひとつですが、おそらくC# 6.0で追加されたusing static機能をC# Interactive内部で暗黙的に使い、using static InteractiveScriptGlobals;とすることでクラス名を省略できているものと思われます。using static自体はJava 5におけるimport staticの後追いのようなもので、不要な混乱を招きかねないため積極的に使うべき類の機能ではありませんが、こういったスクリプトコードに関してはC/C++Pythonなどのようにグローバルメソッドが簡潔に使えるということは重要ですね。

ちなみにC#スクリプトの拡張子は.csxで、C# Interactive外のVisual Studioコードエディターでもコード補完が効きますが、この拡張子のファイルをVisual Studio 2015 Update 3で編集していると、IDEが突然クラッシュするという残念な不具合があるようです。VS2017は試していません。

スレッドの強制終了について

I/O処理のタイムアウト時間が過ぎる前に処理を強制的に中断しようとして、ワーカースレッドをメインスレッドからSystem.Threading.Thread.Abortメソッドで強制終了しようなどとするのはご法度です。中断の結果として起こり得る未定義動作の危険性が説明されています。

Abortメソッドの危険レベルとしてはWin32 APITerminateThread関数と同程度で、よほどのことがないかぎり使うべきではありません。現に.NET Coreの仕様では、Thread.Abortメソッドは削除されているそうです。