Bagaimana saya bisa tahu ketika HttpClient telah kehabisan waktu?

142

Sejauh yang saya tahu, tidak ada cara untuk mengetahui bahwa ini adalah timeout khusus yang telah terjadi. Apakah saya tidak mencari di tempat yang tepat, atau saya kehilangan sesuatu yang lebih besar?

string baseAddress = "http://localhost:8080/";
var client = new HttpClient() 
{ 
    BaseAddress = new Uri(baseAddress), 
    Timeout = TimeSpan.FromMilliseconds(1) 
};
try
{
    var s = client.GetAsync("").Result;
}
catch(Exception e)
{
    Console.WriteLine(e.Message);
    Console.WriteLine(e.InnerException.Message);
}

Ini mengembalikan:

Satu atau lebih kesalahan terjadi.

Tugas dibatalkan.

Benjol
sumber
3
Kita dapat mengatasi masalah pada GitHub: HttpClient melempar TaskCanceledException pada batas waktu #
20296
Suara besar untuk pertanyaan itu. Juga ... ada ide bagaimana melakukan ini di UWP? Windows.Web.HTTP.HTTPClient-nya tidak memiliki anggota timeout. Juga metode GetAsync tidak menerima token pembatalan ...
Do-do-new
1
6 tahun kemudian, dan tampaknya masih tidak mungkin untuk mengetahui apakah klien kehabisan waktu.
Steve Smith

Jawaban:

61

Anda harus menunggu GetAsyncmetode ini. Kemudian akan membuang TaskCanceledExceptionjika sudah habis. Selain itu, GetStringAsyncdan secara GetStreamAsyncinternal menangani batas waktu, sehingga mereka tidak akan pernah menyerah.

string baseAddress = "http://localhost:8080/";
var client = new HttpClient() 
{ 
    BaseAddress = new Uri(baseAddress), 
    Timeout = TimeSpan.FromMilliseconds(1) 
};
try
{
    var s = await client.GetAsync();
}
catch(Exception e)
{
    Console.WriteLine(e.Message);
    Console.WriteLine(e.InnerException.Message);
}
murkaeus
sumber
2
Saya menguji ini, dan GetStreamAsyncmelemparkan TaskCanceledExceptionuntuk saya.
Sam
38
Bagaimana saya bisa tahu apakah TaskCanceledExceptiondisebabkan oleh waktu tunggu HTTP dan tidak, katakan pembatalan langsung atau alasan lain?
UserControl
8
@UserControl periksa TaskCanceledException.CancellationToken.IsCancellationRequested. Jika salah, Anda dapat yakin itu adalah batas waktu.
Todd Menier
3
Ternyata, Anda tidak bisa mengandalkan IsCancellationRequested set token pengecualian pada pembatalan langsung seperti yang saya pikirkan sebelumnya: stackoverflow.com/q/29319086/62600
Todd Menier
2
@testing Mereka tidak berperilaku berbeda. Hanya saja Anda memiliki satu token yang akan mewakili permintaan pembatalan pengguna dan internal (Anda tidak dapat mengakses dan Anda tidak perlu) yang mewakili batas waktu klien. Ini adalah kasus penggunaan yang berbeda
Sir Rufo
59

Saya mereproduksi masalah yang sama dan itu sangat menjengkelkan. Saya menemukan ini berguna:

HttpClient - berurusan dengan pengecualian agregat

Bug di HttpClient.GetAsync harus membuang WebException, bukan TaskCanceledException

Beberapa kode jika tautannya tidak menuju ke mana-mana:

var c = new HttpClient();
c.Timeout = TimeSpan.FromMilliseconds(10);
var cts = new CancellationTokenSource();
try
{
    var x = await c.GetAsync("http://linqpad.net", cts.Token);  
}
catch(WebException ex)
{
    // handle web exception
}
catch(TaskCanceledException ex)
{
    if(ex.CancellationToken == cts.Token)
    {
        // a real cancellation, triggered by the caller
    }
    else
    {
        // a web request timeout (possibly other things!?)
    }
}
vezenkov
sumber
Dalam pengalaman saya, WebException tidak dapat ditangkap dalam keadaan apa pun. Apakah orang lain mengalami sesuatu yang berbeda?
naksir
1
@ Crush WebException bisa ditangkap. Mungkin ini akan membantu.
DavidRR
Ini tidak berfungsi untuk saya jika saya tidak menggunakan cts. Saya hanya menggunakan Task <T> task = SomeTask () coba {T result = task.Result} catch (TaskCanceledException) {} catch (Exception e) {} Hanya pengecualian umum yang ditangkap, bukan TaskCanceledException. Apa yang salah dalam versi kode saya?
Naomi
1
Saya membuat laporan bug baru karena yang asli muncul di posting forum yang diarsipkan: connect.microsoft.com/VisualStudio/feedback/details/3141135
StriplingWarrior
1
Jika token dilewatkan dari luar periksa itu bukan default(CancellationToken)sebelum membandingkan dengan ex.CancellationToken.
SerG
25

Saya menemukan bahwa cara terbaik untuk menentukan apakah panggilan layanan telah kehabisan waktu adalah dengan menggunakan token pembatalan dan bukan properti batas waktu HttpClient:

