CodeGym /コース /C# SELF /非同期操作でのエラー処理 ( async/ await)

非同期操作でのエラー処理 ( async/ await)

C# SELF
レベル 42, レッスン 3
使用可能

1. はじめに

まず重要な問いから始めましょう:なぜ非同期メソッドをただ信用して「全部うまくいくはず」と期待してはいけないのでしょうか?ファイル操作は例外を投げることがよくあります — ファイルが削除されているかもしれない、ディスク容量が足りない、権限がない、ファイルがロックされているなど。同期コードなら慣れたtry-catchブロックで問題を捕まえます。非同期コードでも考え方は同じですが、ニュアンスがあります:エラーはメソッド呼び出し直後ではなく、実際に操作が行われる後になって発生することがあります。

エラーが発生するタイミング

同期コードでStreamReader.Read()を使って読み取ると、例外は呼び出し行で直接飛びます — catchで捕まえておけばOKです。
非同期コードでは(await stream.ReadAsync())、エラーは操作開始時ではなく、タスクが失敗して完了したときにawaitの地点で発生します。もしawaitを付け忘れると、エラーはしばらく「見えない」ままになることがあります。

2. 非同期メソッドで例外を捕まえる方法

典型的なテンプレートをすぐに見てみましょう:


try
{
    using FileStream fs = new FileStream("myfile.txt", FileMode.Open);
    byte[] buffer = new byte[1024];
    int bytesRead = await fs.ReadAsync(buffer, 0, buffer.Length);
    // さらに処理...
}
catch (IOException ex)
{
    Console.WriteLine("入出力エラー: " + ex.Message);
}
catch (UnauthorizedAccessException ex)
{
    Console.WriteLine("ファイルへのアクセス権がありません: " + ex.Message);
}
catch (Exception ex)
{
    Console.WriteLine("未知のエラー: " + ex);
}

そう、シンプルです — 見慣れたtry-catchを非同期メソッド内で使います。重要なのは、そのメソッド自体がasyncであることです。そうしないとコンパイラが怒ります。

重要なポイント: awaitをどこに置くか


Task<int> readTask = fs.ReadAsync(buffer, 0, buffer.Length);
// ... ここでうっかり await や処理を忘れる

この場合、もしエラーが起きたらそれはタスク自体(Task)に入ります。結果を取得するまで — 例えばawait readTaskやTask.Exceptionを通して — あなたはそのエラーに気づきません。もし完全にawaitを忘れていたら、タスクは失敗しても誰にも知らせません。

3. なぜエラー処理なしの非同期コードは厄介なのか

シナリオ1: 「Fire and forget」— 初心者の罠


FileStream fs = new FileStream("file.txt", FileMode.Open);
byte[] buffer = new byte[8000];
fs.ReadAsync(buffer, 0, buffer.Length);
// その後プログラムは普通に動き続ける

読み取り操作は「並行で泳ぎに行く」ような状態になり、もしそれが失敗しても、どのcatchもその例外を捕まえません。例外はタスク内部に隠れます。このパターンは「fire and forget」と呼ばれ、実アプリでは重大なエラーの見逃しやリソースリークを招きます。

シナリオ2: awaitなしの非同期メソッド


Task t = MyAsyncMethod();
// ... ここでいろいろやって、その後 t を忘れる

MyAsyncMethod 内で発生したエラーは、あなたが明示的にタスクを待たない限り(await t や t.Wait())表に出てきません。

4. 非同期タスクのエラーを正しく捕まえる方法

戦略1: いつもawaitを使う


try
{
    await SomeFileOperationAsync();
}
catch (Exception ex)
{
    Console.WriteLine("何かがうまくいかなかった: " + ex.Message);
}

こうすると例外は待ちの場所で投げられ、失われません。

戦略2: .ContinueWithでの処理

何らかの理由でawaitを使わない場合、ContinueWithでエラーハンドラを付けることができます:


var task = fs.ReadAsync(buffer, 0, buffer.Length);
task.ContinueWith(t =>
{
    if (t.Exception != null)
        Console.WriteLine("非同期読み取りでのエラー: " + t.Exception.InnerException);
}, TaskContinuationOptions.OnlyOnFaulted);

正直に言うと、現代のC#アプリではあまり使われません — async/awaitの方がコードをシンプルで読みやすくします。

ファイル操作の非同期で起こりうる例外

5. 例: エラー処理付きの非同期読み取り

このロジックをアプリに追加してみます:


using System;
using System.IO;
using System.Threading.Tasks;

namespace FileAsyncDemo
{
    class Program
    {
        static async Task Main()
        {
            string path = "bigfile.txt";
            byte[] buffer = new byte[4096];

            try
            {
                using FileStream fs = new FileStream(path, FileMode.Open);
                int bytesRead = await fs.ReadAsync(buffer, 0, buffer.Length);
                Console.WriteLine($"読み取ったバイト数: {bytesRead} バイト, ファイル: {path}");
            }
            catch (FileNotFoundException ex)
            {
                Console.WriteLine("ファイルが見つかりません: " + ex.Message);
            }
            catch (UnauthorizedAccessException ex)
            {
                Console.WriteLine("ファイルへのアクセス権がありません: " + ex.Message);
            }
            catch (IOException ex)
            {
                Console.WriteLine("読み取り/書き込みエラー: " + ex.Message);
            }
            catch (Exception ex)
            {
                Console.WriteLine("その他のエラー: " + ex.Message);
            }
        }
    }
}

重要な注意

もしMainでawaitを使わず、ただTask result = SomeAsyncMethod(); のようにしていると — エラーは「黙る」ことになり、結果を取得しようとしたときに初めて出てきます。

6. エラー処理でよくあるミスと「踏み抜きやすい地雷」

非同期メソッドに対してawaitを付け忘れる — エラーが適切なタイミングで浮上せず、プログラムの挙動が予測不能になる。

非同期呼び出しをtry-catchで囲まない — アプリが最初の障害でクラッシュする。

汎用的なExceptionだけを捕まえて、UnauthorizedAccessExceptionやOperationCanceledExceptionのような特有の例外を無視する — 結果として診断が難しくなる。

ログなしで「fire and forget」タスクを使う — 例外がTaskの中に隠れてしまう。

タスクが完了したからといって成功したと考えるのは誤り。結果は明示的に待って(await)例外を処理する必要がある。

2
タスク
C# SELF, レベル 42, レッスン 3
ロック未解除
ファイル読み取り時のエラー処理
ファイル読み取り時のエラー処理
コメント
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION