Tarayıcı isteği iptal ettiğinde ASP.NET Web API OperationCanceledException


119

Bir kullanıcı bir sayfa yüklediğinde, ASP.NET Web API 2 denetleyicilerini vuran bir veya daha fazla ajax isteği yapar. Kullanıcı başka bir sayfaya giderse, bu ajax talepleri tamamlanmadan önce, istekler tarayıcı tarafından iptal edilir. ELMAH HttpModülümüz daha sonra iptal edilen her istek için iki hata kaydeder:

Hata 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()

Hata 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)

Yığın izine baktığımda, istisnanın buradan atıldığını görüyorum: https://github.com/ASP-NET-MVC/aspnetwebstack/blob/master/src/System.Web.Http.WebHost/HttpControllerHandler.cs# L413

Sorum şu: Bu istisnalarla nasıl başa çıkabilirim ve yok sayabilirim?

Kullanıcı kodunun dışında görünüyor ...

Notlar:

  • ASP.NET Web API 2 kullanıyorum
  • Web API uç noktaları, zaman uyumsuz ve eşzamansız olmayan yöntemlerin bir karışımıdır.
  • Hata günlüğünü nereye eklersem eklemem, kullanıcı kodundaki istisnayı yakalayamıyorum

1
Aynı istisnaları (TaskCanceledException ve OperationCanceledException) Katana kitaplıklarının güncel sürümüyle de gördük.
David McClelland

Her iki istisnanın ne zaman meydana geldiğiyle ilgili daha fazla ayrıntı buldum ve bu geçici çözümün yalnızca bunlardan birine karşı işe yaradığını anladım. İşte bazı ayrıntılar: stackoverflow.com/questions/22157596/…
Ilya Chernomordik

Yanıtlar:


78

Bu ASP.NET Web API 2'deki bir hatadır ve maalesef her zaman başarılı olacak bir geçici çözüm olduğunu düşünmüyorum. Bir hata bildirdikBizim tarafımızda düzeltmek için .

Sonuçta sorun, bu durumda ASP.NET'e iptal edilmiş bir görevi döndürmemiz ve ASP.NET'in iptal edilmiş bir görevi işlenmemiş bir istisna gibi ele almasıdır (sorunu Uygulama olay günlüğüne kaydeder).

Bu arada, aşağıdaki kod gibi bir şey deneyebilirsiniz. İptal belirteci tetiklendiğinde içeriği kaldıran üst düzey bir mesaj işleyici ekler. Yanıtın içeriği yoksa, hata tetiklenmemelidir. Yine de küçük bir olasılık vardır, çünkü istemci, mesaj işleyici iptal belirtecini kontrol ettikten hemen sonra ancak daha yüksek seviyeli Web API kodu aynı kontrolü yapmadan önce bağlantıyı kesebilir. Ancak çoğu durumda yardımcı olacağını düşünüyorum.

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;
    }
}

2
Güncelleme olarak, bu bazı istekleri yakalar. Hala günlüklerimizde epeyce görüyoruz. Çalışma için teşekkürler. Bir düzeltmeyi dört gözle bekliyorum.
Bates Westmoreland

2
@KiranChalla - 5.2.2'ye yükseltmenin hala bu hataları içerdiğini doğrulayabilirim.
KnightFox

2
Yine de hatayı alıyorum. Yukarıdaki öneriyi, diğer ipuçlarını kullandım.
M2012

