Runtime: HttpClient melempar TaskCanceledException pada waktu habis

Dibuat pada 25 Mei 2017  ·  119Komentar  ·  Sumber: dotnet/runtime

HttpClient melempar TaskCanceledException pada waktu habis dalam beberapa keadaan. Ini terjadi pada kami ketika server berada di bawah beban berat. Kami dapat mengatasinya dengan meningkatkan batas waktu default. Utas forum MSDN berikut menangkap inti dari masalah yang dialami: https://social.msdn.microsoft.com/Forums/en-US/d8d87789-0ac9-4294-84a0-91c9fa27e353/bug-in-httpclientgetasync-should-throw -webexception-not-taskcanceledexception?forum=netfxnetcom&prof=wajib

Terima kasih

area-System.Net.Http enhancement

Komentar yang paling membantu

@karelz menambahkan catatan sebagai pelanggan lain yang terpengaruh oleh pengecualian yang menyesatkan ini :)

Semua 119 komentar

@awithy versi .NET Core apa yang Anda gunakan?

1.1

Apakah desain yang tepat atau tidak, dengan desain OperationCanceledExceptions dilemparkan untuk batas waktu (dan TaskCanceledException adalah OperationCanceledException).
cc: @davidsh

Tim kami menganggap ini tidak intuitif, tetapi tampaknya berfungsi seperti yang dirancang. Sayangnya, ketika kami menemukan ini, kami membuang sedikit waktu untuk mencari sumber pembatalan. Harus menangani skenario ini secara berbeda dari pembatalan tugas cukup buruk (kami membuat penangan khusus untuk mengelola ini). Ini juga tampaknya merupakan penggunaan pembatalan yang berlebihan sebagai sebuah konsep.

Terima kasih sekali lagi atas respon cepatnya.

Sepertinya menurut desain + hanya ada 2 keluhan pelanggan dalam 6+ bulan terakhir.
Penutupan.

@karelz menambahkan catatan sebagai pelanggan lain yang terpengaruh oleh pengecualian yang menyesatkan ini :)

Terima kasih atas infonya, silakan upvote juga posting teratas (masalah dapat diurutkan berdasarkan itu), terima kasih!

Apakah desain yang tepat atau tidak, dengan desain OperationCanceledExceptions dilemparkan untuk batas waktu

Ini adalah desain IMO yang buruk. Tidak ada cara untuk mengetahui apakah permintaan tersebut benar-benar dibatalkan (yaitu, token pembatalan yang diteruskan ke SendAsync telah dibatalkan) atau apakah batas waktu telah berlalu. Dalam kasus terakhir, masuk akal untuk mencoba lagi, sedangkan dalam kasus sebelumnya tidak. Ada TimeoutException yang sempurna untuk kasus ini, mengapa tidak menggunakannya?

Ya, ini juga membuat tim kami gagal. Ada seluruh utas Stack Overflow yang didedikasikan untuk mencoba menghadapinya: https://stackoverflow.com/questions/10547895/how-can-i-tell-when-httpclient-has-timed-out/32230327#32230327

Keren terima kasih.

Dibuka kembali karena minat sekarang lebih besar - utas StackOverflow sekarang memiliki 69 suara positif.

cc @dotnet/ncl

Ada berita ? Masih terjadi di dotnet core 2.0

Itu ada dalam daftar masalah teratas kami untuk pasca-2.1. Itu tidak akan diperbaiki di 2.0/2.1.

Kami memiliki beberapa opsi apa yang harus dilakukan ketika batas waktu terjadi:

  1. Lempar TimeoutException alih-alih TaskCanceledException .

    • Pro: Mudah untuk membedakan batas waktu dari tindakan pembatalan eksplisit saat runtime

    • Con: Perubahan pemutusan teknis - jenis pengecualian baru dilemparkan.

  2. Lempar TaskCanceledException dengan pengecualian dalam sebagai TimeoutException

    • Pro: Kemungkinan untuk membedakan batas waktu dari tindakan pembatalan eksplisit saat runtime. Jenis pengecualian yang kompatibel.

    • Pertanyaan terbuka: Apakah boleh membuang tumpukan TaskCanceledException asli dan membuang yang baru, sambil mempertahankan InnerException (sebagai TimeoutException.InnerException )? Atau haruskah kita mempertahankan dan membungkus yang asli TaskCanceledException ?



      • Entah: TaskCanceledException baru -> TimeoutException baru -> TaskCanceledException asli (dengan InnerException asli yang mungkin nol)


      • Atau: TaskCanceledException baru -> TimeoutException baru -> TaskCanceledException.InnerException asli (mungkin nol)



  3. Lempar TaskCanceledException dengan pesan yang menyebutkan batas waktu sebagai alasannya

    • Pro: Kemungkinan untuk membedakan batas waktu dari tindakan pembatalan eksplisit dari pesan / log

    • Con: Tidak dapat dibedakan saat runtime, ini "hanya debug".

    • Pertanyaan terbuka: Sama seperti pada [2] - haruskah kita membungkus atau mengganti TaskCanceledException asli (dan menumpuk)

Saya condong ke opsi [2], dengan membuang tumpukan asli TaskCanceledException asli.
@stephentoub @davidsh ada pemikiran?

BTW: Perubahannya harus cukup mudah di HttpClient.SendAsync tempat kami mengatur batas waktu:
https://github.com/dotnet/corefx/blob/30fb7887514865b98748ede3013641734d9bf5c/src/System.Net.Http/src/System/Net/Http/HttpClient.cs#L433 -L461

Pengecualian tingkat atas harus selalu berupa HttpRequestException. Itu adalah model API standar untuk HttpClient.

Di dalamnya, sebagai pengecualian dalam, "batas waktu" mungkin harus berupa TimeoutException. Faktanya, jika kita menggabungkan ini dengan masalah lain (tentang memodifikasi HttpRequestion untuk memasukkan properti enum baru yang mirip dengan WebExceptionStatus), maka pengembang hanya perlu memeriksa enum itu dan tidak perlu memeriksa pengecualian dalam sama sekali.

Ini pada dasarnya adalah opsi 1) tetapi terus mempertahankan model API aplikasi-kompat saat ini bahwa pengecualian tingkat atas dari API HttpClient selalu HttpRequestException.

Catatan: bahwa setiap "batas waktu" dari langsung membaca aliran respons HTTP dari Stream API seperti:

```c#
var klien = baru HttpClient();
Stream responseStream = menunggu client.GetStreamAsync(url);

int byteRead = menunggu responseStream.ReadAsync(buffer, 0, buffer.Length);
```

harus terus membuang System.IO.IOException dll sebagai pengecualian tingkat atas (kami sebenarnya memiliki beberapa bug di SocketsHttpHandler tentang ini).

@davidsh apakah Anda menyarankan untuk membuang HttpRequestException bahkan jika ada pembatalan pengguna secara eksplisit? Mereka melempar TaskCanceledException hari ini dan tampaknya baik-baik saja. Tidak yakin apakah itu sesuatu yang ingin kita ubah...
Saya boleh saja mengekspos batas waktu sisi klien sebagai HttpRequestException alih-alih TaskCanceledException .

@davidsh apakah Anda menyarankan untuk membuang HttpRequestException bahkan dalam kasus ketika ada pembatalan pengguna secara eksplisit? Mereka melempar TaskCanceledException hari ini dan sepertinya baik-baik saja. Tidak yakin apakah itu sesuatu yang ingin kami ubah…

Saya perlu menguji perilaku yang berbeda dari tumpukan termasuk .NET Framework untuk memverifikasi perilaku saat ini yang tepat sebelum saya dapat menjawab pertanyaan ini.

Juga, dalam beberapa kasus, beberapa tumpukan kami melempar HttpRequestException dengan pengecualian dalam OperationCanceledException. TaskCanceledException adalah jenis turunan dari OperationCanceledException.

ketika ada pembatalan pengguna secara eksplisit

Kita juga perlu definisi yang jelas tentang apa itu "eksplisit". Misalnya, apakah pengaturan properti HttpClient.Timeout "eksplisit"? Saya pikir kita mengatakan tidak. Bagaimana dengan memanggil .Cancel() pada objek CancellationTokenSource (yang memengaruhi objek yang diteruskan di CancellationToken)? Apakah itu "eksplisit"? Mungkin ada contoh lain yang bisa kita pikirkan.

.NET Framework juga menampilkan TaskCanceledException saat Anda menyetel HttpClient.Timeout .

