C#非同期プログラミングの実践的ガイド

非同期メソッドの基礎

C#のasyncとawaitキーワードについての基本的な理解:

  • asyncはランタイムに対して、この関数が効率向上のために非同期実行可能であることを示します
  • awaitはランタイムに対して、実際に時間がかかる操作がこのキーワードの後にあることを伝えます
  • 待機中に現在のスレッドをスレッドプールに返却し、非同期処理完了後に新しいスレッドで後続コードを実行します
public static async Task ProcessDataAsync()
{
    var data = await LoadDataAsync();
    Console.WriteLine(data);
}

public static async Task<string> LoadDataAsync()
{
    await Task.Delay(1000);
    return "処理完了";
}

非同期メソッドの特徴

  • async修飾子で宣言されたメソッド
  • 戻り値は通常TaskまたはTask<T>
  • メソッド名はAsyncで終わる慣例
  • 戻り値がない場合でもTaskを返すことを推奨
  • 呼び出し時にはawaitキーワードを使用
  • 非同期メソッドは伝播性を持つ

非同期デリゲートの実装

public void ExecuteAsyncOperation()
{
    Task.Run(async () =>
    {
        for (int i = 0; i < 10; i++)
        {
            await SaveToFileAsync($@"C:\temp\file{i}.txt", $"データ{i}");
        }
    });
}

非同期処理の内部動作

非同期メソッドはコンパイラによってクラスに変換され、awaitの位置で分割されます。各状態はMoveNextメソッドを通じて管理され、実際の待機処理はブロッキングを行いません。

効率的な非同期パターン

async/awaitの最適化

単純な転送のみを行うメソッドでは、async/awaitを省略可能:

// async/awaitを使用する場合
public async Task<string> ReadWithAsync()
{
    return await File.ReadAllTextAsync(@"data.txt");
}

// 最適化されたバージョン
public Task<string> ReadWithoutAsync()
{
    return File.ReadAllTextAsync(@"data.txt");
}

値のラッピング

public Task<int> CalculateAsync()
{
    return Task.Run(() => 
    {
        int result = ComplexCalculation();
        return Task.FromResult(result);
    });
}

非同期処理の一時停止

非同期メソッド内での一時停止にはThread.Sleep()ではなくTask.Delay()を使用:

public async Task ProcessWithDelay()
{
    await ProcessStep1();
    await Task.Delay(2000); // 2秒待機
    await ProcessStep2();
}

キャンセルトークンの活用

長時間実行される非同期操作のキャンセル処理:

public class AsyncOperations
{
    public static async Task DownloadMultipleTimesAsync(
        string url, 
        int count, 
        CancellationToken cancellationToken)
    {
        using var client = new HttpClient();
        
        for (int i = 0; i < count; i++)
        {
            cancellationToken.ThrowIfCancellationRequested();
            
            var response = await client.GetStringAsync(url);
            Console.WriteLine($"ダウンロード {i + 1}: {response.Length} bytes");
        }
    }
}

// 使用方法
public static async Task Main()
{
    var cts = new CancellationTokenSource();
    cts.CancelAfter(TimeSpan.FromSeconds(30));
    
    try
    {
        await AsyncOperations.DownloadMultipleTimesAsync(
            "https://example.com", 
            5, 
            cts.Token);
    }
    catch (OperationCanceledException)
    {
        Console.WriteLine("操作がキャンセルされました");
    }
}

ASP.NET Coreでのキャンセルトークン

public class DataController : Controller
{
    public async Task<IActionResult> GetData(CancellationToken cancellationToken)
    {
        var result = await DataService.FetchDataAsync(cancellationToken);
        return Ok(result);
    }
}

Taskクラスの主要メソッド

メソッド説明
Task<Task> WhenAny(IEnumerable<Task> tasks)いずれかのタスクが完了すると完了
Task<TResult[]> WhenAll<TResult>(params Task<TResult>[] tasks)全てのタスクが完了すると完了
FromResult(T result)値をTaskでラップ

WhenAllの実用例

public async Task<int> CountAllWordsAsync(string directoryPath)
{
    var textFiles = Directory.GetFiles(directoryPath, "*.txt");
    var wordCountTasks = textFiles.Select(CountWordsInFileAsync);
    
    var wordCounts = await Task.WhenAll(wordCountTasks);
    return wordCounts.Sum();
}

private async Task<int> CountWordsInFileAsync(string filePath)
{
    var content = await File.ReadAllTextAsync(filePath);
    return content.Split(new[] { ' ', '\n', '\r' }, 
                        StringSplitOptions.RemoveEmptyEntries).Length;
}

インターフェースと非同期メソッド

インターフェースや抽象メソッドではasync修飾子を使用できません。asyncはコンパイラへのヒントであり、メソッドシグネチャの一部ではないためです。

public interface IDataProvider
{
    Task<string> GetDataAsync(); // async修飾子なし
}

タグ: C# 非同期プログラミング async/await Task CancellationToken

5月13日 04:41 投稿