3
Yukarıdaki öneriyi denediğimde, istek gönderilmeden önce iptal edildiğinde hala İstisnalar alıyorum SendAsync( F5API'nize istek gönderen bir url'de tarayıcıda basılı tutarak bunu simüle edebilirsiniz . Bu sorunu da çözdüm. if (cancellationToken.IsCancellationRequested)çeki aramanın üzerine ekleyerek SendAsync. Artık tarayıcı istekleri hızlı bir şekilde iptal ettiğinde istisnalar artık
görünmüyor

2
Her iki istisnanın ne zaman meydana geldiğiyle ilgili daha fazla ayrıntı buldum ve bu geçici çözümün yalnızca bunlardan birine karşı işe yaradığını anladım. İşte bazı ayrıntılar: stackoverflow.com/a/51514604/1671558
Ilya Chernomordik

17

WebApi için bir istisna kaydedici uygularken, System.Web.Http.ExceptionHandling.ExceptionLoggerbir ExceptionFilter oluşturmak yerine sınıfın genişletilmesi önerilir . WebApi dahili öğeleri, iptal edilen istekler için ExceptionLoggers'ın Log yöntemini çağırmaz (ancak, istisna filtreleri bunları alır). Bu tasarım gereğidir.

HttpConfiguration.Services.Add(typeof(IExceptionLogger), myWebApiExceptionLogger); 

Görünüşe göre bu yaklaşımla ilgili sorun, hatanın Global.asax hata işlemede hala ortaya çıkması ... Olay istisna işleyicisine gönderilmemesine rağmen
Ilya Chernomordik

14

İşte bu sorun için başka bir geçici çözüm. Aşağıdakileri yakalayan OWIN ardışık düzeninin başına özel bir OWIN ara yazılımı eklemeniz yeterlidir OperationCanceledException:

#if !DEBUG
app.Use(async (ctx, next) =>
{
    try
    {
        await next();
    }
    catch (OperationCanceledException)
    {
    }
});
#endif

2
Bu hatayı öncelikle OWIN bağlamından alıyordum ve bu daha iyi hedefliyor
NitinSingh

3

Varsayılan TPL görevi istisna işleme davranışını şu yollarla değiştirmeyi deneyebilirsiniz web.config:

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

Ardından , web uygulamanızda halledecek bir staticsınıfa (bir staticyapıcı ile) sahip olun AppDomain.UnhandledException.

Ancak, bu istisnanın aslında ASP.NET Web API çalışma zamanı içinde bir yerde ele alındığı görülüyor, siz bunu kodunuzla işleme şansınız bile olmadan.

Bu durumda, bunu bir 1. şans istisnası olarak yakalayabilmeniz gerekir AppDomain.CurrentDomain.FirstChanceException, burada nasıl yapılır . Aradığın şeyin bu olmayabileceğini anlıyorum.


1
Bunların hiçbiri istisnayı halletmeme izin vermedi.
Bates Westmoreland

@BatesWestmoreland, değil FirstChanceExceptionmi? HTTP isteklerinde devam eden statik bir sınıfla işlemeyi denediniz mi?
noseratio

2
Bu istisnaları yakalamak ve görmezden gelmek için çözmeye çalıştığım problem. AppDomain.UnhandledExceptionVeya kullanmak AppDomain.CurrentDomain.FirstChanceExceptionistisnayı incelememe izin verebilir, ancak yakalama ve görmezden gelme değil. Bu yaklaşımlardan herhangi birini kullanarak bu istisnaları ele almanın bir yolunu görmedim. Eğer Yanlışsam beni düzelt.
Bates Westmoreland

2

Bazen Web API 2 uygulamamda aynı 2 istisnayı alıyorum, ancak bunları genel bir istisna filtresiApplication_Error içinde Global.asax.csve kullanarak bunları yakalayabilirim .

İşin garibi, bu istisnaları yakalamamayı tercih ediyorum, çünkü uygulamayı çökertebilecek tüm işlenmemiş istisnaları her zaman günlüğe kaydediyorum (ancak bu 2, benim için alakasız ve görünüşe göre çökmemeli veya en azından çökmemeli ama yanılıyor olabilirim). Bu hataların bazı zaman aşımlarının sona ermesi veya istemcinin açık iptali nedeniyle ortaya çıktığından şüpheleniyorum, ancak bunların ASP.NET çerçevesi içinde ele alınmasını ve işlenmemiş istisnalar olarak bunun dışına yayılmamasını beklerdim.


Benim durumumda, bu istisnalar, kullanıcı yeni bir url'ye giderken tarayıcının isteği iptal etmesi nedeniyle ortaya çıkar.
Bates Westmoreland

1
Anlıyorum. Benim durumumda, istekler WinHTTPbir tarayıcıdan değil , API aracılığıyla yayınlanıyor .
Gabriel S.

2

Bu hatayla ilgili biraz daha ayrıntı buldum. Olabilecek 2 olası istisna vardır:

  1. OperationCanceledException
  2. TaskCanceledException

Birincisi, denetleyicideki kodunuz (veya muhtemelen bunun etrafındaki bazı sistem kodları) yürütülürken bağlantı kesilirse gerçekleşir. İkincisi, yürütme bir öznitelik içindeyken bağlantı kesilirse gerçekleşir (örn. AuthorizeAttribute).

Dolayısıyla sağlanan geçici çözüm , kısmen birinci istisnayı azaltmaya yardımcı olurken, ikinciye yardımcı olmak için hiçbir şey yapmaz. İkinci durumda, iptal simgesinin true olarak ayarlanması yerine çağrı TaskCanceledExceptionsırasında base.SendAsyncgerçekleşir.

Bunları çözmenin iki yolunu görebiliyorum:

  1. Global.asax'taki her iki istisnayı da görmezden gelin. Ardından, bunun yerine önemli bir şeyi aniden görmezden gelmenin mümkün olup olmadığı sorusu geliyor.
  2. İşleyicide ek bir deneme / yakalama yapmak (kurşun geçirmez olmasa da + hala TaskCanceledExceptiongöz ardı etmemizin günlüğe kaydetmek istediğimiz bir şey olma olasılığı vardır .

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;
    }
}

