C#の非同期プログラミングにおけるエラーハンドリングテクニックの完全ガイド

非同期プログラミングは、C#でのパフォーマンス向上に不可欠ですが、エラーハンドリングが課題です。本記事では、非同期プログラミングの基礎から始め、具体的なエラーハンドリングテクニック、応用例やテスト方法までを詳細に解説し、効果的なエラーハンドリングを実現するための完全ガイドを提供します。

目次
  1. 非同期プログラミングの基礎
    1. 基本的な非同期メソッドの例
    2. 非同期メソッドの利点
  2. 非同期メソッドのエラーハンドリング
    1. try-catchブロックを使用したエラーハンドリング
    2. 非同期メソッドの例外伝播
    3. 例外の再スロー
  3. タスクベースの非同期パターン(TAP)のエラーハンドリング
    1. タスクのエラー検出
    2. Task.ContinueWithを用いたエラーハンドリング
    3. Task.WhenAllとTask.WhenAnyのエラーハンドリング
  4. 継続タスク(ContinueWith)を用いたエラーハンドリング
    1. ContinueWithの基本的な使い方
    2. 継続タスクの条件付き実行
    3. 複数の継続タスクの連鎖
  5. 非同期ストリームのエラーハンドリング
    1. 非同期ストリームの基本
    2. 非同期ストリームのエラーハンドリング
    3. 非同期ストリームの部分エラー処理
  6. 例外フィルターと再試行パターン
    1. 例外フィルターの使用
    2. 再試行パターンの実装
    3. 例外フィルターと再試行パターンの組み合わせ
  7. ログとモニタリングの実装
    1. ロギングの基本
    2. 非同期メソッドのロギング
    3. モニタリングの導入
  8. テストとデバッグのベストプラクティス
    1. ユニットテストの作成
    2. 非同期コードのデバッグ
    3. テストカバレッジの確保
  9. 応用例と実践
    1. ウェブAPIの非同期呼び出しとエラーハンドリング
    2. データベース操作の非同期エラーハンドリング
    3. 非同期ファイル操作のエラーハンドリング
  10. まとめ

非同期プログラミングの基礎

非同期プログラミングは、システムの応答性を向上させるための重要な技術です。C#では、async/awaitキーワードを使って非同期操作を簡潔に表現できます。これにより、UIスレッドをブロックせずにバックグラウンドで時間のかかる操作を実行できます。以下に基本的な非同期メソッドの構造を示します。

基本的な非同期メソッドの例

public async Task<string> GetDataAsync()
{
    await Task.Delay(1000); // 1秒待機
    return "データ取得完了";
}

この例では、Task.Delayメソッドを使用して1秒間待機し、その後文字列を返します。awaitキーワードにより、待機中に他の処理を続行できます。

非同期メソッドの利点

  • 応答性の向上: UIがフリーズしないため、ユーザーエクスペリエンスが向上します。
  • 効率的なリソース使用: スレッドを効率的に使用し、システム全体のパフォーマンスが向上します。

次のセクションでは、非同期メソッドにおける具体的なエラーハンドリング技術について説明します。

非同期メソッドのエラーハンドリング

非同期メソッドでエラーハンドリングを適切に行うことは、アプリケーションの信頼性を高めるために重要です。C#のasync/awaitキーワードを使用する場合、例外処理は同期メソッドとほぼ同じ方法で行えます。

try-catchブロックを使用したエラーハンドリング

非同期メソッド内で発生した例外は、try-catchブロックを用いて捕捉し、適切に処理することができます。

public async Task<string> GetDataAsync()
{
    try
    {
        await Task.Delay(1000); // 1秒待機
        throw new InvalidOperationException("データ取得中にエラーが発生しました");
    }
    catch (Exception ex)
    {
        // エラーログを記録するなどの処理
        return $"エラー: {ex.Message}";
    }
}

この例では、InvalidOperationExceptionが発生した際にcatchブロックで捕捉し、エラーメッセージを返しています。

非同期メソッドの例外伝播

非同期メソッド内で発生した例外は、awaitキーワードを使用する呼び出し元に伝播します。このため、呼び出し元でも例外処理を行うことが重要です。

public async Task ExecuteAsync()
{
    try
    {
        string result = await GetDataAsync();
        Console.WriteLine(result);
    }
    catch (Exception ex)
    {
        Console.WriteLine($"エラーが発生しました: {ex.Message}");
    }
}