Jika Anda tidak dapat membedakan antara pembatalan dari klien dan pembatalan dari implementasi - cukup periksa keduanya. Sebelum menggunakan token klien, buat token turunan:
```c#
var clientToken = ....
var innerTokenSource = CancellationTokenSource.CreateLinkedTokenSource(clientToken);

If at some point you catch **OperationCancelledException**:
```c#
try
{
     //invocation with innerToken
}
catch(OperationCancelledException oce)
{
      if(clientToken.IsCancellationRequested)
          throw; //as is, it is client initiated and in higher priority
      if(innerToken.IsCancellationRequested)
           //wrap it in HttpException, TimeoutException, whatever, wrap it in another linked token until you process all cases.
}

Pada dasarnya Anda memfilter semua lapisan yang mungkin dari token klien ke yang terdalam, hingga Anda menemukan token yang menyebabkan pembatalan.

Adakah pembaruan tentang ini yang membuat 2.2 atau 3.0? @karelz @davidsh , cc @NickCraver . Saya agak berharap itu hanya akan melakukan opsi 1) melempar TimeoutException, atau sebagai alternatif HttpTimeoutException baru yang berasal dari HttpRequestException. Terima kasih!

Adakah pembaruan tentang ini yang membuat 2.2 atau 3.0?

Terlambat untuk 2.2, itu dirilis minggu lalu;)

Terlambat untuk 2.2, itu dirilis minggu lalu;)

Argh, itulah yang terjadi ketika saya istirahat;)

Meskipun sedikit konyol, solusi paling sederhana yang saya temukan adalah membuat penangan tangkapan untuk OperationCanceledException dan kemudian mengevaluasi nilai IsCancellationRequested untuk menentukan apakah token pembatalan adalah hasil dari timeout atau sesi aborsi yang sebenarnya. Jelas logika yang melakukan panggilan API kemungkinan akan berada dalam logika bisnis atau lapisan layanan tetapi saya telah mengkonsolidasikan contoh untuk kesederhanaan:

````csharp
[HttpDapatkan]
Tugas asinkron publikIndeks(PembatalanTokenPembatalanToken)
{
mencoba
{
// Ini biasanya dilakukan dalam logika bisnis atau lapisan layanan daripada di pengontrol itu sendiri, tetapi saya menggambarkan intinya di sini demi kesederhanaan
mencoba
{
//Menggunakan HttpClientFactory untuk instance HttpClient
var httpClient = _httpClientFactory.CreateClient();

        //Set an absurd timeout to illustrate the point
        httpClient.Timeout = new TimeSpan(0, 0, 0, 0, 1);

        //Perform call that requires special timeout logic                
        var httpResponse = await httpClient.GetAsync("https://someurl.com/api/long/running");

        //... (if GetAsync doesn't fail, handle the response as desired)
    }
    catch (OperationCanceledException innerOperationCanceled)
    {
        //If a canceled token exception occurs due to a timeout, "IsCancellationRequested" should be false
        if (cancellationToken.IsCancellationRequested)
        {
            //Bubble exception to global handler
            throw innerOperationCanceled;
        }
        else
        {
            //... (perform timeout logic here)
        }                    
    }                

}
catch (OperationCanceledException operationCanceledEx)
{
    _logger.LogWarning(operationCanceledEx, "Request was aborted by the end user.");
    return new StatusCodeResult(499);
}
catch (Exception ex)
{
    _logger.LogError(ex, "An unexepcted error occured.");
    return new StatusCodeResult(500);
}

}
````

Saya telah melihat artikel lain tentang bagaimana menangani skenario ini dengan lebih elegan tetapi semuanya membutuhkan penggalian ke dalam pipa, dll. Saya menyadari bahwa pendekatan ini tidak sempurna dengan harapan saya adalah akan ada dukungan yang lebih baik untuk pengecualian batas waktu aktual dengan masa depan melepaskan.

Bekerja dengan klien http benar-benar buruk. HttpClient perlu dihapus dan dimulai dari awal.

Meskipun sedikit konyol, solusi paling sederhana yang saya temukan adalah membuat penangan tangkapan untuk OperationCanceledException dan kemudian mengevaluasi nilai IsCancellationRequested untuk menentukan apakah token pembatalan adalah hasil dari timeout atau sesi aborsi yang sebenarnya. Jelas logika yang melakukan panggilan API kemungkinan akan berada dalam logika bisnis atau lapisan layanan tetapi saya telah mengkonsolidasikan contoh untuk kesederhanaan:

[HttpGet]
public async Task<IActionResult> Index(CancellationToken cancellationToken)
{
  try
  {
      //This would normally be done in a business logic or service layer rather than in the controller itself but I'm illustrating the point here for simplicity sake
      try
      {
          //Using HttpClientFactory for HttpClient instances      
          var httpClient = _httpClientFactory.CreateClient();

          //Set an absurd timeout to illustrate the point
          httpClient.Timeout = new TimeSpan(0, 0, 0, 0, 1);

          //Perform call that requires special timeout logic                
          var httpResponse = await httpClient.GetAsync("https://someurl.com/api/long/running");

          //... (if GetAsync doesn't fail, handle the response as desired)
      }
      catch (OperationCanceledException innerOperationCanceled)
      {
          //If a canceled token exception occurs due to a timeout, "IsCancellationRequested" should be false
          if (cancellationToken.IsCancellationRequested)
          {
              //Bubble exception to global handler
              throw innerOperationCanceled;
          }
          else
          {
              //... (perform timeout logic here)
          }                    
      }                

  }
  catch (OperationCanceledException operationCanceledEx)
  {
      _logger.LogWarning(operationCanceledEx, "Request was aborted by the end user.");
      return new StatusCodeResult(499);
  }
  catch (Exception ex)
  {
      _logger.LogError(ex, "An unexepcted error occured.");
      return new StatusCodeResult(500);
  }
}

Saya telah melihat artikel lain tentang bagaimana menangani skenario ini dengan lebih elegan tetapi semuanya membutuhkan penggalian ke dalam pipa, dll. Saya menyadari bahwa pendekatan ini tidak sempurna dengan harapan saya adalah akan ada dukungan yang lebih baik untuk pengecualian batas waktu aktual dengan masa depan melepaskan.

Benar-benar gila kita perlu menulis kode yang mengerikan ini. HttpClient adalah abstraksi paling bocor yang pernah dibuat oleh Microsoft

Saya setuju bahwa tampaknya agak picik untuk tidak memasukkan informasi pengecualian yang lebih spesifik seputar waktu habis. Sepertinya hal yang cukup mendasar bagi orang untuk ingin menangani batas waktu aktual secara berbeda dari pengecualian token yang dibatalkan.

Bekerja dengan klien http benar-benar buruk. HttpClient perlu dihapus dan dimulai dari awal.\

Itu bukan umpan balik yang sangat membantu atau membangun @dotnetchris . Pelanggan dan karyawan MSFT semuanya ada di sini untuk mencoba dan membuat segalanya lebih baik, dan itulah sebabnya orang-orang seperti @karelz masih di sini mendengarkan umpan balik pelanggan dan mencoba untuk meningkatkan. Tidak ada keharusan bagi mereka untuk melakukan pembangunan di tempat terbuka, dan jangan membakar niat baik mereka dengan hal-hal negatif atau mereka dapat memutuskan untuk mengambil bola mereka dan pulang, dan itu akan lebih buruk bagi masyarakat. Terima kasih! :)

Mengapa tidak membuat HttpClient2 (nama sementara) yang memiliki perilaku yang diharapkan dan menginformasikan, dalam dokumentasi, bahwa "HttpClient sudah usang, silakan gunakan HttpClient2" dan jelaskan perbedaan di antara keduanya.

Dengan cara ini tidak ada perubahan yang melanggar yang dimasukkan ke dalam .NetFramework.

HttpClient tidak boleh didasarkan pada melempar pengecualian. Bukan hal yang luar biasa bahwa host internet tidak tersedia. Ini sebenarnya diharapkan, mengingat permutasi alamat yang tak terbatas, kasus luar biasa adalah memberikannya sesuatu yang berfungsi.

Saya bahkan membuat blog tentang seberapa buruk penggunaan HttpClient lebih dari 3 tahun yang lalu. https://dotnetchris.wordpress.com/2015/12/11/working-with-httpclient-is-absurd/

Ini adalah desain yang sangat buruk.

Apakah ada orang di microsoft yang pernah menggunakan HttpClient? Sepertinya tidak.

Kemudian di luar permukaan api yang mengerikan ini, ada segala macam abstraksi yang bocor mengenai konstruksi dan threadingnya. Saya tidak percaya itu mungkin untuk diselamatkan dan hanya merek baru yang merupakan solusi yang layak. HttpClient2, HttpClientSlim baik-baik saja atau apa pun.

Akses HTTP tidak boleh membuang pengecualian. Itu harus selalu mengembalikan hasil, hasilnya harus dapat diperiksa untuk penyelesaian, batas waktu, respons http. Satu-satunya alasan itu harus diizinkan untuk dilempar adalah jika Anda memanggil EnsureSuccess() maka tidak apa-apa untuk membuang pengecualian apa pun di dunia. Tetapi harus ada penanganan aliran kontrol nol untuk perilaku internet yang sepenuhnya normal: host up, host tidak up, host timeout. Tak satu pun dari mereka yang luar biasa.

