MeWrite Docs

TaskCanceledException

非同期タスクがキャンセルまたはタイムアウトした場合に発生するC#の例外

概要

System.Threading.Tasks.TaskCanceledExceptionは、非同期タスクがキャンセルされた場合に発生します。HttpClientのタイムアウトやCancellationTokenによるキャンセルが主な原因です。

エラーメッセージ

System.Threading.Tasks.TaskCanceledException: A task was canceled.
   at System.Net.Http.HttpClient.HandleFailure(Exception e, Boolean telemetryStarted, HttpResponseMessage response, CancellationTokenSource cts, CancellationToken cancellationToken, CancellationTokenSource pendingRequestsCts)
System.Threading.Tasks.TaskCanceledException: The request was canceled due to the configured HttpClient.Timeout of 100 seconds elapsing.

原因

  1. HttpClientのタイムアウト: デフォルト100秒のタイムアウトを超過
  2. CancellationTokenによるキャンセル: 明示的なキャンセル要求
  3. サーバーの応答遅延: APIやデータベースの応答が遅い
  4. HttpClientの不正なライフサイクル: usingブロック内でリクエスト完了前にDispose

解決策

1. HttpClientのタイムアウトを調整する

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// NG: デフォルトの100秒タイムアウト
var client = new HttpClient();

// OK: タイムアウトを明示的に設定
var client = new HttpClient
{
    Timeout = TimeSpan.FromSeconds(30) // 短めに設定
};

// 特定のリクエストだけタイムアウトを変えたい場合
var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5));
try
{
    var response = await client.GetAsync("https://api.example.com/data", cts.Token);
}
catch (TaskCanceledException) when (!cts.IsCancellationRequested)
{
    Console.WriteLine("HttpClientのタイムアウト");
}
catch (TaskCanceledException)
{
    Console.WriteLine("明示的なキャンセル");
}

2. リトライロジックを実装する(Polly使用)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// NuGet: Microsoft.Extensions.Http.Polly
using Polly;
using Polly.Extensions.Http;

// Program.cs(.NET 6以降)
builder.Services.AddHttpClient("MyApi", client =>
{
    client.BaseAddress = new Uri("https://api.example.com");
    client.Timeout = TimeSpan.FromSeconds(30);
})
.AddPolicyHandler(GetRetryPolicy());

static IAsyncPolicy<HttpResponseMessage> GetRetryPolicy()
{
    return HttpPolicyExtensions
        .HandleTransientHttpError()
        .Or<TaskCanceledException>()
        .WaitAndRetryAsync(3, retryAttempt =>
            TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)), // 指数バックオフ
            onRetry: (outcome, timespan, retryCount, context) =>
            {
                Console.WriteLine($"リトライ {retryCount}回目: {timespan.TotalSeconds}秒後");
            });
}

3. CancellationTokenを正しく扱う

 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
public class DataService
{
    private readonly HttpClient _client;

    public async Task<string> FetchDataAsync(CancellationToken cancellationToken = default)
    {
        try
        {
            var response = await _client.GetAsync(
                "https://api.example.com/data",
                cancellationToken);

            response.EnsureSuccessStatusCode();
            return await response.Content.ReadAsStringAsync(cancellationToken);
        }
        catch (TaskCanceledException) when (cancellationToken.IsCancellationRequested)
        {
            // ユーザーによる明示的なキャンセル
            Console.WriteLine("リクエストがキャンセルされました");
            throw; // 呼び出し元に伝播
        }
        catch (TaskCanceledException)
        {
            // タイムアウト
            Console.WriteLine("リクエストがタイムアウトしました");
            throw new TimeoutException("APIの応答がタイムアウトしました");
        }
    }
}

4. IHttpClientFactoryを使用する

HttpClientのライフサイクルを適切に管理する。

 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
// Program.cs
builder.Services.AddHttpClient("MyApi", client =>
{
    client.BaseAddress = new Uri("https://api.example.com");
    client.Timeout = TimeSpan.FromSeconds(30);
    client.DefaultRequestHeaders.Add("Accept", "application/json");
});

// サービスクラス
public class ApiService
{
    private readonly IHttpClientFactory _factory;

    public ApiService(IHttpClientFactory factory)
    {
        _factory = factory;
    }

    public async Task<string> GetDataAsync(CancellationToken ct)
    {
        using var client = _factory.CreateClient("MyApi");
        var response = await client.GetAsync("/data", ct);
        return await response.Content.ReadAsStringAsync(ct);
    }
}

5. タイムアウトとキャンセルを区別する

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public static async Task<T?> SafeRequestAsync<T>(
    Func<CancellationToken, Task<T>> request,
    TimeSpan timeout,
    CancellationToken externalToken = default)
{
    using var cts = CancellationTokenSource.CreateLinkedTokenSource(externalToken);
    cts.CancelAfter(timeout);

    try
    {
        return await request(cts.Token);
    }
    catch (OperationCanceledException) when (externalToken.IsCancellationRequested)
    {
        Console.WriteLine("外部からキャンセルされました");
        return default;
    }
    catch (OperationCanceledException)
    {
        Console.WriteLine($"タイムアウト ({timeout.TotalSeconds}秒)");
        return default;
    }
}

関連エラー

C# の他のエラー

最終更新: 2026-02-03