この例では、GetDataAsyncメソッド内で発生した例外が呼び出し元のExecuteAsyncメソッドで捕捉され、エラーメッセージが表示されます。

例外の再スロー

特定の例外を再スローする場合も、通常の例外処理と同様に行えます。

public async Task<string> GetDataAsync()
{
    try
    {
        await Task.Delay(1000);
        throw new InvalidOperationException("データ取得中にエラーが発生しました");
    }
    catch (InvalidOperationException ex)
    {
        // 特定の例外を再スロー
        throw;
    }
}

このセクションでは、async/awaitを用いた非同期メソッドの基本的なエラーハンドリング技術を紹介しました。次のセクションでは、タスクベースの非同期パターン(TAP)を使用したエラーハンドリングについて詳しく説明します。

タスクベースの非同期パターン(TAP)のエラーハンドリング

タスクベースの非同期パターン(Task-based Asynchronous Pattern, TAP)は、C#における非同期プログラミングの標準的な手法です。TAPを使用することで、非同期メソッドはTaskまたはTaskを返します。このパターンでは、タスクの完了、成功、失敗を簡単にハンドリングできます。

タスクのエラー検出

タスクの実行中に発生した例外は、タスクのExceptionプロパティを通じて検出できます。これにより、非同期メソッドの呼び出し元で適切にエラーハンドリングを行うことができます。

public async Task<string> GetDataAsync()
{
    await Task.Delay(1000); // 1秒待機
    throw new InvalidOperationException("データ取得中にエラーが発生しました");
}

public async Task ExecuteAsync()
{
    Task<string> dataTask = GetDataAsync();
    try
    {
        string result = await dataTask;
        Console.WriteLine(result);
    }
    catch (Exception ex)
    {
        Console.WriteLine($"エラーが発生しました: {ex.Message}");
    }
}

この例では、GetDataAsyncメソッドで発生した例外がdataTaskExceptionプロパティに格納され、awaitを使用することでキャッチできます。

Task.ContinueWithを用いたエラーハンドリング

ContinueWithメソッドを使用すると、タスクの完了後に継続タスクを実行できます。これにより、エラーが発生した場合に特定の処理を行うことが可能です。

public Task<string> GetDataAsync()
{
    return Task.Run(async () =>
    {
        await Task.Delay(1000);
        throw new InvalidOperationException("データ取得中にエラーが発生しました");
    });
}

public void Execute()
{
    Task<string> dataTask = GetDataAsync();
    dataTask.ContinueWith(task =>
    {
        if (task.IsFaulted)
        {
            Console.WriteLine($"エラーが発生しました: {task.Exception?.GetBaseException().Message}");
        }
        else
        {
            Console.WriteLine(task.Result);
        }
    });
}

この例では、GetDataAsyncメソッドから返されるタスクに対してContinueWithメソッドを使用し、エラーが発生した場合にエラーメッセージを表示しています。

Task.WhenAllとTask.WhenAnyのエラーハンドリング

複数のタスクを同時に実行し、その結果を待つ場合、Task.WhenAllTask.WhenAnyメソッドを使用できます。これらのメソッドでもエラーハンドリングが可能です。

public async Task ExecuteMultipleTasksAsync()
{
    Task task1 = Task.Delay(1000);
    Task task2 = Task.Run(() => throw new InvalidOperationException("タスク2でエラー発生"));

    try
    {
        await Task.WhenAll(task1, task2);
    }
    catch (Exception ex)
    {
        Console.WriteLine($"複数タスクの実行中にエラーが発生しました: {ex.Message}");
    }
}

この例では、Task.WhenAllを使用して複数のタスクを待機し、その中で発生した例外をキャッチしています。

次のセクションでは、継続タスク(ContinueWith)を用いたエラーハンドリングについてさらに詳しく説明します。

継続タスク(ContinueWith)を用いたエラーハンドリング

継続タスク(ContinueWith)は、タスクが完了した後に続けて実行する処理を定義するための強力な方法です。特に、エラーが発生した場合に特定の処理を実行するために役立ちます。

ContinueWithの基本的な使い方

ContinueWithメソッドを使用すると、元のタスクが完了した後に実行される継続タスクを定義できます。これにより、タスクの結果に基づいて次の処理を柔軟に決定できます。

