ASP.NET Web API OperationCanceledException saat browser membatalkan permintaan

119

Ketika pengguna memuat halaman, itu membuat satu atau lebih permintaan ajax, yang menekan pengontrol ASP.NET Web API 2. Jika pengguna menavigasi ke halaman lain, sebelum permintaan ajax ini selesai, permintaan dibatalkan oleh browser. ELMAH HttpModule kami kemudian mencatat dua kesalahan untuk setiap permintaan yang dibatalkan:

Galat 1:

System.Threading.Tasks.TaskCanceledException: A task was canceled.
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()
   at System.Web.Http.Controllers.ApiControllerActionInvoker.<InvokeActionAsyncCore>d__0.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()
   at System.Web.Http.Controllers.ActionFilterResult.<ExecuteAsync>d__2.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Web.Http.Filters.AuthorizationFilterAttribute.<ExecuteAuthorizationFilterAsyncCore>d__2.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()
   at System.Web.Http.Controllers.ExceptionFilterResult.<ExecuteAsync>d__0.MoveNext()

Kesalahan 2:

System.OperationCanceledException: The operation was canceled.
   at System.Threading.CancellationToken.ThrowIfCancellationRequested()
   at System.Web.Http.WebHost.HttpControllerHandler.<WriteBufferedResponseContentAsync>d__1b.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Web.Http.WebHost.HttpControllerHandler.<CopyResponseAsync>d__7.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Web.Http.WebHost.HttpControllerHandler.<ProcessRequestAsyncCore>d__0.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Web.TaskAsyncHelper.EndTask(IAsyncResult ar)
   at System.Web.HttpApplication.CallHandlerExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute()
   at System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously)

Melihat stacktrace, saya melihat bahwa pengecualian dilempar dari sini: https://github.com/ASP-NET-MVC/aspnetwebstack/blob/master/src/System.Web.Http.WebHost/HttpControllerHandler.cs# L413

Pertanyaan saya adalah: Bagaimana cara menangani dan mengabaikan pengecualian ini?

Tampaknya di luar kode pengguna ...

Catatan:

  • Saya menggunakan ASP.NET Web API 2
  • Titik akhir API Web adalah campuran metode asinkron dan non-asinkron.
  • Di mana pun saya menambahkan pencatatan kesalahan, saya tidak dapat menangkap pengecualian dalam kode pengguna
Bates Westmoreland
sumber
1
Kami telah melihat pengecualian yang sama (TaskCanceledException dan OperationCanceledException) dengan versi library Katana saat ini juga.
David McClelland
Saya telah menemukan beberapa detail lebih lanjut tentang kapan kedua pengecualian terjadi dan menemukan bahwa solusi ini hanya berfungsi untuk salah satunya. Berikut adalah beberapa detailnya: stackoverflow.com/questions/22157596/…
Ilya Chernomordik

Jawaban:

78

Ini adalah bug di ASP.NET Web API 2 dan sayangnya, saya rasa tidak ada solusi yang akan selalu berhasil. Kami mengajukan bug untuk memperbaikinya di pihak kami.

Pada akhirnya, masalahnya adalah kita mengembalikan tugas yang dibatalkan ke ASP.NET dalam kasus ini, dan ASP.NET memperlakukan tugas yang dibatalkan seperti pengecualian yang tidak tertangani (itu mencatat masalah di log peristiwa aplikasi).

Sementara itu, Anda dapat mencoba sesuatu seperti kode di bawah ini. Ia menambahkan penangan pesan tingkat atas yang menghapus konten saat token pembatalan diaktifkan. Jika respons tidak memiliki konten, bug tidak boleh dipicu. Masih ada kemungkinan kecil hal itu bisa terjadi, karena klien dapat memutuskan sambungan tepat setelah penangan pesan memeriksa token pembatalan tetapi sebelum kode API Web tingkat yang lebih tinggi melakukan pemeriksaan yang sama. Tapi saya pikir itu akan membantu dalam banyak kasus.

David

