ASP.NET Web API OperationCanceledException เมื่อเบราว์เซอร์ยกเลิกการร้องขอ


119

เมื่อผู้ใช้โหลดเพจผู้ใช้จะทำการร้องขอ ajax อย่างน้อยหนึ่งรายการซึ่งเข้าสู่ตัวควบคุม ASP.NET Web API 2 หากผู้ใช้ไปยังหน้าอื่นก่อนที่คำขอ ajax เหล่านี้จะเสร็จสมบูรณ์คำขอจะถูกยกเลิกโดยเบราว์เซอร์ ELMAH HttpModule ของเราจะบันทึกข้อผิดพลาดสองข้อสำหรับแต่ละคำขอที่ถูกยกเลิก:

ข้อผิดพลาด 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()

ข้อผิดพลาด 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)

เมื่อดู stacktrace ฉันเห็นว่ามีการโยนข้อยกเว้นจากที่นี่: https://github.com/ASP-NET-MVC/aspnetwebstack/blob/master/src/System.Web.Http.WebHost/HttpControllerHandler.cs# L413

คำถามของฉันคือฉันจะจัดการและเพิกเฉยต่อข้อยกเว้นเหล่านี้ได้อย่างไร

ดูเหมือนจะอยู่นอกรหัสผู้ใช้ ...

หมายเหตุ:

  • ฉันใช้ ASP.NET Web API 2
  • จุดสิ้นสุดของ Web API เป็นการผสมผสานระหว่างเมธอด async และ non-async
  • ไม่ว่าฉันจะเพิ่มการบันทึกข้อผิดพลาดที่ใดฉันก็ไม่สามารถตรวจจับข้อยกเว้นในรหัสผู้ใช้ได้

1
เราได้เห็นข้อยกเว้นเดียวกัน (TaskCanceledException และ OperationCanceledException) กับไลบรารี Katana เวอร์ชันปัจจุบันด้วย
David McClelland

ฉันพบรายละเอียดเพิ่มเติมเกี่ยวกับเวลาที่ข้อยกเว้นทั้งสองเกิดขึ้นและพบว่าวิธีแก้ปัญหานี้ใช้ได้ผลกับข้อยกเว้นข้อใดข้อหนึ่งเท่านั้น นี่คือรายละเอียดบางส่วน: stackoverflow.com/questions/22157596/…
Ilya Chernomordik

คำตอบ:


78

นี่เป็นข้อบกพร่องใน ASP.NET Web API 2 และน่าเสียดายที่ฉันไม่คิดว่าจะมีวิธีแก้ปัญหาที่จะประสบความสำเร็จเสมอไป เรายื่นข้อบกพร่องเพื่อแก้ไขในฝั่งของเรา

ท้ายที่สุดปัญหาคือเราส่งคืนงานที่ถูกยกเลิกไปยัง ASP.NET ในกรณีนี้และ ASP.NET ถือว่างานที่ถูกยกเลิกเหมือนกับข้อยกเว้นที่ไม่สามารถจัดการได้ (จะบันทึกปัญหาในบันทึกเหตุการณ์ของแอปพลิเคชัน)

ในระหว่างนี้คุณสามารถลองใช้โค้ดด้านล่างนี้ เพิ่มตัวจัดการข้อความระดับบนสุดที่จะลบเนื้อหาเมื่อโทเค็นการยกเลิกเริ่มทำงาน หากการตอบกลับไม่มีเนื้อหาข้อบกพร่องไม่ควรถูกทริกเกอร์ ยังมีความเป็นไปได้เล็กน้อยที่อาจเกิดขึ้นเนื่องจากไคลเอนต์สามารถยกเลิกการเชื่อมต่อได้ทันทีหลังจากที่ตัวจัดการข้อความตรวจสอบโทเค็นการยกเลิก แต่ก่อนที่รหัส Web API ระดับสูงกว่าจะทำการตรวจสอบแบบเดียวกัน แต่ฉันคิดว่ามันจะช่วยได้ในกรณีส่วนใหญ่