public Task<string> GetDataAsync()
{
    return Task.Run(async () =>
    {
        await Task.Delay(1000);
        throw new InvalidOperationException("データ取得中にエラーが発生しました");
    });
}

public void Execute()
{
    Task<string> dataTask = GetDataAsync();
    dataTask.ContinueWith(task =>
    {
        if (task.IsFaulted)
        {
            Console.WriteLine($"エラーが発生しました: {task.Exception?.GetBaseException().Message}");
        }
        else
        {
            Console.WriteLine(task.Result);
        }
    });
}

この例では、GetDataAsyncメソッドから返されるタスクに対してContinueWithメソッドを使用し、エラーが発生した場合にエラーメッセージを表示し、正常に完了した場合には結果を表示します。

継続タスクの条件付き実行

継続タスクは、タスクの状態に応じて条件付きで実行することもできます。たとえば、成功した場合と失敗した場合で異なる処理を実行することが可能です。

public void ExecuteConditional()
{
    Task<string> dataTask = GetDataAsync();
    dataTask.ContinueWith(task =>
    {
        if (task.IsCompletedSuccessfully)
        {
            Console.WriteLine($"成功: {task.Result}");
        }
    }, TaskContinuationOptions.OnlyOnRanToCompletion);

    dataTask.ContinueWith(task =>
    {
        Console.WriteLine($"失敗: {task.Exception?.GetBaseException().Message}");
    }, TaskContinuationOptions.OnlyOnFaulted);
}

この例では、TaskContinuationOptions.OnlyOnRanToCompletionオプションを使用して、タスクが正常に完了した場合のみ実行される継続タスクを定義し、TaskContinuationOptions.OnlyOnFaultedオプションを使用して、タスクが失敗した場合にのみ実行される別の継続タスクを定義しています。

複数の継続タスクの連鎖

複数の継続タスクを連鎖させることで、複雑なエラーハンドリングや後続処理を構築することができます。

public void ExecuteChainedContinuations()
{
    Task<string> dataTask = GetDataAsync();
    dataTask.ContinueWith(task =>
    {
        if (task.IsFaulted)
        {
            Console.WriteLine($"初期タスク失敗: {task.Exception?.GetBaseException().Message}");
            return Task.FromResult("デフォルトデータ");
        }
        return Task.FromResult(task.Result);
    }).ContinueWith(task =>
    {
        Console.WriteLine($"最終結果: {task.Result}");
    });
}

この例では、初期タスクが失敗した場合にはデフォルトデータを返し、それを用いて次の継続タスクを実行しています。これにより、エラーが発生しても後続の処理を続行できます。

次のセクションでは、非同期ストリームのエラーハンドリングについて詳しく説明します。

非同期ストリームのエラーハンドリング

非同期ストリーム(IAsyncEnumerable)は、C# 8.0で導入された機能で、非同期にデータをストリーム処理する方法を提供します。非同期ストリームを使用することで、大量のデータを効率的に処理できますが、エラーハンドリングも重要な課題となります。

非同期ストリームの基本

非同期ストリームは、await foreachループを使用してデータを逐次処理します。以下は、非同期ストリームを使用する基本的な例です。

public async IAsyncEnumerable<int> GenerateNumbersAsync()
{
    for (int i = 0; i < 10; i++)
    {
        await Task.Delay(500);
        if (i == 5) throw new InvalidOperationException("5でエラーが発生しました");
        yield return i;
    }
}

public async Task ProcessNumbersAsync()
{
    await foreach (var number in GenerateNumbersAsync())
    {
        Console.WriteLine(number);
    }
}

この例では、数値を生成する非同期ストリームGenerateNumbersAsyncを定義し、5が生成される際に例外をスローします。

非同期ストリームのエラーハンドリング

非同期ストリーム内で発生する例外は、await foreachループ内で捕捉することができます。以下にその例を示します。

public async Task ProcessNumbersWithErrorHandlingAsync()
{
    try
    {
        await foreach (var number in GenerateNumbersAsync())
        {
            Console.WriteLine(number);
        }
    }
    catch (Exception ex)
    {
        Console.WriteLine($"エラーが発生しました: {ex.Message}");
    }
}

この例では、await foreachループ内で例外を捕捉し、エラーメッセージを表示します。

非同期ストリームの部分エラー処理

場合によっては、エラーが発生した後も処理を続行する必要があることがあります。以下はその方法を示します。

