ファイルがアクセス可能か調べる

最近はプログラミングでハマって半日苦しむというようなことは無くなってきましたが、久々にハマりました。

ファイルシステムウォッチャ ( FileSystemWatcher ) を使ってディレクトリを監視しておき、新しいファイルが作成されたのを検知したら、そのファイルにアクセスして中身を読むというコードを書いていたら例外が発生してしまうという悲しい状態。

ほとんどうまくいくのですが、ときどき失敗してしまうのです。産業用のミッションクリティカルなアプリケーションだったので、万が一の失敗も許されません。( といってもバグったら許してもらえるように平謝りしますけどね、笑 )

FileSystemWatcher は「ファイルが作成され始めました」ということを通知するのであって、「ファイルの作成が完了しました」ということを通知するわけではないのです。したがってファイルの作成が完了するまで、ファイルの中身を取り出していけません。

ファイルがアクセス可能かどうか調べるには、FileStream クラスを使ってファイルの読み込みを例外が出なくなるまでループでトライすればよいです。下記のコードを使って動きを確認してください。

Form1 に button1 と button2 を配置してください。

button1 で時間をかけてファイルを書き込みます。button2 を連打してファイルがアクセス可能か調べてみてください。何度か押したら、「ファイルがアクセスできるようになりました」というメッセージボックスが出現するはずです。

using System;
using System.Windows.Forms;
using System.IO;
using System.Threading.Tasks;
using System.Diagnostics;

namespace aaa
{
	public partial class Form1 : Form
	{

		const String FILE_PATH_FOR_DEBUG = "c:/tmp/this_is_test.bin";

		public Form1()
		{
			InitializeComponent();

			// button1.Text = "ファイル書き込み";
			// button2.Text = "ファイルアクセスチェック";
		}

		// ファイルがアクセスできるか確認するメソッド.
		private bool wait_for_file_write_completion( string filepath, int msec_timeout )
		{

			Stopwatch sw = new Stopwatch();
			sw.Start();

			// ファイルがアクセスできるかどうかタイムアウト付き無限ループでチェックする.
			for (;;)
			{

				try
				{
					FileMode file_mode = FileMode.Open;
					FileAccess file_access = FileAccess.Read;
					FileShare file_share = FileShare.None;
					using ( FileStream fs = new FileStream( filepath, file_mode, file_access, file_share ) )
					{
						// ファイルアクセス可能として true 脱出する.
						return true;
					}
				}
				catch ( IOException )
				{
					// すぐに次回のチェックをすると無駄なので少し待つ.
					const int MSEC_WAIT_NEXT_CHECK = 100;
					System.Threading.Thread.Sleep( MSEC_WAIT_NEXT_CHECK );
				}

				if ( sw.ElapsedMilliseconds > msec_timeout )
				{
					// タイムアウトとして false 脱出する.
					return false;
				}

			}

		}

		private void button1_Click( object sender, EventArgs e )
		{

			Task.Run(() => {

				int w = 2560;
				int h = 1280;
				int numpix = w * h;
				byte[] buffer = new byte[numpix];

				String fp = FILE_PATH_FOR_DEBUG;
				FileMode fm = FileMode.Create;
				FileAccess fa = FileAccess.Write;
				using ( FileStream fs = new FileStream( fp, fm, fa ) )
				{ 

					int one_scan_bytes = w * sizeof( byte );

					// ループで書き込んで、わざと時間をかける.
					for ( int j = 0; j < h; j++ )
					{

						int offset = w * j;
						int count = w;
						fs.Write( buffer, offset, count );

						// 少し待つ.
						System.Threading.Thread.Sleep(1);

					}

				}

			});

		}

		private void button2_Click( object sender, EventArgs e )
		{

			String str = "";
			String cap = Application.ProductName;
			MessageBoxButtons btn = MessageBoxButtons.OK;
			MessageBoxIcon icn;

			// ここを調整して試してください.
			int msec_tmo = 1000;

			bool ret = wait_for_file_write_completion( FILE_PATH_FOR_DEBUG, msec_tmo );
			if ( ret )
			{
				str = "ファイルがアクセスできるようになりました.";
				icn = MessageBoxIcon.Information;
			}
			else
			{
				str = "ファイル監視タイムアウトに達しました.";
				icn = MessageBoxIcon.Warning;
			}

			// メッセージボックスを表示する.
			MessageBox.Show( str, cap, btn, icn );

		}
	}
}

あsdb