config.MessageHandlers.Add(new CancelledTaskBugWorkaroundMessageHandler());

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

        // Try to suppress response content when the cancellation token has fired; ASP.NET will log to the Application event log if there's content in this case.
        if (cancellationToken.IsCancellationRequested)
        {
            return new HttpResponseMessage(HttpStatusCode.InternalServerError);
        }

        return response;
    }
}
dmatson.dll
sumber
2
Sebagai pembaruan, ini menangkap beberapa permintaan. Kami masih melihat beberapa di log kami. Terima kasih untuk pekerjaannya. Menantikan perbaikan.
Bates Westmoreland
2
@KiranChall - Saya dapat mengonfirmasi bahwa peningkatan ke 5.2.2 masih memiliki kesalahan ini.
KnightFox
2
Masih saya mendapatkan kesalahan. Saya telah menggunakan saran di atas, petunjuk lainnya.
M2012
3
Ketika saya mencoba rekomendasi di atas, saya masih menerima Pengecualian ketika permintaan telah dibatalkan bahkan sebelum diteruskan ke SendAsync(Anda dapat mensimulasikan ini dengan menahan F5di browser pada url yang membuat permintaan ke Api Anda. Saya menyelesaikan masalah ini dengan juga menambahkan if (cancellationToken.IsCancellationRequested)centang di atas panggilan ke SendAsync. Sekarang pengecualian tidak lagi muncul saat browser dengan cepat membatalkan permintaan.
seangwright
2
Saya telah menemukan beberapa detail lebih lanjut tentang kapan kedua pengecualian terjadi dan menemukan bahwa solusi ini hanya berfungsi untuk salah satunya. Berikut beberapa detailnya: stackoverflow.com/a/51514604/1671558
Ilya Chernomordik
17

Saat mengimplementasikan logger pengecualian untuk WebApi, disarankan untuk memperluas System.Web.Http.ExceptionHandling.ExceptionLoggerkelas daripada membuat ExceptionFilter. Internal WebApi tidak akan memanggil metode Log ExceptionLoggers untuk permintaan yang dibatalkan (namun, filter pengecualian akan mendapatkannya). Ini memang disengaja.

HttpConfiguration.Services.Add(typeof(IExceptionLogger), myWebApiExceptionLogger); 
Shaddy Zeineddine
sumber
Tampaknya masalah dengan pendekatan ini adalah bahwa kesalahan masih muncul dalam penanganan kesalahan Global.asax ... Meskipun acara tidak dikirim ke penangan pengecualian
Ilya Chernomordik
14

Berikut solusi lain untuk masalah ini. Cukup tambahkan middleware OWIN khusus di awal pipeline OWIN yang menangkap OperationCanceledException:

#if !DEBUG
app.Use(async (ctx, next) =>
{
    try
    {
        await next();
    }
    catch (OperationCanceledException)
    {
    }
});
#endif
huysentruitw
sumber
2
Saya mendapatkan kesalahan ini terutama dari konteks OWIN dan yang ini menargetkannya dengan lebih baik
NitinSingh
3

Anda dapat mencoba mengubah perilaku penanganan pengecualian tugas TPL default melalui web.config:

<configuration> 
    <runtime> 
        <ThrowUnobservedTaskExceptions enabled="true"/> 
    </runtime> 
</configuration>

Kemudian miliki statickelas (dengan statickonstruktor) di aplikasi web Anda, yang akan menangani AppDomain.UnhandledException.

Namun, tampaknya itu pengecualian ini sebenarnya ditangani di suatu tempat di dalam runtime API Web ASP.NET , bahkan sebelum Anda memiliki kesempatan untuk menanganinya dengan kode Anda.

Dalam hal ini, Anda harus dapat menangkapnya sebagai pengecualian kesempatan pertama, dengan AppDomain.CurrentDomain.FirstChanceException, berikut caranya . Saya mengerti ini mungkin bukan yang Anda cari.

noseratio
sumber
1
Tak satu pun dari mereka mengizinkan saya untuk menangani pengecualian.
Bates Westmoreland
@ BatesWestmoreland, bahkan tidak FirstChanceException? Sudahkah Anda mencoba menanganinya dengan kelas statis yang tetap ada di seluruh permintaan HTTP?
noseratio
2
Masalah yang saya coba selesaikan untuk menangkapnya, dan mengabaikan pengecualian ini. Menggunakan AppDomain.UnhandledExceptionatau AppDomain.CurrentDomain.FirstChanceExceptionmengizinkan saya untuk memeriksa pengecualian, tetapi tidak menangkap dan mengabaikan. Saya tidak melihat cara untuk menandai pengecualian ini sebagai ditangani menggunakan salah satu dari pendekatan ini. Koreksi saya jika saya salah.
Bates Westmoreland
2

Terkadang saya mendapatkan 2 pengecualian yang sama di aplikasi API Web 2 saya, namun saya dapat menangkapnya dengan Application_Errormetode di Global.asax.csdan menggunakan filter pengecualian umum .

Lucunya, saya memilih untuk tidak menangkap pengecualian ini, karena saya selalu mencatat semua pengecualian yang tidak tertangani yang dapat merusak aplikasi (2 ini, bagaimanapun, tidak relevan bagi saya dan tampaknya tidak atau setidaknya tidak boleh crash itu, tapi saya mungkin salah). Saya menduga kesalahan ini muncul karena beberapa batas waktu kedaluwarsa atau pembatalan eksplisit dari klien, tetapi saya berharap mereka diperlakukan di dalam kerangka ASP.NET dan tidak disebarkan di luar sebagai pengecualian yang tidak tertangani.

Gabriel S.
sumber
Dalam kasus saya, pengecualian ini terjadi karena browser membatalkan permintaan saat pengguna menavigasi ke url baru.
Bates Westmoreland
1
Saya melihat. Dalam kasus saya, permintaan dikeluarkan melalui WinHTTPAPI, bukan dari browser.
Gabriel S.
2

Saya telah menemukan sedikit lebih banyak detail tentang kesalahan ini. Ada 2 kemungkinan pengecualian yang dapat terjadi:

  1. OperationCanceledException
  2. TaskCanceledException

Yang pertama terjadi jika koneksi terputus saat kode Anda dalam pengontrol dijalankan (atau mungkin beberapa kode sistem di sekitarnya juga). Sedangkan yang kedua terjadi jika koneksi terputus saat eksekusi berada di dalam atribut (misalnya AuthorizeAttribute).

Jadi solusi yang disediakan membantu untuk mengurangi sebagian dari pengecualian pertama, tidak ada yang membantu dengan yang kedua. Dalam kasus terakhir, TaskCanceledExceptionterjadi selama base.SendAsyncpanggilan itu sendiri daripada token pembatalan yang disetel ke true.

Saya dapat melihat dua cara untuk menyelesaikan ini:

  1. Hanya mengabaikan kedua pengecualian di global.asax. Lalu muncul pertanyaan apakah mungkin tiba-tiba mengabaikan sesuatu yang penting?
  2. Melakukan try / catch tambahan di handler (walaupun tidak anti peluru + masih ada kemungkinan TaskCanceledExceptionyang kita abaikan akan menjadi salah satu yang ingin kita log.

config.MessageHandlers.Add(new CancelledTaskBugWorkaroundMessageHandler());

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

            // Try to suppress response content when the cancellation token has fired; ASP.NET will log to the Application event log if there's content in this case.
            if (cancellationToken.IsCancellationRequested)
            {
                return new HttpResponseMessage(HttpStatusCode.InternalServerError);
            }
        }
        catch (TaskCancellationException)
        {
            // Ignore
        }

        return response;
    }
}