public async Task ProcessNumbersWithPartialErrorHandlingAsync()
{
    await foreach (var number in GenerateNumbersAsync().WithPartialErrorHandling())
    {
        Console.WriteLine(number);
    }
}

public static async IAsyncEnumerable<T> WithPartialErrorHandling<T>(this IAsyncEnumerable<T> source)
{
    await using var enumerator = source.GetAsyncEnumerator();
    while (true)
    {
        try
        {
            if (!await enumerator.MoveNextAsync()) break;
            yield return enumerator.Current;
        }
        catch (Exception ex)
        {
            Console.WriteLine($"ストリーム処理中にエラーが発生しました: {ex.Message}");
        }
    }
}

この例では、WithPartialErrorHandling拡張メソッドを使用して、エラーが発生した場合にも処理を続行する非同期ストリームを構築しています。

次のセクションでは、例外フィルターと再試行パターンを使用した堅牢なエラーハンドリング技術について説明します。

例外フィルターと再試行パターン

例外フィルターと再試行パターンは、非同期プログラミングにおいて堅牢なエラーハンドリングを実現するための重要なテクニックです。これらの技術を組み合わせることで、特定の条件下で例外を処理し、エラー発生時に再試行を行うことが可能です。

例外フィルターの使用

例外フィルターは、特定の条件を満たす場合にのみ例外をキャッチするために使用されます。これにより、例外ハンドリングをより柔軟に制御できます。

public async Task<string> GetDataWithFilterAsync()
{
    try
    {
        await Task.Delay(1000);
        throw new InvalidOperationException("データ取得中にエラーが発生しました");
    }
    catch (InvalidOperationException ex) when (ex.Message.Contains("エラー"))
    {
        // 条件に一致する場合のみこのブロックが実行される
        return $"フィルタ処理されたエラー: {ex.Message}";
    }
}

この例では、InvalidOperationExceptionが発生した場合に、例外メッセージに「エラー」という文字列が含まれている場合のみキャッチして処理します。

再試行パターンの実装

再試行パターンは、エラーが発生した際に一定回数再試行を行うことで、一時的な問題を解決するために使用されます。以下は、再試行パターンを実装した例です。

public async Task<string> GetDataWithRetryAsync()
{
    int retryCount = 3;
    int delay = 1000;

    for (int i = 0; i < retryCount; i++)
    {
        try
        {
            await Task.Delay(delay);
            throw new InvalidOperationException("データ取得中に一時的なエラーが発生しました");
        }
        catch (InvalidOperationException ex) when (i < retryCount - 1)
        {
            Console.WriteLine($"再試行 {i + 1}: {ex.Message}");
            await Task.Delay(delay); // 再試行前に待機
        }
    }

    // 最終的に成功しなかった場合の処理
    throw new InvalidOperationException("データ取得に失敗しました");
}

この例では、データ取得中に一時的なエラーが発生した場合に最大3回まで再試行を行います。再試行の間には一定の待機時間を設けています。

例外フィルターと再試行パターンの組み合わせ

例外フィルターと再試行パターンを組み合わせることで、特定の条件下でのみ再試行を行う柔軟なエラーハンドリングが可能です。

public async Task<string> GetDataWithFilterAndRetryAsync()
{
    int retryCount = 3;
    int delay = 1000;

    for (int i = 0; i < retryCount; i++)
    {
        try
        {
            await Task.Delay(delay);
            throw new InvalidOperationException("一時的なエラーが発生しました");
        }
        catch (InvalidOperationException ex) when (ex.Message.Contains("一時的") && i < retryCount - 1)
        {
            Console.WriteLine($"再試行 {i + 1}: {ex.Message}");
            await Task.Delay(delay);
        }
    }

    throw new InvalidOperationException("データ取得に失敗しました");
}

この例では、エラーメッセージに「一時的」という文字列が含まれている場合にのみ再試行を行い、それ以外の場合は即座にエラーをスローします。

次のセクションでは、エラー検出とトラブルシューティングを容易にするためのログとモニタリングの設定方法について解説します。

ログとモニタリングの実装

非同期プログラミングにおけるエラーハンドリングの一環として、適切なログとモニタリングの設定は不可欠です。これにより、エラーの検出とトラブルシューティングが容易になります。C#での非同期処理におけるログとモニタリングの実装方法を紹介します。