var cts = new CancellationTokenSource();
cts.CancelAfter(timeout);

Dan kemudian menangani PembatalanException selama panggilan layanan ...

catch(TaskCanceledException)
{
    if(!cts.Token.IsCancellationRequested)
    {
        // Timed Out
    }
    else
    {
        // Cancelled for some other reason
    }
}

Tentu saja jika batas waktu terjadi pada sisi layanan, itu harus dapat ditangani oleh WebException.

Mendongkrak
sumber
1
Hmm, saya kira operator negasi (yang ditambahkan dalam edit) harus dihapus agar sampel ini masuk akal? Jika cts.Token.IsCancellationRequestedini trueharus berarti bahwa batas waktu telah terjadi?
Lasse Christiansen
9

Dari http://msdn.microsoft.com/en-us/library/system.net.http.httpclient.timeout.aspx

Kueri Sistem Nama Domain (DNS) mungkin membutuhkan waktu hingga 15 detik untuk kembali atau waktu habis. Jika permintaan Anda berisi nama host yang memerlukan resolusi dan Anda menetapkan Timeout ke nilai kurang dari 15 detik, mungkin diperlukan 15 detik atau lebih sebelum WebException dilemparkan untuk menunjukkan batas waktu pada permintaan Anda .

Anda kemudian mendapatkan akses ke Statusproperti, lihat WebExceptionStatus

pengguna247702
sumber
3
Hm, saya mendapatkan kembali AggregateExceptiondengan TaskCancelledExceptiondalam. Saya pasti melakukan sesuatu yang salah ...
Benjol
Apakah Anda menggunakan catch(WebException e)?
user247702
Tidak, dan jika saya mencoba, AggregateExceptionitu tidak tertangani. Jika Anda membuat proyek konsol VS, tambahkan referensi System.Net.Httpdan masukkan kode main, Anda dapat melihatnya sendiri (jika Anda mau).
Benjol
5
Jika periode tunggu melebihi periode waktu tugas, Anda akan mendapatkan TaskCanceledException. Ini tampaknya dilemparkan oleh penanganan batas waktu internal TPL, pada tingkat yang lebih tinggi dari HttpWebClient. Tampaknya tidak ada cara yang baik untuk membedakan antara pembatalan batas waktu dan pembatalan pengguna. Hasil dari ini adalah bahwa Anda mungkin tidak mendapatkan WebExceptiondalam Anda AggregateException.
JT.
1
Seperti yang dikatakan orang lain, Anda harus mengasumsikan TaskCanceledException adalah batas waktu. Saya menggunakan coba {// Kode di sini} catch (pengecualian AggregateException) {if (exception.InnerExceptions.OfType <TaskCanceledException> () .Any ()) {// Tangani batas waktu di sini}}
Vdex
8

Pada dasarnya, Anda perlu menangkap OperationCanceledExceptiondan memeriksa status token pembatalan yang diteruskan ke SendAsync(atau GetAsync, atau HttpClientmetode apa pun yang Anda gunakan):

  • jika dibatalkan (IsCancellationRequested benar), itu berarti permintaan itu benar-benar dibatalkan
  • jika tidak, itu berarti permintaannya habis

Tentu saja, ini sangat tidak nyaman ... akan lebih baik untuk menerima TimeoutExceptionjika ada batas waktu. Saya mengusulkan solusi di sini berdasarkan penangan pesan HTTP khusus: Penanganan batas waktu yang lebih baik dengan HttpClient

Thomas Levesque
sumber
ah! itu kamu! Saya menulis komentar di posting blog Anda sebelumnya hari ini. Tetapi untuk jawaban ini, saya pikir pendapat Anda tentang IsCancellationRequested tidak benar, karena sepertinya itu selalu benar untuk saya, ketika saya tidak membatalkannya sendiri
knocte
@knocte itu aneh ... Tapi kalau begitu, solusi dari posting blog saya tidak akan membantu Anda, karena itu juga bergantung pada ini
Thomas Levesque
1
dalam masalah github tentang ini, banyak yang mengklaim apa yang saya katakan: bahwa IsCancellationRequested benar ketika ada batas waktu; jadi saya tergoda untuk
membatalkan
@knocte, saya tidak tahu harus bilang apa ... Saya sudah menggunakan ini sejak lama dan selalu berhasil untuk saya. Apakah Anda mengatur HttpClient.Timeoutke tak terhingga?
Thomas Levesque
tidak, saya tidak melakukannya karena saya tidak bisa mengendalikan HttpClient sendiri, ini adalah perpustakaan pihak ketiga yang saya gunakan, yang menggunakannya
knocte
-1
_httpClient = new HttpClient(handler) {Timeout = TimeSpan.FromSeconds(5)};

adalah apa yang biasanya saya lakukan, sepertinya berhasil cukup baik untuk saya, terutama baik saat menggunakan proxy.

Pengembangan Syv
sumber
1
Ini adalah bagaimana Anda mengatur batas waktu httpclient. Ini tidak menjawab pertanyaan, yang merupakan bagaimana Anda memberi tahu kapan httpclient telah habis.
Ethan Fischer