通常の処理であれば同一ファイルへの書き込みと読み込みはそれぞれ他方が終わってから行なわれるので、あまり気にすることはありません。しかし非同期処理の場合は書き込みが完了するまえに別の書き込みの処理が始まったり、書き込みをしている最中に読み込みがはじまることがあります。この場合はどうなるのでしょうか?
以下はテキストファイルへ0.1秒ごとに”0″~”9″までを書き込むコードです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
using System; using System.IO; using System.Threading.Tasks; using System.Threading; class Program { static void Main() { SaveFile(1); Console.ReadLine(); // これがないとSaveFileが完了するまえにプログラムが終わってしまう } async static Task SaveFile(int id) { try { StreamWriter sw = new StreamWriter(@".\file.txt"); for (int i = 0; i < 10; i++) { sw.WriteLine(i.ToString()); Console.WriteLine($"引数:{id} \"{i}\"を書き込んでいます"); await Task.Delay(100); } sw.Close(); } catch { Console.WriteLine($"例外:引数{id}の書き込みに失敗しました"); Console.WriteLine(ex.Message); return; } Console.WriteLine($"引数{id}の書き込み終了"); } } |
実行結果
1 2 3 4 5 6 7 8 9 10 11 |
引数:1 "0"を書き込んでいます 引数:1 "1"を書き込んでいます 引数:1 "2"を書き込んでいます 引数:1 "3"を書き込んでいます 引数:1 "4"を書き込んでいます 引数:1 "5"を書き込んでいます 引数:1 "6"を書き込んでいます 引数:1 "7"を書き込んでいます 引数:1 "8"を書き込んでいます 引数:1 "9"を書き込んでいます 引数1の書き込み終了 |
同時に同じファイルに書き込もうとした場合
では同時に同じファイルに書き込もうとした場合はどうなるのでしょうか?
1 2 3 4 5 6 7 8 9 |
class Program { static void Main() { SaveFile(1); SaveFile(2); Console.ReadLine(); } } |
実行結果
1 2 3 4 5 6 7 8 9 10 11 12 13 |
引数:1 "0"を書き込んでいます 例外:引数:2の書き込みに失敗しました The process cannot access the file '(フォルダのパス)\file.txt' because it is being used by another process. 引数:1 "1"を書き込んでいます 引数:1 "2"を書き込んでいます 引数:1 "3"を書き込んでいます 引数:1 "4"を書き込んでいます 引数:1 "5"を書き込んでいます 引数:1 "6"を書き込んでいます 引数:1 "7"を書き込んでいます 引数:1 "8"を書き込んでいます 引数:1 "9"を書き込んでいます 引数1の書き込み終了 |
回避策
同時に書き込もうとすると例外が発生してしまいます。この問題を回避するためにはawaitをつけるしかありません。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
class Program { static void Main() { Func1(); // static async void Main() とは書けないので別のメソッドを定義する Console.ReadLine(); } static async void Func1() { await SaveFile(1); await SaveFile(2); // 最後の処理であれば await はなくてもよいが警告がでる } } |
同時に読み込もうとした場合
では同時に読み込もうとした場合はどうなるのでしょうか?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
class Program { static void Main() { ReadFile(1); ReadFile(2); Console.ReadLine(); } async static Task ReadFile(int i) { try { StreamReader sr = new StreamReader(@".\file.txt"); // 上記で生成されたテキストファイル List<string> vs = new List<string>(); while (true) { string str = sr.ReadLine(); if (str == null) break; vs.Add(str); Console.WriteLine($"引数:{i} \"{str}\"を読み込みました"); await Task.Delay(100); } sr.Close(); Console.WriteLine($"引数:{i} すべて読み込みました"); } catch (Exception ex) { Console.WriteLine($"例外:引数:{i}の読み込みに失敗しました"); Console.WriteLine(ex.Message); } } } |
この場合は同時に読み込むことができます。非同期処理なので引数:2の処理のほうが先に出力される場合があります。
実行結果
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
引数:1 "0"を読み込みました 引数:2 "0"を読み込みました 引数:2 "1"を読み込みました 引数:1 "1"を読み込みました 引数:2 "2"を読み込みました 引数:1 "2"を読み込みました 引数:1 "3"を読み込みました 引数:2 "3"を読み込みました 引数:1 "4"を読み込みました 引数:2 "4"を読み込みました 引数:2 "5"を読み込みました 引数:1 "5"を読み込みました 引数:2 "6"を読み込みました 引数:1 "6"を読み込みました 引数:1 "7"を読み込みました 引数:2 "7"を読み込みました 引数:1 "8"を読み込みました 引数:2 "8"を読み込みました 引数:2 "9"を読み込みました 引数:1 "9"を読み込みました 引数:2 すべて読み込みました 引数:1 すべて読み込みました |
同時に書き込みと読み込みをしようとした場合
では書き込みをしている最中に読み込みを開始したらどうなるのでしょうか?
1 2 3 4 5 6 7 8 9 10 11 12 |
class Program { static void Main() { SaveFile(1); Thread.Sleep(2000); // 2秒あればおそらくSaveFile(1)の処理は完了するのではないか? ReadFile(1); Console.ReadLine(); } } |
2秒あればおそらくSaveFileメソッドは完了するということで2秒待ってから読み込みを開始しています。この場合は特に問題なく読み込みの処理がおこなわれます。
ではThread.Sleepメソッドの引数を800くらいにすればどうなるでしょうか? 書き込みは完了していないのに読み込みが開始されることになります。
実行結果は以下のとおり。書き込みの最中に読み込もうとすると例外が発生します。
実行結果
1 2 3 4 5 6 7 8 9 10 11 12 13 |
引数:1 "0"を書き込んでいます 引数:1 "1"を書き込んでいます 引数:1 "2"を書き込んでいます 引数:1 "3"を書き込んでいます 引数:1 "4"を書き込んでいます 引数:1 "5"を書き込んでいます 引数:1 "6"を書き込んでいます 引数:1 "7"を書き込んでいます 例外:引数:1の読み込みに失敗しました The process cannot access the file '(フォルダのパス)\file.txt' because it is being used by another process. 引数:1 "8"を書き込んでいます 引数:1 "9"を書き込んでいます 引数1の書き込み終了 |
では逆に読み込みをしている最中に書き込みを開始したらどうなるのでしょうか?
1 2 3 4 5 6 7 8 9 10 11 |
class Program { static void Main() { ReadFile(1); Thread.Sleep(800); SaveFile(1); // ReadFileメソッドは完了していないタイミングで書き込みを開始する Console.ReadLine(); } } |
例外が発生して書き込みは失敗します。
実行結果
1 2 3 4 5 6 7 8 9 10 11 12 13 |
引数:1 "0"を読み込みました 引数:1 "1"を読み込みました 引数:1 "2"を読み込みました 引数:1 "3"を読み込みました 引数:1 "4"を読み込みました 引数:1 "5"を読み込みました 引数:1 "6"を読み込みました 引数:1 "7"を読み込みました 例外:引数:1の書き込みに失敗しました The process cannot access the file '(フォルダのパス)\file.txt' because it is being used by another process. 引数:1 "8"を読み込みました 引数:1 "9"を読み込みました 引数:1 すべて読み込みました |
非同期処理と例外処理
上の場合は例外を捕捉できますが、下の場合は例外を捕捉できません。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
class Program { static void Main() { Task.Run(() => { try { throw new Exception("意図的に例外を発生させる"); } catch (Exception ex) { Console.WriteLine(ex.Message); // "意図的に例外を発生させる"と表示される } }); Console.ReadLine(); } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
class Program { static void Main() { try { Task.Run(() => { throw new Exception("意図的に例外を発生させる"); // この部分で発生した例外はこのなかでしか捕捉できない // Task.Runの前にawaitをつければ捕捉できるが・・・ }); } catch (Exception ex) { Console.WriteLine(ex.Message); // この部分は実行されないのでなにも表示されない } Console.ReadLine(); } } |