HttpClient tidak boleh didasarkan pada melempar pengecualian. Bukan hal yang luar biasa bahwa host internet tidak tersedia. Ini sebenarnya diharapkan, mengingat permutasi alamat yang tak terbatas, kasus luar biasa adalah memberikannya sesuatu yang berfungsi.

@dotnetchris karena sepertinya Anda sangat menyukai topik ini, mengapa tidak meluangkan waktu untuk menulis API lengkap yang Anda usulkan untuk HttpClient2 dan mengirimkannya ke tim untuk diskusi sebagai masalah baru?

Saya tidak repot-repot menyelam ke dalam mur dan baut apa yang membuat HttpClient baik atau buruk dari tingkat teknis yang lebih dalam jadi saya tidak punya apa-apa untuk ditambahkan ke komentar @dotnetchris. Saya benar-benar berpikir tidak pantas untuk mengkritik tim pengembang MS tetapi saya juga mengerti betapa frustasinya menghabiskan berjam-jam meneliti solusi untuk situasi yang tampaknya sangat mudah ditangani. Ini adalah frustrasi karena berjuang untuk memahami mengapa keputusan/perubahan tertentu dibuat, berharap seseorang di MS akan mengatasi masalah tersebut sambil tetap berusaha menyelesaikan dan menyelesaikan pekerjaan Anda sementara itu.

Dalam kasus saya, saya memiliki layanan eksternal yang saya panggil selama proses OAuth untuk menetapkan peran pengguna dan jika panggilan itu habis dalam rentang waktu tertentu (yang sayangnya terjadi lebih dari yang saya inginkan), saya perlu kembali ke metode sekunder .

Sungguh, ada segudang alasan lain yang dapat saya lihat mengapa seseorang ingin dapat membedakan antara batas waktu dan pembatalan. Mengapa dukungan yang lebih baik untuk skenario umum seperti ini tampaknya tidak ada adalah apa yang saya perjuangkan untuk dipahami dan saya benar-benar berharap itu adalah sesuatu yang tim MS akan pikirkan.

@karelz apa yang diperlukan untuk mendapatkan ini sesuai jadwal untuk 3.0, proposal API dari seseorang?

Ini bukan tentang proposal API. Ini tentang menemukan implementasi yang tepat. Itu ada di backlog 3.0 kami dan cukup tinggi dalam daftar (di belakang HTTP/2 dan skenario perusahaan). Ini adalah kesempatan yang sangat baik untuk ditangani di 3.0 oleh tim kami.
Tentu saja jika seseorang di komunitas termotivasi, kami akan mengambil kontribusi juga.

@karelz bagaimana komunitas dapat membantu dalam kasus ini? Alasan saya menyebutkan jika proposal API diperlukan adalah karena saya tidak yakin dari pesan Anda yang dimulai "Kami memiliki beberapa opsi apa yang harus dilakukan ketika waktu habis:" jika tim telah memutuskan alternatif pilihan mereka dari yang Anda daftarkan. Jika Anda telah memutuskan arah maka kita bisa pergi dari sana. Terima kasih!

Secara teknis itu bukan pertanyaan proposal API. Keputusan tidak akan memerlukan persetujuan API. Namun, saya setuju bahwa kita harus selaras tentang bagaimana mengekspos perbedaan - kemungkinan akan ada beberapa diskusi.
Saya tidak berharap bahwa opsinya terlalu berbeda satu sama lain dalam implementasi -- jika komunitas ingin membantu, mengimplementasikan salah satu opsi akan membawa kita 98% ke sana (dengan asumsi pengecualian dilemparkan dari satu tempat dan dapat mudah diubah nanti untuk beradaptasi dengan opsi [1]-[3] yang tercantum di atas).

Hai! Kami juga mengalami masalah ini baru-baru ini. Hanya ingin tahu apakah itu masih ada di radar Anda untuk 3.0?

Ya, itu masih ada di daftar 3.0 kami - saya masih akan memberikannya 90% kemungkinan itu akan ditangani di 3.0.

Aku berlari melintasi ini hari ini. Saya menetapkan batas waktu pada HttpClient dan itu memberi saya ini

{System.Threading.Tasks.TaskCanceledException: A task was canceled.}
CancellationRequested = true

Saya setuju dengan orang lain ini membingungkan karena tidak menyebutkan batas waktu sebagai masalahnya.

Umm, oke aku sedikit bingung. Semua orang di utas ini tampaknya mengatakan bahwa batas waktu HTTP melempar TaskCanceledException tetapi bukan itu yang saya dapatkan ... Saya mendapatkan OperationCanceledException di .NET Core 2.1...

Jika Anda ingin melempar OperationCanceledException untuk batas waktu maka baiklah saya dapat mengatasinya, tetapi saya hanya menghabiskan berjam-jam mencoba melacak apa yang sedang terjadi karena tidak didokumentasikan dengan benar:

https://docs.microsoft.com/en-us/dotnet/api/system.net.http.httpclient.sendasync

Ini dengan jelas menyatakan bahwa HttpRequestException dilemparkan untuk batas waktu ...

TaskCanceledException berasal dari OperationCanceledException.

@stephentoub Ya, tapi saya tidak mendapatkan TaskCanceledException , saya mendapatkan OperationCanceledException .

Seperti dalam, blok catch yang menangkap TaskCanceledException tidak menangkap pengecualian, ini secara khusus membutuhkan blok catch untuk OperationCanceledException .

Saya sekarang secara khusus menguji batas waktu HTTP dengan memfilter jenis pengecualian, dan cara saya mengetahui batas waktu adalah jika BUKAN TaskCanceledException :

```c#
mencoba {
menggunakan (respon var = menunggu s_httpClient.SendAsync(request, _updateCancellationSource.Token).ConfigureAwait(false))
return (int)response.StatusCode < 400;
}
catch (OperationCanceledException ex) ketika (ex.GetType() != typeof(TaskCanceledException)) {
// Batas waktu HTTP
kembali salah;
}
tangkap (HttpRequestException) {
kembali salah;
}

or alternatively this works as well, which might be better:

```c#
            try {
                using (var response = await s_httpClient.SendAsync(request, _updateCancellationSource.Token).ConfigureAwait(false))
                    return (int)response.StatusCode < 400;
            }
            catch (OperationCanceledException ex) when (ex.GetType() == typeof(OperationCanceledException)) {
                // HTTP timeout
                return false;
            }
            catch (HttpRequestException) {
                return false;
            }

Umm, oke aku sedikit bingung. Semua orang di utas ini tampaknya mengatakan bahwa batas waktu HTTP melempar TaskCanceledException tetapi bukan itu yang saya dapatkan ... Saya mendapatkan OperationCanceledException di .NET Core 2.1...

Jika Anda ingin melempar OperationCanceledException untuk batas waktu maka baiklah saya dapat mengatasinya, tetapi saya hanya menghabiskan berjam-jam mencoba melacak apa yang sedang terjadi karena tidak didokumentasikan dengan benar:

https://docs.microsoft.com/en-us/dotnet/api/system.net.http.httpclient.sendasync

Ini dengan jelas menyatakan bahwa HttpRequestException dilemparkan untuk batas waktu ...

Apakah Anda membaca seluruh utas karena sebenarnya ada beberapa respons di mana OperationCanceledExecption dirujuk, termasuk contoh kode yang saya ajukan yang menunjukkan cara (tidak sebersih yang saya inginkan tetapi ini cara) untuk menangani batas waktu secara eksplisit.

@Hobray Ya saya punya - apa yang membuat Anda berpikir saya tidak? Itu masih tidak menjelaskan berapa banyak orang di utas ini yang seharusnya mendapatkan TaskCanceledException pada batas waktu tetapi saya tidak.

Saya melihat contoh kode Anda juga, tetapi sayangnya ia memiliki kondisi balapan di dalamnya yang menurut saya tidak Anda pertimbangkan. Kode diposting yang saya gunakan menghindari masalah ini dan tidak memerlukan akses ke token pembatalan, yang bisa menjadi penting jika Anda mencoba untuk mengejar batas waktu lebih jauh ke hulu.

@mikernet Mungkin kata-katanya membuatku bingung. Anda benar bahwa saya tidak mempertimbangkan kemungkinan kondisi balapan. Saya menyadari bahwa itu bukan solusi yang luar biasa tetapi jika Anda ingin menjelaskan apa yang saya abaikan, saya ingin mendengarnya.