เดวิด

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
ในการอัปเดตสิ่งนี้จะรวบรวมคำขอบางส่วน เรายังเห็นบางส่วนในบันทึกของเรา ขอบคุณสำหรับการแก้ไขปัญหา รอคอยที่จะแก้ไข
Bates Westmoreland

2
@KiranChalla - ฉันยืนยันได้ว่าการอัปเกรดเป็น 5.2.2 ยังคงมีข้อผิดพลาดเหล่านี้
KnightFox

2
ฉันยังคงได้รับข้อผิดพลาด ฉันใช้คำแนะนำข้างต้นเบาะแสอื่น ๆ
M2012

3
เมื่อฉันลองทำตามคำแนะนำข้างต้นฉันยังคงได้รับข้อยกเว้นเมื่อคำขอถูกยกเลิกก่อนที่จะส่งต่อไปSendAsync(คุณสามารถจำลองสิ่งนี้ได้โดยกดค้างF5ในเบราว์เซอร์บน url ที่ส่งคำขอไปยัง Api ของคุณฉันแก้ไขปัญหานี้ด้วย เพิ่มการif (cancellationToken.IsCancellationRequested)ตรวจสอบด้านบนการเรียกเข้าไปSendAsyncตอนนี้ข้อยกเว้นจะไม่ปรากฏอีกต่อไปเมื่อเบราว์เซอร์ยกเลิกคำขออย่างรวดเร็ว
seangwright

2
ฉันพบรายละเอียดเพิ่มเติมเกี่ยวกับเวลาที่ข้อยกเว้นทั้งสองเกิดขึ้นและพบว่าวิธีแก้ปัญหานี้ใช้ได้ผลกับข้อยกเว้นข้อใดข้อหนึ่งเท่านั้น นี่คือรายละเอียดบางส่วน: stackoverflow.com/a/51514604/1671558
Ilya Chernomordik

17

เมื่อใช้ตัวบันทึกข้อยกเว้นสำหรับ WebApi ขอแนะนำให้ขยายSystem.Web.Http.ExceptionHandling.ExceptionLoggerคลาสแทนที่จะสร้าง ExceptionFilter WebApi ภายในจะไม่เรียกเมธอด Log ของ ExceptionLoggers สำหรับคำขอที่ถูกยกเลิก (อย่างไรก็ตามตัวกรองข้อยกเว้นจะได้รับ) นี่คือการออกแบบ

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

ดูเหมือนว่าปัญหาในแนวทางนี้คือข้อผิดพลาดยังคงปรากฏขึ้นในการจัดการข้อผิดพลาด Global.asax ... แม้ว่าจะไม่ได้ส่งไปยังตัวจัดการข้อยกเว้นก็ตาม
Ilya Chernomordik

14

นี่เป็นวิธีแก้ปัญหาอื่น ๆ สำหรับปัญหานี้ เพียงเพิ่มมิดเดิลแวร์ OWIN ที่กำหนดเองที่จุดเริ่มต้นของไปป์ไลน์ OWIN ที่จับOperationCanceledException:

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

2
ฉันได้รับข้อผิดพลาดนี้เป็นหลักจากบริบท OWIN และข้อนี้กำหนดเป้าหมายให้ดีขึ้น
NitinSingh

3

คุณสามารถลองเปลี่ยนพฤติกรรมการจัดการข้อยกเว้นงาน TPL เริ่มต้นผ่านweb.config:

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

จากนั้นมีstaticระดับ (ที่มีstaticคอนสตรัค) ใน app AppDomain.UnhandledExceptionเว็บของคุณซึ่งจะจัดการ

อย่างไรก็ตามดูเหมือนว่าข้อยกเว้นนี้จะได้รับการจัดการในรันไทม์ ASP.NET Web APIก่อนที่คุณจะมีโอกาสจัดการกับโค้ดของคุณด้วยซ้ำ

ในกรณีนี้คุณควรจะสามารถที่จะจับมันเป็นข้อยกเว้นโอกาสที่ 1 ด้วยAppDomain.CurrentDomain.FirstChanceException, นี่คือวิธี ฉันเข้าใจว่านี่อาจไม่ใช่สิ่งที่คุณกำลังมองหา


1
ทั้งสองอย่างไม่อนุญาตให้ฉันจัดการข้อยกเว้นเช่นกัน
Bates Westmoreland

@BatesWestmoreland ไม่ได้FirstChanceExceptionเหรอ? คุณได้ลองจัดการกับคลาสแบบคงที่ซึ่งยังคงอยู่ในคำขอ HTTP หรือไม่?
อัตราส่วนจมูก

2
ปัญหาที่ฉันพยายามแก้ไขเพื่อตรวจจับและเพิกเฉยต่อข้อยกเว้นเหล่านี้ ใช้AppDomain.UnhandledExceptionหรือAppDomain.CurrentDomain.FirstChanceExceptionอาจอนุญาตให้ฉันตรวจสอบข้อยกเว้น แต่ไม่จับและเพิกเฉย ฉันไม่เห็นวิธีที่จะทำเครื่องหมายข้อยกเว้นเหล่านี้ว่าจัดการโดยใช้วิธีใดวิธีหนึ่งเหล่านี้ แก้ไขฉันถ้าฉันผิด
Bates Westmoreland

2

บางครั้งผมได้รับเดียวกัน 2 ข้อยกเว้นในการประยุกต์ใช้ Web API 2 ของฉัน แต่ฉันสามารถจับพวกเขาด้วยApplication_ErrorวิธีการในGlobal.asax.csการใช้ทั่วไปยกเว้นของตัวกรอง

สิ่งที่ตลกคือฉันไม่ต้องการจับข้อยกเว้นเหล่านี้เพราะฉันมักจะบันทึกข้อยกเว้นที่ไม่สามารถจัดการได้ทั้งหมดที่อาจทำให้แอปพลิเคชันขัดข้อง (อย่างไรก็ตาม 2 ข้อนี้ไม่เกี่ยวข้องกับฉันและดูเหมือนจะไม่หรืออย่างน้อยก็ไม่ควรผิดพลาด แต่ฉันอาจจะผิด) ฉันสงสัยว่าข้อผิดพลาดเหล่านี้ปรากฏขึ้นเนื่องจากการหมดเวลาการหมดเวลาหรือการยกเลิกอย่างชัดเจนจากไคลเอนต์ แต่ฉันคาดว่าจะได้รับการปฏิบัติภายในกรอบงาน ASP.NET และไม่เผยแพร่ออกไปนอกข้อยกเว้นที่ไม่สามารถจัดการได้


ในกรณีของฉันข้อยกเว้นเหล่านี้เกิดขึ้นเนื่องจากเบราว์เซอร์ยกเลิกคำขอเมื่อผู้ใช้นำทางไปยัง URL ใหม่
Bates Westmoreland

1
ฉันเห็น. ในกรณีของฉันคำขอจะออกผ่านWinHTTPAPI ไม่ใช่จากเบราว์เซอร์
Gabriel S.

2

ฉันพบรายละเอียดเพิ่มเติมเล็กน้อยเกี่ยวกับข้อผิดพลาดนี้ มี 2 ​​ข้อยกเว้นที่อาจเกิดขึ้นได้:

  1. OperationCanceledException
  2. TaskCanceledException

สิ่งแรกเกิดขึ้นหากการเชื่อมต่อขาดหายไปในขณะที่โค้ดของคุณในคอนโทรลเลอร์ทำงาน (หรืออาจเป็นรหัสระบบบางอย่างเช่นกัน) ในขณะที่สิ่งที่สองเกิดขึ้นหากการเชื่อมต่อหลุดในขณะที่การดำเนินการอยู่ภายในแอตทริบิวต์ (เช่นAuthorizeAttribute)

ดังนั้นวิธีแก้ปัญหาที่ให้มาจะช่วยบรรเทาข้อยกเว้นแรกได้บางส่วน แต่ก็ไม่ได้ช่วยอะไรกับข้อที่สอง ในกรณีหลังนี้TaskCanceledExceptionจะเกิดขึ้นระหว่างการbase.SendAsyncเรียกตัวเองแทนที่จะตั้งค่าโทเค็นการยกเลิกเป็นจริง

ฉันเห็นสองวิธีในการแก้ปัญหาเหล่านี้:

  1. เพียงละเว้นทั้งสองข้อยกเว้นใน global.asax จากนั้นคำถามก็เกิดขึ้นว่าเป็นไปได้หรือไม่ที่จะเพิกเฉยต่อสิ่งที่สำคัญแทน?
  2. ทำการลอง / จับเพิ่มเติมในตัวจัดการ (แม้ว่าจะไม่กันกระสุน + ยังมีความเป็นไปได้TaskCanceledExceptionที่เราจะเพิกเฉยจะเป็นสิ่งที่เราต้องการบันทึก

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

วิธีเดียวที่ฉันพบว่าเราสามารถพยายามระบุข้อยกเว้นที่ไม่ถูกต้องคือการตรวจสอบว่า stacktrace มีสิ่งของ Asp.Net หรือไม่ ดูเหมือนจะไม่แข็งแกร่งมากนัก

ป.ล. นี่คือวิธีที่ฉันกรองข้อผิดพลาดเหล่านี้ออก:

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

1
ในโค้ดที่คุณแนะนำคุณสร้างresponseตัวแปรภายในการลองและส่งคืนนอกการลอง ที่ไม่สามารถทำงานได้? คุณใช้ IsAspNetBugException ที่ไหน
Schoof

1
ไม่ใช้ไม่ได้ก็ต้องมีการประกาศนอกบล็อก try / catch แน่นอนและเริ่มต้นด้วยบางอย่างเช่นงานที่เสร็จสมบูรณ์ เป็นเพียงตัวอย่างของการแก้ปัญหาที่กันกระสุนไม่ได้อยู่ดี สำหรับคำถามอื่น ๆ ที่คุณใช้ในตัวจัดการ Global Asax OnError หากคุณไม่ได้ใช้ข้อความนั้นเพื่อบันทึกข้อความของคุณคุณก็ไม่จำเป็นต้องกังวลต่อไป หากคุณเป็นเช่นนั้นนี่คือตัวอย่างวิธีกรอง "ไม่ใช่ข้อผิดพลาด" ออกจากระบบ
Ilya Chernomordik

1

เราได้รับข้อยกเว้นเดียวกันเราพยายามใช้วิธีแก้ปัญหาของ @ dmatson แต่เรายังคงได้รับข้อยกเว้นบางประการ เราจัดการกับมันจนกระทั่งเมื่อไม่นานมานี้ เราสังเกตเห็นบันทึกของ Windows บางรายการเพิ่มขึ้นในอัตราที่น่าตกใจ

ไฟล์ข้อผิดพลาดที่อยู่ใน: C: \ Windows \ System32 \ LogFiles \ HTTPERR

ข้อผิดพลาดส่วนใหญ่เกิดจาก "Timer_ConnectionIdle" ทั้งหมด ฉันค้นหารอบ ๆ และดูเหมือนว่าแม้ว่าการเรียก web api จะเสร็จสิ้น แต่การเชื่อมต่อยังคงอยู่เป็นเวลาสองนาทีหลังจากการเชื่อมต่อเดิม

จากนั้นฉันคิดว่าเราควรพยายามปิดการเชื่อมต่อในการตอบสนองและดูว่าเกิดอะไรขึ้น

ฉันได้เพิ่มresponse.Headers.ConnectionClose = true;ไปยัง SendAsync MessageHandler และจากสิ่งที่ฉันบอกได้ว่าลูกค้ากำลังปิดการเชื่อมต่อและเราไม่พบปัญหาอีกต่อไป

ฉันรู้ว่านี่ไม่ใช่วิธีแก้ปัญหาที่ดีที่สุด แต่ใช้ได้ในกรณีของเรา ฉันค่อนข้างมั่นใจว่าประสิทธิภาพที่ชาญฉลาดนี่ไม่ใช่สิ่งที่คุณต้องการทำหาก API ของคุณได้รับการโทรหลายครั้งจากไคลเอนต์เดียวกันแบบ back-to-back

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.