ロギングの基本

C#では、ログを記録するために一般的にILoggerインターフェースを使用します。以下は、Microsoft.Extensions.Loggingパッケージを使用したロギングの基本的な例です。

using Microsoft.Extensions.Logging;

public class DataService
{
    private readonly ILogger<DataService> _logger;

    public DataService(ILogger<DataService> logger)
    {
        _logger = logger;
    }

    public async Task<string> GetDataAsync()
    {
        _logger.LogInformation("データ取得を開始します");

        try
        {
            await Task.Delay(1000);
            throw new InvalidOperationException("データ取得中にエラーが発生しました");
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "データ取得中にエラーが発生しました");
            throw;
        }
    }
}

この例では、GetDataAsyncメソッドの開始時とエラー発生時にログを記録します。ILoggerインターフェースを使用することで、ログのレベルや出力先を柔軟に設定できます。

非同期メソッドのロギング

非同期メソッド内でのロギングは、例外が発生した際の詳細な情報を提供し、問題のトラブルシューティングを支援します。

public async Task<string> GetDataWithLoggingAsync()
{
    _logger.LogInformation("非同期データ取得を開始します");

    try
    {
        await Task.Delay(1000);
        throw new InvalidOperationException("データ取得中にエラーが発生しました");
    }
    catch (Exception ex)
    {
        _logger.LogError(ex, "非同期データ取得中にエラーが発生しました");
        return $"エラー: {ex.Message}";
    }
}

この例では、非同期データ取得の開始時とエラー発生時に情報とエラーログを記録しています。

モニタリングの導入

モニタリングは、アプリケーションのパフォーマンスやエラーの発生状況をリアルタイムで追跡するために重要です。アプリケーションインサイト(Application Insights)などのツールを使用して、非同期処理の監視を行います。

using Microsoft.ApplicationInsights;
using Microsoft.ApplicationInsights.DataContracts;

public class MonitoringService
{
    private readonly TelemetryClient _telemetryClient;

    public MonitoringService(TelemetryClient telemetryClient)
    {
        _telemetryClient = telemetryClient;
    }

    public async Task<string> GetDataWithMonitoringAsync()
    {
        var operation = _telemetryClient.StartOperation<RequestTelemetry>("GetDataWithMonitoringAsync");

        try
        {
            await Task.Delay(1000);
            throw new InvalidOperationException("データ取得中にエラーが発生しました");
        }
        catch (Exception ex)
        {
            _telemetryClient.TrackException(ex);
            _telemetryClient.StopOperation(operation);
            throw;
        }
    }
}

この例では、Application Insightsを使用して非同期処理の開始、例外の追跡、および操作の終了を記録しています。これにより、エラーの詳細な分析とパフォーマンスの監視が可能になります。

次のセクションでは、非同期エラーハンドリングコードのテストとデバッグ方法についてのベストプラクティスを示します。

テストとデバッグのベストプラクティス

非同期エラーハンドリングコードのテストとデバッグは、アプリケーションの信頼性を確保するために重要です。ここでは、非同期コードのテストとデバッグを効率的に行うためのベストプラクティスを紹介します。

ユニットテストの作成

非同期メソッドのユニットテストを作成するには、適切なツールとフレームワークを使用します。xUnitやNUnitは、非同期メソッドのテストに適しています。

using System.Threading.Tasks;
using Xunit;

public class DataServiceTests
{
    [Fact]
    public async Task GetDataAsync_ShouldReturnData_WhenSuccessful()
    {
        // Arrange
        var dataService = new DataService(Mock.Of<ILogger<DataService>>());

        // Act
        var result = await dataService.GetDataAsync();

        // Assert
        Assert.Equal("データ取得完了", result);
    }

    [Fact]
    public async Task GetDataAsync_ShouldLogError_WhenExceptionThrown()
    {
        // Arrange
        var loggerMock = new Mock<ILogger<DataService>>();
        var dataService = new DataService(loggerMock.Object);

        // Act & Assert
        var exception = await Assert.ThrowsAsync<InvalidOperationException>(async () => await dataService.GetDataAsync());
        Assert.Equal("データ取得中にエラーが発生しました", exception.Message);
        loggerMock.Verify(logger => logger.LogError(It.IsAny<Exception>(), It.IsAny<string>()), Times.Once);
    }
}