Satu hal yang saya perhatikan adalah bahwa pengecualian yang Anda dapatkan tampaknya bervariasi tergantung pada apakah batas waktu atau pembatalan terjadi dalam pemanggilan Controller vs middleware. Di dalam doa middleware tampaknya hanya TaskCanceledException . Sayangnya saya tidak punya waktu untuk menggali pustaka kode .NET Core untuk memahami alasannya, tetapi itu adalah sesuatu yang saya perhatikan.

@Hobray Haha ya tentu saja - maaf, saya tidak bermaksud samar tentang kondisi balapan, saya hanya menggunakan ponsel saat sedang dalam perjalanan mengetik pesan terakhir yang membuatnya sulit untuk dijelaskan.

Kondisi balapan dalam solusi Anda terjadi jika pembatalan tugas dimulai antara batas waktu HttpClient dan titik di mana Anda memeriksa status pembatalan token pembatalan. Dalam situasi saya, saya memerlukan pengecualian pembatalan tugas untuk terus menggelegak sehingga kode upstream dapat mendeteksi pembatalan pengguna sehingga saya harus dapat menangkap pengecualian hanya jika itu benar-benar waktu habis - itu sebabnya saya menggunakan when untuk menyaring hasil tangkapan. Menggunakan solusi Anda untuk memeriksa token dapat mengakibatkan OperationCanceledException yang tidak tertangani melewati filter dan menggelegak dan membuat crash program, karena seseorang yang membatalkan operasi hulu akan mengharapkan TaskCanceledException dilemparkan, bukan sebuah OperationCanceledException .

Jika kode yang memanggil metode HttpClient seharusnya menangani batas waktu dan pembatalan tugas, maka perbedaan antara batas waktu dan pembatalan pengguna saat kondisi balapan terjadi mungkin tidak penting bagi Anda, tergantung pada situasinya... tetapi jika Anda sedang menulis kode tingkat perpustakaan dan perlu membiarkan gelembung pembatalan tugas ditangani pada tingkat yang lebih tinggi maka itu pasti membuat perbedaan.

Jika apa yang Anda katakan itu benar dan batas waktu memberikan jenis pengecualian yang berbeda tergantung pada konteks panggilannya, maka itu pasti tampak seperti masalah bagi saya yang perlu ditangani. Tidak ada cara yang merupakan atau seharusnya menjadi perilaku yang dimaksudkan.

Menarik pada kondisi balapan yang memungkinkan. Saya tidak mempertimbangkan itu. Untungnya, untuk kasus penggunaan saya, itu tidak akan menjadi masalah besar.

Sejauh variasi pengecualian, saya perlu mencari waktu untuk mengecilkan middleware yang telah saya kerjakan untuk melihat apakah itu konsisten dengan apa yang saya lihat beberapa kali saat debugging. Panggilan pramuat omnibox Chrome yang bodoh telah terjadi di mana saya melihatnya di middleware saya ketika waktunya tepat. Saya pikir saya dapat membuat contoh sederhana untuk membuktikannya secara pasti dengan satu atau lain cara.

Saya masih tidak jelas seperti apa solusi yang benar.

Solusi saya adalah yang paling benar dalam situasi di mana batas waktu http membuang OperationCanceledException. Saya tidak dapat mereproduksi situasi di mana TaskCanceledException dilemparkan dan tidak ada yang menyediakan cara untuk mereproduksi perilaku itu, jadi saya katakan uji lingkungan Anda dan jika cocok dengan milik saya maka itu solusi yang tepat.

Jika dalam beberapa konteks eksekusi, TaskCanceledException dilemparkan maka solusi saya gagal sebagai solusi tujuan umum untuk konteks tersebut dan solusi lain yang diusulkan adalah solusi "terbaik" tetapi berisi kondisi balapan yang mungkin atau mungkin tidak penting untuk situasi Anda, itu untuk Anda untuk mengevaluasi.

Saya ingin mendengar lebih banyak detail dari seseorang yang mengklaim mereka mendapatkan TaskCanceledExceptions karena itu jelas akan menjadi bug besar. Saya agak skeptis bahwa ini benar-benar terjadi.

