.NET HttpClient zaman aşımları: TaskCanceledException ve TimeoutRejectedException

API çok yavaş olduğunda, .NET uygulamanız tetiklenen zaman aşımına bağlı olarak 2 özel durumdan 1'ini alır. HttpClient.Timeout bir TaskCanceledException fırlatır. Microsoft.Extensions.Http.Resilience standart dayanıklılık işleyicisi Polly'den bir TimeoutRejectedException fırlatır. Bunlar farklı yerlerden gelir, farklı varsayılan değerlere sahiptir ve ayrı catch bloklara ihtiyaç duyar.

Hangi zaman aşımı tetiklendi?

Timeout Varsayılan Kodunuzun aldıkları Onu ayarladığınız yer
HttpClient.Timeout 100 saniye TaskCanceledException, InnerException olarak bir TimeoutException ile (.NET 5 ve üzeri) HttpClient.Timeout
Varsayılan işleyici deneme zaman aşımı Deneme başına 10 saniye İlk başta hiçbir şey olmaz: işleyici girişimi yeniden dener AddStandardResilienceHandler(options => ...)
Varsayılan işleyici toplam zaman aşımı Tüm yeniden denemeler dahil 30 saniye Polly.Timeout.TimeoutRejectedException AddStandardResilienceHandler(options => ...)

HttpClient.Timeout

HttpClient örneğinin gönderdiği her istek için HttpClient.Timeout geçerlidir. Bir istekte farklı bir zaman aşımı kullanmak için, kendi zaman aşımına sahip bir CancellationTokenSource'den bir CancellationToken geçirin. İkisinden kısa olan geçerlidir. Kapatmak için Timeout.InfiniteTimeSpan değerini ayarlayın.

.NET 5 ve sonraki sürümlerde, zaman aşımı TimeoutException içeren bir TaskCanceledException fırlatır. .NET Core'un önceki sürümlerinde inner exception yoktur. .NET Framework'te bunun yerine bir HttpRequestException alırsınız. Ayrıntılar için bkz. HttpClient.Timeout ve HttpClient sınıfıyla HTTP istekleri yapma.

Bir TaskCanceledException ayrıca isteğin biri tarafından iptal edildiği anlamına gelir; örneğin, sayfayı kapatan bir kullanıcı. Zaman aşımını iptalden ayırt etmek için ex.InnerException is TimeoutException öğesini denetleyin veya kendi belirtecinizin iptal edilip edilmediğini denetleyin.

Standart dayanıklılık işleyici

AddStandardResilienceHandler() bir hız sınırlayıcıyı, toplam zaman aşımını, yeniden denemeyi, devre kesiciyi ve deneme zaman aşımını zincirler. Bir deneme 10 saniyeden uzun sürdüğünde, deneme zaman aşımı bunu iptal eder ve yeniden deneme stratejisi yeniden dener: 2 saniyeden başlayarak üstel geri çekilme ve jitter ile en fazla 3 yeniden deneme. Yeniden denemeler de dahil olmak üzere isteğin tamamı 30 saniyeden uzun sürdüğünde toplam zaman aşımı isteği iptal eder ve kodunuz bir TimeoutRejectedException alır.

TimeoutRejectedException'den Exception türetilir. Bu, ne bir TimeoutException ne de bir HttpRequestException olduğundan, bir catch (HttpRequestException) bloğu onu yakalayamaz. Varsayılanların tam listesi için bkz. Standart dayanıklılık işleyicisi varsayılanları.

Örneğin, standart işleyicinin varsayılanlarını kullanan bir .NET 10 uygulaması yanıt başına 11 ila 15 saniye süren bir API çağırdığında, uygulama 30 saniye sonra bir TimeoutRejectedException alır ve catch (HttpRequestException) bloğu çalışmaz.

HttpClient zaman aşımlarını işleme

  1. API'yi çağırdığınız yerde her iki istisnayı da yakalayın. Standart işleyiciyi kullanıyorsanız, TimeoutRejectedException yakalayın. HttpClient.Timeout’i TaskCanceledException için yakala.
  2. İptal ile zaman aşımını ayırt edin. Bir TaskCanceledException öğesini yalnızca iç özel durumu bir TimeoutException olduğunda zaman aşımı olarak değerlendirin. Çağıran iptal ettiğinde sessizce durun.
  3. API'ye uygun zaman aşımlarını seçin. API genellikle 10 saniyeden uzun sürüyorsa, AddStandardResilienceHandler(options => ...) içinde deneme ve toplam zaman aşımı ayarlarını değiştirin.
  4. POST veya PATCH ifadesini, API bunu güvenli hale getirmediği sürece yeniden denemeyin. Standart işleyici, varsayılan olarak POST dahil tüm yöntemleri yeniden dener. PATCH çağrısı yaparak PUT, DELETE, CONNECT, POST ve options.Retry.DisableFor(HttpMethod.Post, HttpMethod.Patch) öğelerini hariç tutabilir veya idempotent PUT ve DELETE öğelerini yeniden denemeyi sürdürmek için options.Retry.DisableForUnsafeHttpMethods() çağrısı yapabilirsiniz.
  5. Kullanıcıya ne olduğunu anlatın. Genel bir hata yerine "hizmet yavaş, yeniden deneyin" ifadesini gösterin.
