そこで、これらの質問や他の同様の質問のいくつかをすばやく確認した後、同期(およびファイル保存)方法としてファイルを使用する2つの別々のプログラムの問題を解決するために、今日の午後に陽気な追跡を行いました。少し変わった状況ですが、「ファイルがロックされているかどうかを確認し、ロックされていない場合は開く」というアプローチの問題がはっきりと浮き彫りになりました。
問題はこれです:ファイルをチェックしてから実際にファイルを開くまでの間にファイルがロックされる可能性があります。散発的なファイルをコピーできません。ファイルを探していない場合は別のプロセスエラーによって使用されるため、追跡するのは非常に困難です。
基本的な解決策は、catchブロック内でファイルを開こうとすることです。これにより、ファイルがロックされている場合は、再試行できます。そうすれば、チェックからオープンまでの経過時間はなく、OSは同時にそれらを実行します。
ここでのコードはFile.Copyを使用していますが、Fileクラスの静的メソッド(File.Open、File.ReadAllText、File.WriteAllTextなど)でも同様に機能します。
/// <param name="timeout">how long to keep trying in milliseconds</param>
static void safeCopy(string src, string dst, int timeout)
{
while (timeout > 0)
{
try
{
File.Copy(src, dst);
//don't forget to either return from the function or break out fo the while loop
break;
}
catch (IOException)
{
//you could do the sleep in here, but its probably a good idea to exit the error handler as soon as possible
}
Thread.Sleep(100);
//if its a very long wait this will acumulate very small errors.
//For most things it's probably fine, but if you need precision over a long time span, consider
// using some sort of timer or DateTime.Now as a better alternative
timeout -= 100;
}
}
並列処理に関するもう1つの小さな注意:
これは同期メソッドであり、待機中とスレッドでの作業中の両方でスレッドをブロックします。これは最も簡単な方法ですが、ファイルが長時間ロックされたままになると、プログラムが応答しなくなる可能性があります。並列処理はあまりにも大きなトピックであり、ここで詳しく説明することはできません(非同期の読み取り/書き込みを設定できる方法の数は、やや馬鹿げています)が、並列化できる1つの方法があります。
public class FileEx
{
public static async void CopyWaitAsync(string src, string dst, int timeout, Action doWhenDone)
{
while (timeout > 0)
{
try
{
File.Copy(src, dst);
doWhenDone();
break;
}
catch (IOException) { }
await Task.Delay(100);
timeout -= 100;
}
}
public static async Task<string> ReadAllTextWaitAsync(string filePath, int timeout)
{
while (timeout > 0)
{
try {
return File.ReadAllText(filePath);
}
catch (IOException) { }
await Task.Delay(100);
timeout -= 100;
}
return "";
}
public static async void WriteAllTextWaitAsync(string filePath, string contents, int timeout)
{
while (timeout > 0)
{
try
{
File.WriteAllText(filePath, contents);
return;
}
catch (IOException) { }
await Task.Delay(100);
timeout -= 100;
}
}
}
そして、これがどのように使用できるかです:
public static void Main()
{
test_FileEx();
Console.WriteLine("Me First!");
}
public static async void test_FileEx()
{
await Task.Delay(1);
//you can do this, but it gives a compiler warning because it can potentially return immediately without finishing the copy
//As a side note, if the file is not locked this will not return until the copy operation completes. Async functions run synchronously
//until the first 'await'. See the documentation for async: https://msdn.microsoft.com/en-us/library/hh156513.aspx
CopyWaitAsync("file1.txt", "file1.bat", 1000);
//this is the normal way of using this kind of async function. Execution of the following lines will always occur AFTER the copy finishes
await CopyWaitAsync("file1.txt", "file1.readme", 1000);
Console.WriteLine("file1.txt copied to file1.readme");
//The following line doesn't cause a compiler error, but it doesn't make any sense either.
ReadAllTextWaitAsync("file1.readme", 1000);
//To get the return value of the function, you have to use this function with the await keyword
string text = await ReadAllTextWaitAsync("file1.readme", 1000);
Console.WriteLine("file1.readme says: " + text);
}
//Output:
//Me First!
//file1.txt copied to file1.readme
//file1.readme says: Text to be duplicated!