@mikernet apakah TaskCanceledException atau OperationCanceledException dilemparkan sebagian besar tidak relevan. Itu adalah detail implementasi yang tidak boleh Anda andalkan. Yang dapat Anda lakukan adalah menangkap OperationException (yang juga akan menangkap TaskCanceledException ), dan memeriksa apakah token pembatalan yang Anda berikan dibatalkan, seperti yang dijelaskan dalam komentar Hobray (https://github.com/ dotnet/corefx/issues/20296#issuecomment-449510983).

@thomaslevesque Saya sudah membahas mengapa pendekatan itu menjadi masalah. Tanpa membahas itu, komentar Anda tidak terlalu berguna - itu hanya mengulangi pernyataan sebelum saya bergabung dengan konversi tanpa mengatasi masalah yang saya jelaskan.

Adapun "itu detail implementasi" - ya, tapi sayangnya saya harus mengandalkannya karena jika tidak, tidak ada cara yang baik untuk menentukan apakah tugas itu benar-benar dibatalkan atau habis. 100% harus ada cara untuk menentukan dari pengecualian mana dari dua skenario yang terjadi untuk menghindari kondisi balapan yang dijelaskan. OperationCanceledException hanyalah pilihan yang buruk karena ini...sudah ada TimeoutException dalam kerangka kerja yang membuat kandidat yang lebih baik.

Pengecualian untuk tugas asinkron tidak boleh bergantung pada pemeriksaan data di objek lain yang juga dapat dimodifikasi secara asinkron untuk menentukan penyebab pengecualian jika kasus penggunaan umum adalah membuat keputusan percabangan berdasarkan penyebab pengecualian. Itu mengemis untuk masalah kondisi balapan.

Juga, dalam hal jenis pengecualian menjadi detail implementasi ... Saya tidak benar-benar berpikir itu benar untuk pengecualian, setidaknya tidak dalam hal praktik dokumentasi .NET yang mapan. Sebutkan satu contoh lain di .NET Framework di mana dokumentasi mengatakan "melempar pengecualian X ketika Y terjadi" dan kerangka kerja sebenarnya melempar subkelas yang dapat diakses publik dari pengecualian itu, apalagi subkelas yang dapat diakses publik yang digunakan untuk kasus pengecualian yang sama sekali berbeda pada metode yang sama ! Itu sepertinya bentuk yang buruk dan saya belum pernah mengalami ini sebelumnya dalam 15 tahun saya pemrograman .NET.

Terlepas dari itu, TaskCanceledException harus dicadangkan untuk pembatalan tugas yang dimulai pengguna, bukan untuk batas waktu - seperti yang saya katakan, kami sudah memiliki pengecualian untuk itu. Perpustakaan harus menangkap setiap pembatalan tugas internal dan melemparkan pengecualian yang lebih tepat. Penangan pengecualian untuk pembatalan tugas yang dimulai dengan token pembatalan tugas seharusnya tidak bertanggung jawab untuk menangani masalah lain yang tidak terkait yang mungkin terjadi selama operasi asinkron.

@mikernet maaf, saya melewatkan bagian diskusi itu. Ya, ada kondisi balapan yang tidak saya pikirkan. Tapi itu mungkin bukan masalah besar seperti yang terlihat... Kondisi balapan terjadi jika token pembatalan ditandai setelah batas waktu terjadi tetapi sebelum Anda memeriksa status token, bukan? Dalam hal ini, tidak masalah jika batas waktu terjadi jika operasi dibatalkan. Jadi Anda akan membiarkan OperationCanceledException menggelembung, membiarkan penelepon percaya bahwa operasi itu berhasil dibatalkan, yang bohong karena sebenarnya terjadi timeout, tetapi tidak masalah bagi penelepon, karena mereka ingin batalkan operasinya.

100% harus ada cara untuk menentukan dari pengecualian mana dari dua skenario yang terjadi untuk menghindari kondisi balapan yang dijelaskan. OperationCanceledException hanyalah pilihan yang buruk karena ini...sudah ada TimeoutException dalam kerangka kerja yang membuat kandidat yang lebih baik.

Saya sepenuhnya setuju dengan itu, perilaku saat ini jelas salah. Menurut saya opsi terbaik untuk memperbaikinya adalah opsi 1 dari komentar @karelz . Ya, ini adalah perubahan yang melanggar, tapi saya curiga sebagian besar basis kode tidak menangani skenario ini dengan benar, jadi itu tidak akan memperburuk keadaan.

@thomaslevesque Ya, tapi saya agak mengatasinya. Ini mungkin atau mungkin tidak penting untuk beberapa aplikasi. Di tambang itu meskipun. Pengguna metode perpustakaan saya tidak mengharapkan OperationCanceledException ketika mereka membatalkan tugas - mereka mengharapkan TaskCanceledException dan mereka harus dapat menangkapnya dan menjadi "aman". Jika saya memeriksa token pembatalan, melihat bahwa itu dibatalkan dan meneruskan pengecualian maka saya baru saja meneruskan pengecualian yang tidak akan ditangkap oleh pawang mereka dan akan membuat program macet. Metode saya tidak seharusnya membuang waktu habis.

Tentu, ada beberapa cara untuk mengatasi ini...Saya bisa saja melempar TaskCanceledException yang baru dibungkus tetapi kemudian saya kehilangan jejak tumpukan tingkat atas, dan waktu tunggu HTTP diperlakukan jauh berbeda dari pembatalan tugas di layanan saya - Batas waktu HTTP pergi ke dalam cache untuk waktu yang lama, sedangkan pembatalan tugas tidak. Saya tidak ingin melewatkan cache timeout yang sebenarnya karena kondisi balapan.

Saya kira saya dapat menangkap OperationCanceledException dan memeriksa token pembatalan - jika telah dibatalkan maka periksa juga jenis pengecualian untuk memastikan itu adalah TaskCanceledException sebelum rethrowing untuk menghindari seluruh program crash. Jika salah satu dari kondisi itu salah maka itu harus menjadi batas waktu. Satu-satunya masalah yang tersisa dalam kasus itu adalah kondisi balapan yang tidak kentara, tetapi ketika Anda memiliki ribuan permintaan HTTP yang terjadi per menit pada layanan yang banyak dimuat dengan batas waktu yang ketat, itu memang terjadi.

Either way itu hacky dan seperti yang Anda tunjukkan harus diselesaikan entah bagaimana.

(Kondisi balapan hanya akan terjadi di lingkungan di mana TaskCanceledException dilemparkan alih-alih OperationCanceledException , di mana pun itu berada, tetapi setidaknya solusinya aman dari memunculkan jenis pengecualian yang tidak terduga dan tidak mengandalkan detail implementasi agar tetap berfungsi dengan baik jika detail implementasi berubah).

Pengecualian batas waktu yang dilontarkan oleh HttpClient dalam hal batas waktu http tidak boleh bertipe System.Net.HttpRequestException (atau turunan?) alih-alih generik System.TimeoutException ? Misalnya, WebClient lama yang bagus melempar WebException dengan properti Status yang dapat disetel ke WebExceptionStatus.Timeout .

Sayangnya, kami tidak akan dapat menyelesaikan ini untuk 3.0. Pindah ke Masa Depan.
Itu masih di atas backlog kami, tetapi secara realistis kami tidak akan dapat melakukannya untuk 3.0 :(.

@karelz oh tidak :( Saya baru saja akan memposting ulang mengatakan bagaimana saya mengalami ini lagi di perusahaan kedua berturut-turut. Berapa lama jendela untuk mengirimkan PR untuk 3.0 jika saya ingin menanganinya sendiri? Cheers

Saya mengalami kesulitan melacak dengan tepat apa yang terjadi dalam banyak komentar di atas dengan orang-orang yang melaporkan perbedaan antara mendapatkan TaskCancelledException dan OperationCancelledException tergantung pada konteksnya, dapatkah siapa pun dari MSFT menjelaskannya?

Sukai komentar dari @mikernet ini :

Seperti dalam, blok tangkap yang menangkap TaskCanceledException tidak menangkap pengecualian, itu secara khusus membutuhkan blok tangkap untuk OperationCanceledException.

cc @davidsh @stephentoub mungkin?

TaskCanceledException berasal dari OperationCanceledException, dan membawa sedikit informasi tambahan. Tangkap saja OperationCanceledException.

@karelz akankah opsi 1 yang Anda posting awalnya, hanya membuatnya membuang pengecualian Timeout, benar-benar tidak enak?

Hal-hal seperti StackExchange.Redis membuang pengecualian yang berasal dari pengecualian Timeout, itu sebabnya akan lebih baik untuk menangkap pengecualian Timeout untuk HttpClient...

Saya mengerti @stephentoub diberikan warisan, hanya komentar di bawah ini dari @mikernet yang mengejutkan saya... Perilaku yang dia gambarkan ini pasti semacam bug?? Tetapi jika benar, akan memengaruhi perbaikan atau solusi…

"blok tangkapan yang menangkap TaskCanceledException tidak menangkap pengecualian, itu secara khusus membutuhkan blok tangkapan untuk OperationCanceledException"

Atau apakah Anda mengatakan bahwa di beberapa tempat ia melempar OperationEx dan di tempat lain TaskEx ? Seperti mungkin orang melihat sesuatu yang berasal dari middleware ASP dan berpikir itu dari HttpClient?

hanya komentar di bawah ini dari @mikernet yang mengejutkan saya... Perilaku yang dia gambarkan ini pasti semacam bug??

Mengapa itu "meniup pikiran Anda"? Jika pengecualian B berasal dari pengecualian A, blok tangkap untuk B tidak akan menangkap pengecualian yang secara konkret A, misalnya jika saya memiliki blok tangkap untuk ArgumentNullException, itu tidak akan menangkap " throw new ArgumentException(...)".

Atau apakah Anda mengatakan bahwa di beberapa tempat ia melempar OperationEx dan di tempat lain TaskEx ?

Benar. Secara umum kode harus mengasumsikan hanya OCE; cara sesuatu ditulis, beberapa kode mungkin berakhir dengan membuang TCE turunan, misalnya tugas yang diselesaikan melalui TaskCompletionSource.TrySetCanceled akan menghasilkan TaskCanceledException.

Terima kasih Stephen, dari suara hal-hal di utas, banyak orang mendapat kesan bahwa TaskCE dilemparkan ke mana-mana, yang akan membuat perilaku yang disebutkan oleh mikernet tidak masuk akal ... Sekarang setelah Anda mengklarifikasi, itu masuk akal pengamatan logis :)

@stephentoub , apakah panduan ini didokumentasikan di mana saja? Ping ke @guardrex

Secara umum kode harus mengasumsikan hanya OCE ...

Saya mengerjakan set dokumen ASP.NET hari ini. Buka masalah untuk kucing dotnet/docs ...

https://github.com/dotnet/docs/issues

Apakah mungkin untuk memulai kembali percakapan ini di mana ada beberapa proposal konkret dengan @karelz dan @davidsh ? Rasanya seperti seseorang yang memiliki kepemilikan model API HttpClient perlu membuat keputusan tentang arah yang akan dituju. Ada beberapa diskusi tentang pengecualian tingkat atas yang selalu menjadi HttpRequestException (selain pembatalan eksplisit??), dicampur dengan beberapa topik jangka panjang tentang refactor yang lebih besar untuk memodifikasi HttpRequestion untuk memasukkan properti enum baru yang mirip dengan WebExceptionStatus...

Rasanya seperti seluruh masalah ini semakin terkait dengan perubahan kode aktual yang diperlukan. Apakah ada orang lain yang memiliki kepentingan/kepemilikan dalam model API HttpClient yang harus diulang untuk membuat beberapa keputusan? Terima kasih banyak semua!

Saya mengalami masalah yang sama di sini -- namun, cancellationToken.IsCancellationRequested mengembalikan true :

    public class TimeoutHandler : DelegatingHandler
    {
        protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
        {
            try
            {
                var response = await base.SendAsync(request, cancellationToken);
                response.EnsureSuccessStatusCode();

                return response;
            }
            catch (OperationCanceledException) when (!cancellationToken.IsCancellationRequested)
            {
                throw new TimeoutException("Timeout expired. The timeout period elapsed prior to completion of the operation or the server is not responding.");
            }
        }

Saya memaksakan batas waktu. Pengecualian yang diangkat adalah TaskCancelledException dengan IOException sebagai pengecualian dalam:

System.Threading.Tasks.TaskCanceledException: The operation was canceled. ---> System.IO.IOException: Unable to read data from the transport connection: A operação de E/S foi anulada devido a uma saída de thread ou a uma requisição de aplicativo. ---> System.Net.Sockets.SocketException: A operação de E/S foi anulada devido a uma saída de thread ou a uma requisição de aplicativo
   --- End of inner exception stack trace ---
   at System.Net.Sockets.Socket.AwaitableSocketAsyncEventArgs.ThrowException(SocketError error)
   at System.Net.Sockets.Socket.AwaitableSocketAsyncEventArgs.GetResult(Int16 token)
   at System.Net.Http.HttpConnection.FillAsync()
   at System.Net.Http.HttpConnection.ReadNextResponseHeaderLineAsync(Boolean foldedHeadersAllowed)
   at System.Threading.Tasks.ValueTask`1.get_Result()
   at System.Net.Http.HttpConnection.SendAsyncCore(HttpRequestMessage request, CancellationToken cancellationToken)
   --- End of inner exception stack trace ---
   at System.Net.Http.HttpConnection.SendAsyncCore(HttpRequestMessage request, CancellationToken cancellationToken)
   at System.Net.Http.HttpConnectionPool.SendWithNtConnectionAuthAsync(HttpConnection connection, HttpRequestMessage request, Boolean doRequestAuth, CancellationToken cancellationToken)
   at System.Net.Http.HttpConnectionPool.SendWithRetryAsync(HttpRequestMessage request, Boolean doRequestAuth, CancellationToken cancellationToken)
   at System.Net.Http.RedirectHandler.SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)

...

(Maaf untuk teks pengecualian yang diterjemahkan -- tampaknya seseorang berpikir itu akan menjadi ide yang baik untuk membawa ini ke .NET Core juga)

Seperti yang Anda lihat, saya berharap untuk memeriksa IsCancellationRequest untuk mengatasi masalah ini seperti yang disarankan orang lain, tetapi bahkan pada waktu habis itu kembali true .

Saat ini saya sedang berpikir untuk menerapkan pengatur waktu untuk memeriksa apakah batas waktu yang dikonfigurasi telah berlalu. Tapi, jelas saya ingin mati ini adalah solusi yang mengerikan.

Hai @FelipeTaveira ,

Pendekatan Anda tidak berhasil, karena pembatalan ditangani di hulu TimeoutHandler Anda, oleh HttpClient itu sendiri. Handler Anda menerima token pembatalan yang dibatalkan oleh HttpClient ketika Timeout terjadi. Jadi memeriksa bahwa token pembatalan tidak dibatalkan hanya berfungsi dalam kode yang memanggil HttpClient , bukan dalam kode yang dipanggil oleh HttpClient .

Cara membuatnya bekerja dengan penangan khusus adalah dengan menonaktifkan HttpClient 's Timeout dengan menyetelnya ke Timeout.InfiniteTimeSpan , dan mengontrol perilaku batas waktu di penangan Anda sendiri, sebagai ditampilkan dalam artikel ini (kode lengkap di sini )

@thomaslevesque Oh, terima kasih! Saya telah membaca kode Anda dan berpikir bagian itu tentang mendukung konfigurasi batas waktu per-permintaan dan tidak memperhatikan bahwa itu juga perlu untuk mendukung hal TimeoutException .

Saya harap ini ditangani di masa depan.

Untuk apa nilainya, saya juga menemukan TaskCanceledException dilemparkan pada batas waktu untuk membingungkan.

Saya dapat membuktikan bahwa TaskCanceledExceptions adalah sumber utama kebingungan ketika menangani masalah produksi di pekerjaan saya sebelumnya. Kami biasanya akan membungkus panggilan ke HttpClient dan membuang TimeoutExceptions jika perlu.

@eiriktsarpalis terima kasih. Kami berencana untuk mengatasinya di 5.0, sehingga Anda dapat mengambilnya dalam jangka waktu itu jika Anda mau, sekarang Anda telah bergabung dengan tim kami .

1 tahun? Di mana kelincahan yang dijanjikan dari .NET Core itu?

Menilai kelincahan seluruh proyek berdasarkan satu masalah agak ... tidak adil.
Ya, ini agak memalukan, kami masih memiliki masalah ini, namun, sulit untuk diperbaiki (dengan dampak yang sesuai) dan hanya ada tujuan bisnis dengan harga tinggi yang harus ditangani terlebih dahulu (dalam jaringan) - lihat perubahan tonggak sejarah sepanjang sejarah dari masalah ini.

BTW: Seperti di banyak proyek OSS lainnya, tidak ada SLA atau ETA atau janji kapan masalah akan diperbaiki. Semua tergantung prioritas, pekerjaan lain, kesulitan dan jumlah tenaga ahli yang mampu menangani masalah tersebut.

Menilai kelincahan seluruh proyek berdasarkan satu masalah agak ... tidak adil.

Yah, itu pertanyaan yang jujur. Sudah dua tahun dan ketika saya melihat bahwa itu akan mengambil yang lain, saya sedikit terkejut. Tidak ada maksud untuk tersinggung . Saya tahu bahwa untuk memperbaiki masalah ini ada/ada beberapa pertanyaan yang harus/harus dijawab terlebih dahulu.

Dalam dunia kompromi... Saya tidak menyukainya, tapi inilah pilihan lain untuk dikunyah:

Kita bisa terus melempar TaskCanceledException , tetapi setel CancellationToken ke nilai penjaga.

c# catch(TaskCanceledException ex) when(ex.CancellationToken == HttpClient.TimeoutToken) { }

Dan satu lagi: lempar HttpTimeoutException baru yang berasal dari TaskCanceledException .

Dan satu lagi: lempar HttpTimeoutException baru yang berasal dari TaskCanceledException .

Saya pikir itu akan mengatasi semua masalah. Itu membuatnya mudah untuk secara khusus menangkap batas waktu, dan itu masih TaskCanceledException , jadi kode yang saat ini menangkap pengecualian itu juga akan menangkap yang baru (tidak ada perubahan yang melanggar).

Dan satu lagi: lempar HttpTimeoutException baru yang berasal dari TaskCanceledException.

Tampaknya agak jelek, tapi mungkin kompromi yang bagus.

Dan satu lagi: lempar HttpTimeoutException baru yang berasal dari TaskCanceledException .

Saya pikir itu akan mengatasi semua masalah. Itu membuatnya mudah untuk secara khusus menangkap batas waktu, dan itu masih TaskCanceledException , jadi kode yang saat ini menangkap pengecualian itu juga akan menangkap yang baru (tidak ada perubahan yang melanggar).

Nah, ada _bisa_ orang di luar sana menulis kode seperti ini:

try
{
  // ...
}
catch (Exception ex) when (ex.GetType() == typeof(TaskCanceledException))
{
}

Jadi ini secara teknis akan menjadi perubahan besar.

Juga saya pikir itu punya sedikit yang terburuk dari kedua dunia. Kami masih tidak akan mendapatkan TimeoutException umum pada saat yang sama memiliki perubahan yang melanggar.

mungkin ada orang di luar sana yang menulis kode seperti ini, jadi ini secara teknis akan menjadi perubahan besar

FWIW, hampir setiap perubahan mungkin melanggar, jadi untuk membuat kemajuan maju pada apa pun, seseorang harus menarik garis di suatu tempat. Di corefx kami tidak menganggap mengembalikan atau melempar tipe yang lebih diturunkan sebagai pelanggaran.
https://github.com/dotnet/corefx/blob/master/Documentation/coding-guidelines/breaking-change-rules.md#exceptions

Dalam dunia kompromi... Saya tidak menyukainya, tapi inilah pilihan lain untuk dikunyah:

Kita bisa terus melempar TaskCanceledException , tetapi setel CancellationToken ke nilai penjaga.

catch(TaskCanceledException ex) when(ex.CancellationToken == HttpClient.TimeoutToken)
{
}

Saya sangat suka yang ini. Apakah ada kerugian lain selain ""jelek""? Apa ex.CancellationToken hari ini ketika batas waktu terjadi?

Apakah ada kerugian lain selain ""jelek""

Akan sangat mudah bagi seseorang untuk berpikir bahwa TimeoutToken dapat diteruskan dan digunakan untuk memberi sinyal pembatalan ketika batas waktu terjadi, karena ini adalah desain yang digunakan di tempat lain untuk hal-hal seperti itu.

Saya sangat suka yang ini. Apakah ada kerugian lain selain ""jelek""? Apa ex.CancellationToken hari ini ketika batas waktu terjadi?

Ini jauh lebih tidak intuitif daripada menangkap jenis pengecualian tertentu, yang dapat disebutkan secara eksplisit dalam dokumentasi.

Saya terbiasa mempertimbangkan OperationCanceledException dan tipe turunan untuk menandakan eksekusi yang terputus, bukan kesalahan aplikasi. Pengecualian yang dilemparkan HttpClient pada waktu habis biasanya merupakan kesalahan - ada panggilan ke API HTTP, dan itu tidak tersedia. Perilaku ini dapat menyebabkan komplikasi saat memproses pengecualian pada tingkat yang lebih tinggi, misalnya, di middleware pipa ASP.NET Core, karena kode ini harus mengetahui bahwa ada dua rasa OperationCanceledException , kesalahan dan bukan kesalahan, atau memaksa Anda untuk membungkus setiap panggilan HttpClient dalam blok lempar-tangkap.

Dan satu lagi: lempar HttpTimeoutException baru yang berasal dari TaskCanceledException.

Tampaknya agak jelek, tapi mungkin kompromi yang bagus.

Apakah ini benar-benar jelek, mengingat kendala desain yang terlibat dan pertimbangan kompatibilitas mundur? Saya tidak tahu bahwa siapa pun di utas ini telah mengusulkan solusi yang jelas lebih unggul yang tidak memiliki kekurangan (apakah penggunaan atau kemurnian desain dll)

Kami ingin melihat ini lebih dalam dari sekedar mengubah tipe pengecualian. Ada juga masalah lama "di mana batas waktu kita?" -- misalnya, ada kemungkinan permintaan untuk timeout tanpa pernah terjadi -- yang perlu kita lihat, dan sebuah solusi harus kompatibel dengan arah yang kita tuju ke sana.

Kami ingin melihat ini lebih dalam dari sekedar mengubah tipe pengecualian. Ada juga masalah lama "di mana batas waktu kita?" -- misalnya, ada kemungkinan permintaan untuk timeout tanpa pernah terjadi -- yang perlu kita lihat, dan sebuah solusi harus kompatibel dengan arah yang kita tuju ke sana.

Apakah ini benar-benar sesuatu yang perlu Anda ketahui? Maksud saya, apakah Anda akan menangani kesalahan secara berbeda tergantung di mana batas waktunya? Bagi saya, mengetahui bahwa batas waktu terjadi biasanya sudah cukup.
Saya tidak keberatan jika Anda ingin berbuat lebih banyak, saya hanya tidak ingin perubahan ini ketinggalan kereta .NET 5 karena persyaratan tambahan :wink:

Saya pikir masalah yang diangkat @scalablecory patut diperhatikan, tetapi saya setuju bahwa itu tidak boleh menghalangi ini untuk diperbaiki lebih cepat. Anda dapat memutuskan mekanisme untuk memungkinkan alasan batas waktu dievaluasi tetapi tolong jangan tunda memperbaiki masalah pengecualian untuk itu...Anda selalu dapat menambahkan properti Reason ke pengecualian nanti.

Saya setuju dengan @thomaslevesque; Saya belum pernah mendengar itu sebagai masalah besar sebelumnya. Dari mana itu berasal?

Apakah itu kasus debugging yang cukup khusus di mana tidak apa-apa untuk menggali alasan untuk memerlukan sedikit tindakan eksplisit, sedangkan untuk pengembang umum mereka hanya perlu tahu/peduli menangani satu hal.

Kami ingin melihat ini lebih dalam dari sekedar mengubah tipe pengecualian. Ada juga masalah lama "di mana batas waktu kita?" -- misalnya, ada kemungkinan permintaan untuk timeout tanpa pernah terjadi -- yang perlu kita lihat, dan sebuah solusi harus kompatibel dengan arah yang kita tuju ke sana.

Saya kira implikasinya di sini adalah bahwa itu harus menjadi tanggung jawab setiap HttpMessageHandler untuk membuang jenis pengecualian batas waktu yang tepat daripada HttpClient memperbaiki semuanya.

Saya kira implikasinya di sini adalah bahwa itu harus menjadi tanggung jawab setiap HttpMessageHandler untuk membuang jenis pengecualian batas waktu yang tepat daripada HttpClient memperbaiki semuanya.

Saya tidak melihat bagaimana itu mungkin dengan abstraksi yang ditentukan hari ini. HttpMessageHandler tidak memiliki pengetahuan tentang Batas Waktu HttpClient; sejauh menyangkut HttpMessageHandler, itu hanya menyerahkan token pembatalan, yang dapat meminta pembatalan karena sejumlah alasan, termasuk token pembatalan penelepon yang ditandai, CancelPendingRequests HttpClient dipanggil, batas waktu paksa HttpClient berakhir, dll.

Apakah ini benar-benar sesuatu yang perlu Anda ketahui? Maksud saya, apakah Anda akan menangani kesalahan secara berbeda tergantung di mana batas waktunya? Bagi saya, mengetahui bahwa batas waktu terjadi biasanya sudah cukup.

Ini berguna untuk mengetahui apakah server mungkin telah memproses permintaan Anda. Ini juga bagus untuk diagnostik -- server jarak jauh tidak kehabisan waktu tetapi waktu tunggu pelaporan klien adalah pengalaman yang membuat frustrasi.

@davidsh mungkin memiliki lebih banyak wawasan.

Saya setuju dengan @thomaslevesque; Saya belum pernah mendengar itu sebagai masalah besar sebelumnya. Dari mana itu berasal?

Itu adalah poin yang diangkat selama pertemuan NCL baru-baru ini. Mungkin kita memutuskan ini tidak cukup penting untuk mengekspos atau membiarkan hal-hal menunda, tapi ada baiknya diskusi cepat terlebih dahulu.

@dotnet/ncl @stephentoub Mari kita taruh yang ini di tempat tidur. Kita tahu bahwa apa pun yang kita lakukan akan secara halus melanggar. Ini sepertinya pilihan yang paling tidak buruk. Saya mengusulkan untuk maju dengan:

lempar HttpTimeoutException baru yang berasal dari TaskCanceledException .

Keberatan?

melempar HttpTimeoutException baru yang berasal dari TaskCanceledException.

Bagaimana kita melakukan ini dalam praktik? Ini mungkin berarti kita harus menggosok kode kita dan menemukan semua tempat yang memanggil CancellationToken.ThrowIfCancellationRequested dan sebagai gantinya mengonversinya menjadi:

c# if (token.IsCancellationRequested) throw new HttpTimeoutException(EXTRASTUFF, token);

serta tempat eksplisit yang mungkin sebenarnya kita sebut OperationCancelledException juga.

Apakah ada opsi yang tidak perlu membahas semua kode terkait itu?

Apakah ada opsi yang tidak perlu membahas semua kode terkait itu?

Mungkin tidak, tetapi mungkin ada tempat yang tidak kami kendalikan (belum begitu yakin) sehingga kami harus berpotensi mengambil TaskCancelledException/OperationCancelledException acak dan melemparkan kembali HttpTimeoutException baru.

Ini mungkin berarti kita harus menggosok kode kita dan menemukan semua tempat yang memanggil CancellationToken.ThrowIfCancellationRequested dan sebagai gantinya mengonversinya menjadi:

Apakah ada opsi yang tidak perlu membahas semua kode terkait itu?

Tidak tepat.

Sejauh menyangkut penangan, yang mereka terima hanyalah CancellationToken: mereka tidak tahu dan tidak peduli dari mana token itu berasal, siapa yang memproduksinya, atau mengapa itu mungkin diminta pembatalan. Yang mereka tahu hanyalah bahwa pembatalan pada suatu saat mungkin diminta, dan mereka memberikan pengecualian pembatalan sebagai tanggapan. Jadi, tidak ada yang bisa dilakukan di sini di penangan.

HttpClient itu sendiri yang membuat CancellationTokenSource yang relevan dan menetapkan batas waktu untuk itu:
https://github.com/dotnet/corefx/blob/bdd33976fe3713cc3e78b83cf2a1176c70b793be/src/System.Net.Http/src/System/Net/Http/HttpClient.cs#L488
Batas waktu sepenuhnya merupakan penemuan HttpClient itu sendiri, dan hanya ia yang tahu apakah ia ingin memperlakukan pengecualian pembatalan yang muncul sebagai khusus.

Itu berarti kode yang direferensikan di atas perlu difaktorkan ulang untuk melacak batas waktu secara terpisah, baik sebagai CancellationTokenSource terpisah, atau lebih mungkin dengan menangani logika batas waktu sendiri, dan selain membatalkan CTS, juga menyetel tanda yang dapat kemudian juga memeriksa. Ini kemungkinan akan menjadi non-pay-for-play, karena kami akan menambahkan alokasi tambahan di sini, tetapi dari perspektif kode, ini relatif mudah.

Kemudian di tempat-tempat di HttpClient seperti:
https://github.com/dotnet/corefx/blob/bdd33976fe3713cc3e78b83cf2a1176c70b793be/src/System.Net.Http/src/System/Net/Http/HttpClient.cs#L536 -L541
dan
https://github.com/dotnet/corefx/blob/bdd33976fe3713cc3e78b83cf2a1176c70b793be/src/System.Net.Http/src/System/Net/Http/HttpClient.cs#L562 -L566
ketika menangkap pengecualian, itu perlu pembatalan kasus khusus, untuk memeriksa untuk melihat apakah batas waktu berakhir, dan jika itu, anggap itu adalah penyebab pembatalan dan melemparkan pengecualian baru dari jenis yang diinginkan.

itu perlu pembatalan kasus khusus, untuk memeriksa untuk melihat apakah batas waktu berakhir, dan jika memang demikian, anggap itu adalah penyebab pembatalan dan berikan pengecualian baru dari jenis yang diinginkan.

Bagaimana cara kerjanya jika properti HttpClient.Timeout tidak terlibat? Mungkin diatur ke "tak terbatas". Bagaimana dengan token pembatalan yang diteruskan langsung ke API HttpClient (GetAsync, SendAync, dll.)? Saya berasumsi bahwa akan bekerja sama?

Bagaimana cara kerjanya jika properti HttpClient.Timeout tidak terlibat?

Saya tidak mengerti pertanyaannya ... itu satu-satunya batas waktu yang sedang kita bicarakan. Jika diatur ke Tak Terbatas, maka pembatalan tidak akan pernah disebabkan oleh batas waktu ini, dan tidak ada yang bisa dilakukan. Kami sudah Timeout kasus khusus == Tak Terbatas dan akan terus:
https://github.com/dotnet/corefx/blob/bdd33976fe3713cc3e78b83cf2a1176c70b793be/src/System.Net.Http/src/System/Net/Http/HttpClient.cs#L481

Bagaimana dengan token pembatalan yang diteruskan langsung ke API HttpClient (GetAsync, SendAync, dll.)? Saya berasumsi bahwa akan bekerja sama?

Kami menggabungkan token apa pun yang diteruskan dengan salah satu buatan kami sendiri:
https://github.com/dotnet/corefx/blob/bdd33976fe3713cc3e78b83cf2a1176c70b793be/src/System.Net.Http/src/System/Net/Http/HttpClient.cs#L485
Kami akan melacak penanganan HttpClient.Timeout kami sendiri, seperti yang disebutkan dalam komentar saya sebelumnya. Jika penelepon memilih untuk memasukkan token yang telah mereka konfigurasikan dengan batas waktu, maka memperlakukannya secara khusus terserah mereka: sejauh yang kami ketahui, itu hanya permintaan pembatalan.

Keberatan?

Mengapa memperkenalkan jenis pengecualian turunan baru yang "bersaing" dengan TimeoutException yang ada daripada, katakanlah, melemparkan pengecualian pembatalan yang membungkus TimeoutException sebagai pengecualian dalam? Jika tujuannya adalah untuk memungkinkan konsumen menentukan secara terprogram apakah pembatalan disebabkan oleh batas waktu, apakah itu sudah cukup? Tampaknya mengkomunikasikan informasi yang sama, tanpa memerlukan luas permukaan baru.

Mengapa memperkenalkan jenis pengecualian turunan baru yang "bersaing" dengan TimeoutException yang ada daripada, katakanlah, melemparkan pengecualian pembatalan yang membungkus TimeoutException sebagai pengecualian dalam

Untuk kenyamanan, kebanyakan. Lebih mudah dan lebih intuitif untuk menulis

catch(HttpTimeoutException)

dibandingkan

catch(OperationCanceledException ex) when (ex.InnerException is TimeoutException)

Yang sedang berkata, kedua cara baik-baik saja oleh saya, selama itu cukup mudah untuk mengidentifikasi batas waktu.

katakanlah, melempar pengecualian pembatalan yang membungkus TimeoutException sebagai pengecualian dalam?

Saya pikir itu kompromi yang bagus. Ini akan membantu membedakan log pengecualian, yang merupakan sumber utama gangguan saya ketika berhadapan dengan jenis waktu tunggu ini. Mungkin juga lebih baik mengkomunikasikan fakta bahwa ini adalah HttpClient yang membatalkan permintaan, daripada batas waktu yang dinaikkan oleh penangan yang mendasarinya. Juga, tidak ada perubahan yang melanggar.

Triase:

  • Kami akan memperbarui skenario ini untuk memiliki InnerException dari TimeoutException dengan pesan untuk logging/diagnostik yang dengan mudah membedakan pembatalan yang disebabkan oleh pemicu batas waktu.
  • Kami akan memperbarui dokumentasi dengan panduan tentang cara yang dapat diperiksa secara terprogram untuk membedakan pembatalan: memeriksa TimeoutException , menggunakan Timeout tak terbatas dan meneruskan CancellationToken dengan periode waktu habis, dll.

Kami menyadari bahwa setiap penyelesaian di sini melibatkan beberapa kompromi, dan percaya ini akan memberikan tingkat kompatibilitas yang baik dengan kode yang ada.

Mengapa memperkenalkan jenis pengecualian turunan baru yang "bersaing" dengan TimeoutException yang ada daripada, katakanlah, melemparkan pengecualian pembatalan yang membungkus TimeoutException sebagai pengecualian dalam

Untuk kenyamanan, kebanyakan. Lebih mudah dan lebih intuitif untuk menulis

catch(HttpTimeoutException)

dibandingkan

catch(OperationCanceledException ex) when (ex.InnerException is TimeoutException)

Yang sedang berkata, kedua cara baik-baik saja oleh saya, selama itu cukup mudah untuk mengidentifikasi batas waktu.

@scalablecory Saya kecewa karena saya tampaknya melewatkan diskusi selama beberapa jam - pengalaman saya mengomunikasikan hal-hal semacam ini dengan pengembang LoB .NET umum adalah bahwa menangkap HttpTimeoutException turunan jauh lebih mudah bagi orang untuk memahami/mengadopsi daripada harus ingatlah untuk menggunakan filter pengecualian pada pengecualian dalam. Tidak semua orang bahkan tahu apa itu filter pengecualian.

Pada saat yang sama, saya benar-benar menyadari bahwa ini adalah salah satu skenario di mana tidak ada satu solusi menang/tanpa kompromi yang jelas, seperti yang Anda katakan.

Kami menyetujui hal itu @ericsampson tetapi itu akan merusak perubahan bagi banyak pengguna yang ada. Seperti yang disebutkan @scalablecory, kami berkompromi untuk melakukan peningkatan sambil membatasi kerusakan kompatibilitas.

Ketika saya memikirkan pengecualian, secara umum, saya menganggap pendekatan pembungkus lebih baik. Mendapat pengecualian dari OperationCanceledException membuat pengecualian tersebut hanya tersedia dalam penggunaan asinkron. Dan bahkan itu ketat untuk tugas. Seperti yang kita lihat di masa lalu ada beberapa evolusi pendekatan async dan saya menganggap tugas sebagai detail implementasi namun TimeoutException, misalnya, adalah bagian dari konsep soket yang tidak akan berubah.

Kami menyetujui hal itu @ericsampson tetapi itu akan merusak perubahan bagi banyak pengguna yang ada. Seperti yang disebutkan @scalablecory, kami berkompromi untuk melakukan peningkatan sambil membatasi kerusakan kompatibilitas.

Meskipun saya tidak selalu tidak setuju dengan opsi pembungkus, saya pikir perlu untuk menunjukkan bahwa - sejauh yang saya pahami - masalahnya bukanlah bahwa opsi yang diturunkan adalah perubahan yang melanggar, tetapi bahwa pembungkusnya lebih sederhana. Seperti yang disebutkan @stephentoub sebelumnya dalam diskusi, melempar pengecualian turunan tidak dianggap sebagai perubahan yang melanggar oleh pedoman corefx.

Menurunkan HttpTimeoutException dari TaskCanceledException melanggar hubungan "adalah" antara dua pengecualian ini. Itu masih akan membuat kode panggilan untuk berpikir bahwa pembatalan terjadi, kecuali jika secara khusus menangani HttpTimeoutException. Ini pada dasarnya adalah penanganan yang sama yang harus kami lakukan pada saat kami memeriksa pembatalan token yang dibatalkan untuk menentukan apakah itu batas waktu atau tidak. Selain itu, memiliki dua pengecualian batas waktu di perpustakaan inti membingungkan.
Menambahkan TimeoutException sebagai pengecualian dalam ke TaskCanceledException tidak akan memberikan informasi tambahan apa pun dalam banyak kasus, karena kami sudah dapat memeriksa token pembatalan dan menentukan apakah itu timeout atau tidak.

Saya pikir satu-satunya solusi yang masuk akal adalah mengubah pengecualian yang dilemparkan pada waktu habis menjadi TimeoutException. Ya, ini akan menjadi perubahan besar, tetapi masalahnya sudah mencakup beberapa rilis utama dan perubahan besar adalah untuk apa rilis besar itu.

Bisakah dokumentasi untuk versi yang ada diperbarui untuk menunjukkan bahwa metode dapat membuang TaskCanceledException dan/atau OperationCanceledException (kedua tipe konkret tampaknya menjadi kemungkinan) ketika batas waktu terjadi? Saat ini dokumentasi menunjukkan HttpRequestException berurusan dengan batas waktu, yang tidak benar dan menyesatkan.

@qidydl itulah rencananya... akan sepenuhnya mendokumentasikan cara apa pun yang dapat dimanifestasikan oleh timeout dan bagaimana merincinya.

Terima kasih @alnikola dan @scalablecory dan @davidsh dan @karelz dan semua orang yang terlibat dalam diskusi/implementasi ini, saya sangat menghargainya.

Apakah halaman ini membantu?
0 / 5 - 0 peringkat