Satu-satunya cara saya mengetahui bahwa kita dapat mencoba menunjukkan pengecualian yang salah adalah dengan memeriksa apakah stacktrace berisi beberapa item Asp.Net. Sepertinya tidak terlalu kuat.

NB Beginilah cara saya memfilter kesalahan ini:

private static bool IsAspNetBugException(Exception exception)
{
    return
        (exception is TaskCanceledException || exception is OperationCanceledException) 
        &&
        exception.StackTrace.Contains("System.Web.HttpApplication.ExecuteStep");
}
Ilya Chernomordik
sumber
1
Dalam kode yang Anda sarankan, Anda membuat responsevariabel di dalam percobaan dan mengembalikannya di luar percobaan. Itu tidak mungkin berhasil, bukan? Juga di mana Anda menggunakan IsAspNetBugException?
Schoof
1
Tidak, itu tidak bisa berfungsi, itu hanya harus dideklarasikan di luar blok coba / tangkap dan diinisialisasi dengan sesuatu seperti tugas yang sudah selesai. Ini hanya contoh solusi yang tidak anti peluru. Adapun pertanyaan lain Anda menggunakannya dalam penanganan Global.Asax OnError. Jika Anda tidak menggunakannya untuk mencatat pesan Anda, Anda tidak perlu khawatir. Jika Anda melakukannya, ini adalah contoh bagaimana Anda memfilter "non error" dari sistem.
Ilya Chernomordik
1

Kami telah menerima pengecualian yang sama, kami mencoba menggunakan solusi @ dmatson, tetapi kami masih mendapatkan beberapa pengecualian. Kami menanganinya sampai saat ini. Kami melihat beberapa log Windows tumbuh dengan kecepatan yang mengkhawatirkan.

File kesalahan terletak di: C: \ Windows \ System32 \ LogFiles \ HTTPERR

Sebagian besar kesalahan adalah untuk "Timer_ConnectionIdle". Saya mencari-cari dan tampaknya meskipun panggilan web api selesai, koneksi masih bertahan selama dua menit setelah koneksi asli.

Saya kemudian berpikir kita harus mencoba menutup koneksi dalam tanggapan dan melihat apa yang terjadi.

Saya menambahkan response.Headers.ConnectionClose = true;ke SendAsync MessageHandler dan dari apa yang saya tahu klien menutup koneksi dan kami tidak mengalami masalah lagi.

Saya tahu ini bukan solusi terbaik, tetapi berhasil dalam kasus kami. Saya juga cukup yakin dari segi performa, ini bukan sesuatu yang ingin Anda lakukan jika API Anda mendapatkan banyak panggilan dari klien yang sama secara berurutan.

Michael Margala
sumber