Yanlış istisnaları saptamaya çalışabilmenin tek yolu, stacktrace'in bazı Asp.Net öğeleri içerip içermediğini kontrol etmektir. Yine de çok sağlam görünmüyor.

Not: Bu hataları şu şekilde filtreliyorum:

private static bool IsAspNetBugException(Exception exception)
{
    return
        (exception is TaskCanceledException || exception is OperationCanceledException) 
        &&
        exception.StackTrace.Contains("System.Web.HttpApplication.ExecuteStep");
}

1
Önerilen kodunuzda, responsedeğişkeni try içinde oluşturursunuz ve denemenin dışında döndürürsünüz. Bu mümkün olamaz, değil mi? Ayrıca IsAspNetBugException'ı nerede kullanıyorsunuz?
Schoof

1
Hayır, bu işe yaramaz, elbette dene / yakala bloğunun dışında bildirilmeli ve tamamlanmış görev gibi bir şeyle başlatılmalıdır. Zaten kurşun geçirmez olmayan çözümün bir örneği. Diğer soruya gelince, bunu Global.Asax OnError işleyicisinde kullanırsınız. Mesajlarınızı kaydetmek için bunu kullanmazsanız, yine de endişelenmenize gerek yok. Bunu yaparsanız, bu "hata olmayanları" sistemden nasıl filtrelediğinizin bir örneğidir.
Ilya Chernomordik

1

Aynı istisnayı alıyorduk, @ dmatson'ın geçici çözümünü kullanmaya çalıştık, ancak yine de bazı istisnalar elde edebilirdik. Yakın zamana kadar bununla uğraştık. Bazı Windows günlüklerinin endişe verici bir hızla büyüdüğünü fark ettik.

Şu konumda bulunan hata dosyaları: C: ​​\ Windows \ System32 \ LogFiles \ HTTPERR

Hataların çoğu "Timer_ConnectionIdle" içindi. Etrafı aradım ve göründü ki web api araması bitmiş olsa bile, bağlantı orijinal bağlantıdan sonra iki dakika daha devam etti.

Daha sonra yanıtta bağlantıyı kapatmaya çalışmamız ve ne olacağını görmemiz gerektiğini düşündüm.

ekledim response.Headers.ConnectionClose = true; SendAsync MessageHandler ve ben istemciler bağlantıları kapanıyor ve biz artık sorun yaşamıyorsanız söyleyebilirim gelen.

Bunun en iyi çözüm olmadığını biliyorum, ancak bizim durumumuzda işe yarıyor. Ayrıca, performans açısından eminim ki, API'niz aynı istemciden arka arkaya birden fazla çağrı alıyorsa, bunun yapmak isteyeceğiniz bir şey değildir.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.