この例では、xUnitを使用して非同期メソッドのテストを作成し、エラーハンドリングの動作を確認しています。モックライブラリ(Moq)を使用して、ロガーの動作を検証しています。

非同期コードのデバッグ

非同期コードのデバッグは、通常の同期コードと比較して難易度が高いです。以下のポイントに注意してデバッグを行います。

  • ブレークポイントの設定: 非同期メソッド内の適切な位置にブレークポイントを設定し、コードの実行フローを確認します。
  • 例外の詳細情報: 例外が発生した場合、その詳細情報を確認し、スタックトレースを追跡します。
  • ログの活用: ログを活用して、非同期処理の流れとエラー発生箇所を特定します。

テストカバレッジの確保

非同期コードのエラーハンドリングを含むテストカバレッジを高めるために、以下の点に注意します。

  • 正常系と異常系のテスト: 非同期メソッドが正常に動作する場合と、例外が発生する場合の両方をテストします。
  • 再試行ロジックのテスト: 再試行パターンが正しく実装されているかを確認するテストを行います。
  • 異常状態のシミュレーション: モックやスタブを使用して、非同期メソッドが異常状態でも適切に動作するかを確認します。
[Fact]
public async Task GetDataWithRetryAsync_ShouldRetry_OnTransientError()
{
    // Arrange
    var loggerMock = new Mock<ILogger<DataService>>();
    var dataService = new DataService(loggerMock.Object);
    int attemptCount = 0;

    dataService.GetDataWithRetryAsync = async () =>
    {
        attemptCount++;
        if (attemptCount < 3)
        {
            throw new InvalidOperationException("一時的なエラーが発生しました");
        }
        return "データ取得成功";
    };

    // Act
    var result = await dataService.GetDataWithRetryAsync();

    // Assert
    Assert.Equal("データ取得成功", result);
    Assert.Equal(3, attemptCount);
    loggerMock.Verify(logger => logger.LogError(It.IsAny<Exception>(), It.IsAny<string>()), Times.Exactly(2));
}

この例では、一時的なエラーが発生した際に再試行ロジックが正しく動作するかをテストしています。

次のセクションでは、実際のプロジェクトでの応用例を通じて、非同期エラーハンドリング技術の実践方法を学びます。

応用例と実践

実際のプロジェクトで非同期エラーハンドリング技術を適用することで、その有効性を確認し、さらに深く理解することができます。ここでは、非同期エラーハンドリングを活用した実践例をいくつか紹介します。

ウェブAPIの非同期呼び出しとエラーハンドリング

ウェブAPIを非同期に呼び出し、エラーハンドリングを適切に行う例です。外部サービスの呼び出しは、ネットワークエラーやタイムアウトなどの例外が発生しやすい場面です。

using System.Net.Http;
using System.Net.Http.Json;
using Microsoft.Extensions.Logging;

public class ApiService
{
    private readonly HttpClient _httpClient;
    private readonly ILogger<ApiService> _logger;

    public ApiService(HttpClient httpClient, ILogger<ApiService> logger)
    {
        _httpClient = httpClient;
        _logger = logger;
    }

    public async Task<string> FetchDataAsync(string url)
    {
        try
        {
            _logger.LogInformation("API呼び出しを開始します: {Url}", url);
            var response = await _httpClient.GetAsync(url);
            response.EnsureSuccessStatusCode();
            var data = await response.Content.ReadAsStringAsync();
            _logger.LogInformation("API呼び出し成功: {Url}", url);
            return data;
        }
        catch (HttpRequestException ex) when (ex.StatusCode == System.Net.HttpStatusCode.NotFound)
        {
            _logger.LogWarning("APIリソースが見つかりません: {Url}", url);
            return "リソースが見つかりません";
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "API呼び出し中にエラーが発生しました: {Url}", url);
            throw;
        }
    }
}

この例では、HttpClientを使用して非同期にAPIを呼び出し、HttpRequestExceptionや一般的な例外をキャッチして処理しています。特定のステータスコード(404 Not Found)の場合には、適切なメッセージを返すようにしています。

データベース操作の非同期エラーハンドリング

データベース操作を非同期で行う場合、接続エラーやクエリの実行エラーなどを適切に処理する必要があります。

using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;

public class DatabaseService
{
    private readonly DbContext _dbContext;
    private readonly ILogger<DatabaseService> _logger;