using Polly.Timeout;

public async Task<string?> GetForecastAsync(HttpClient client, CancellationToken cancellationToken)
{
    try
    {
        return await client.GetStringAsync("https://api.contoso.com/forecast", cancellationToken);
    }
    catch (TimeoutRejectedException)
    {
        // Standard resilience handler: total timeout expired after all retries
        return null;
    }
    catch (TaskCanceledException ex) when (ex.InnerException is TimeoutException)
    {
        // HttpClient.Timeout expired
        return null;
    }
    catch (HttpRequestException)
    {
        // Network error, or an error status code after all retries
        return null;
    }
}

Uygulamanızın zaman aşımlarını işlediğini test etme

Geliştirme sırasında nadiren zaman aşımı görürsünüz, bu nedenle bloklarınız catch nadiren çalışır. Test yönteminiz, hataları kullanıcılarınızdan önce bulup bulamayacağınızı belirler.

Approach Bulduklarınız Kaçırdıklarınız
Üretimi bekleyin Gerçek zaman aşımı Bir kullanıcı ona tıklayana kadar her şey
Testlerinizde API'yi mock'layın veya kodlama aracınızın mock'u yazmasına izin verin catch blokunuzun çalışıp çalışmadığı, mock nesnesinin doğru istisnayı fırlatıp fırlatmadığı Gerçek HttpClient kurulumunuz, dayanıklılık işleyicisinin yeniden denemeleri ve gerçekten oluşturduğu özel durum. Uygulamanızın mock'a erişmek için yalnızca test amaçlı bir anahtara da ihtiyacı vardır.
Gerçek API'yi çağırın ve yavaş olmasını umun Fiili davranış API'yi talep üzerine yavaşlatamazsınız
Uygulamanızın gerçek trafiğini yakalayın ve yanıtları geciktirin Gerçek HttpClient, esneklik işleyiciniz ve özel durumlarınız Uygulamanızda hiçbir şey değişmez, bu nedenle kodunuzu yalıtılmış olarak test etmez. Bunun için birim testlerinizi kullanın.

Uygulamanızda deneyin

Dev Proxy, uygulamanızın API'ye yönelik isteklerini yakalar ve LatencyPlugin ile yanıtları geciktirir. .NET sistem ara sunucusunu kullandığından kodunuzu değiştirmeniz gerekmez. Bu örnek, her yanıtı 11 ila 15 saniye geciktirir; bu süre, standart işleyicinin 10 saniyelik deneme zaman aşımından daha uzundur. https://api.contoso.com değerini uygulamanızın çağırdığı API'nin URL'si ile değiştirin.

Dosya: devproxyrc.json

{
  "$schema": "https://raw.githubusercontent.com/dotnet/dev-proxy/main/schemas/v3.3.1/rc.schema.json",
  "plugins": [
    {
      "name": "LatencyPlugin",
      "enabled": true,
      "pluginPath": "~appFolder/plugins/DevProxy.Plugins.dll",
      "configSection": "slowApi"
    }
  ],
  "urlsToWatch": [
    "https://api.contoso.com/*"
  ],
  "slowApi": {
    "$schema": "https://raw.githubusercontent.com/dotnet/dev-proxy/main/schemas/v3.3.1/latencyplugin.schema.json",
    "minMs": 11000,
    "maxMs": 15000
  }
}

Geliştirme Proxy'sini devproxy --config-file devproxyrc.json ile başlatın ve uygulamanızı çalıştırın. Her deneme zaman aşımına uğrar, işleyici yeniden dener ve 30 saniye sonra kodunuz TimeoutRejectedException alır. Bunun yerine HttpClient.Timeout test etmek için, minMs değerini yapılandırdığınız zaman aşımından daha yüksek ayarlayın. Kurulum ayrıntıları için bkz. .NET uygulamalarla Dev Proxy kullanma. Dev Proxy'yi yüklemek için bkz. Dev Proxy'yi ayarlama.

Sonraki Adımlar

Ayrıca bkz.