    public DatabaseService(DbContext dbContext, ILogger<DatabaseService> logger)
    {
        _dbContext = dbContext;
        _logger = logger;
    }

    public async Task<List<Entity>> GetEntitiesAsync()
    {
        try
        {
            _logger.LogInformation("データベースからエンティティを取得します");
            return await _dbContext.Entities.ToListAsync();
        }
        catch (DbUpdateException ex)
        {
            _logger.LogError(ex, "データベースの更新中にエラーが発生しました");
            throw new ApplicationException("データベースの更新に失敗しました", ex);
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "データベース操作中にエラーが発生しました");
            throw;
        }
    }
}

この例では、Entity Framework Coreを使用してデータベースからエンティティを非同期に取得し、DbUpdateExceptionなどの例外をキャッチして適切に処理しています。

非同期ファイル操作のエラーハンドリング

ファイル操作を非同期に行う際も、エラー処理が重要です。以下は、ファイルの読み書きを非同期で行う例です。

using System.IO;
using System.Text;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;

public class FileService
{
    private readonly ILogger<FileService> _logger;

    public FileService(ILogger<FileService> logger)
    {
        _logger = logger;
    }

    public async Task<string> ReadFileAsync(string filePath)
    {
        try
        {
            _logger.LogInformation("ファイルを読み込んでいます: {FilePath}", filePath);
            var content = await File.ReadAllTextAsync(filePath, Encoding.UTF8);
            _logger.LogInformation("ファイルの読み込みに成功しました: {FilePath}", filePath);
            return content;
        }
        catch (FileNotFoundException ex)
        {
            _logger.LogWarning("ファイルが見つかりません: {FilePath}", filePath);
            return "ファイルが見つかりません";
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "ファイル操作中にエラーが発生しました: {FilePath}", filePath);
            throw;
        }
    }
}

この例では、File.ReadAllTextAsyncを使用してファイルを非同期に読み込み、FileNotFoundExceptionやその他の例外をキャッチして適切に処理しています。

次のセクションでは、本記事のまとめを行います。

まとめ

非同期プログラミングにおけるエラーハンドリングは、C#のアプリケーションで信頼性とパフォーマンスを確保するために非常に重要です。本記事では、非同期プログラミングの基本から始め、具体的なエラーハンドリング技術、例外フィルター、再試行パターン、ロギングとモニタリング、そして実際の応用例について詳しく説明しました。これらの技術を適用することで、非同期メソッドの信頼性を高め、エラー発生時のトラブルシューティングを容易にすることができます。非同期エラーハンドリングの技術を駆使して、堅牢でパフォーマンスの高いアプリケーションを構築してください。

コメント

コメントする

目次
  1. 非同期プログラミングの基礎
    1. 基本的な非同期メソッドの例
    2. 非同期メソッドの利点
  2. 非同期メソッドのエラーハンドリング
    1. try-catchブロックを使用したエラーハンドリング
    2. 非同期メソッドの例外伝播
    3. 例外の再スロー
  3. タスクベースの非同期パターン(TAP)のエラーハンドリング
    1. タスクのエラー検出
    2. Task.ContinueWithを用いたエラーハンドリング
    3. Task.WhenAllとTask.WhenAnyのエラーハンドリング
  4. 継続タスク(ContinueWith)を用いたエラーハンドリング
    1. ContinueWithの基本的な使い方
    2. 継続タスクの条件付き実行
    3. 複数の継続タスクの連鎖
  5. 非同期ストリームのエラーハンドリング
    1. 非同期ストリームの基本
    2. 非同期ストリームのエラーハンドリング
    3. 非同期ストリームの部分エラー処理
  6. 例外フィルターと再試行パターン
    1. 例外フィルターの使用
    2. 再試行パターンの実装
    3. 例外フィルターと再試行パターンの組み合わせ
  7. ログとモニタリングの実装
    1. ロギングの基本
    2. 非同期メソッドのロギング
    3. モニタリングの導入
  8. テストとデバッグのベストプラクティス
    1. ユニットテストの作成
    2. 非同期コードのデバッグ
    3. テストカバレッジの確保
  9. 応用例と実践
    1. ウェブAPIの非同期呼び出しとエラーハンドリング
    2. データベース操作の非同期エラーハンドリング
    3. 非同期ファイル操作のエラーハンドリング
  10. まとめ