ASP.NET Web API OperationCanceledException khi trình duyệt hủy yêu cầu


119

Khi người dùng tải một trang, nó thực hiện một hoặc nhiều yêu cầu ajax, yêu cầu này sẽ đánh vào bộ điều khiển ASP.NET Web API 2. Nếu người dùng điều hướng đến một trang khác, trước khi các yêu cầu ajax này hoàn tất, trình duyệt sẽ hủy các yêu cầu này. Sau đó ELMAH HttpModule của chúng tôi ghi lại hai lỗi cho mỗi yêu cầu bị hủy:

Lỗi 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()

Lỗi 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)

Nhìn vào stacktrace, tôi thấy rằng ngoại lệ đang được đưa ra từ đây: https://github.com/ASP-NET-MVC/aspnetwebstack/blob/master/src/System.Web.Http.WebHost/HttpControllerHandler.cs# L413

Câu hỏi của tôi là: Làm cách nào để xử lý và bỏ qua những ngoại lệ này?

Nó dường như nằm ngoài mã người dùng ...

Ghi chú:

  • Tôi đang sử dụng ASP.NET Web API 2
  • Các điểm cuối API Web là sự kết hợp của các phương thức không đồng bộ và không đồng bộ.
  • Bất kể tôi thêm ghi nhật ký lỗi ở đâu, tôi không thể bắt được ngoại lệ trong mã người dùng

1
Chúng tôi đã thấy các ngoại lệ tương tự (TaskCanceledException và OperationCanceledException) với phiên bản hiện tại của thư viện Katana.
David McClelland

Tôi đã tìm thấy thêm một số chi tiết về thời điểm cả hai ngoại lệ xảy ra và phát hiện ra rằng cách giải quyết này chỉ hoạt động chống lại một trong số chúng. Dưới đây là một số chi tiết: stackoverflow.com/questions/22157596/…
Ilya Chernomordik

Câu trả lời:


78

Đây là một lỗi trong ASP.NET Web API 2 và thật không may, tôi không nghĩ rằng có cách giải quyết nào sẽ luôn thành công. Chúng tôi đã gửi một lỗi để sửa nó cho phía chúng tôi.

Cuối cùng, vấn đề là chúng tôi trả lại một tác vụ đã bị hủy cho ASP.NET trong trường hợp này và ASP.NET xử lý một tác vụ bị hủy giống như một ngoại lệ chưa được xử lý (nó ghi lại sự cố trong Nhật ký sự kiện ứng dụng).

Trong thời gian chờ đợi, bạn có thể thử một cái gì đó như mã bên dưới. Nó thêm một trình xử lý thư cấp cao nhất để xóa nội dung khi mã thông báo hủy kích hoạt. Nếu phản hồi không có nội dung, lỗi sẽ không được kích hoạt. Vẫn có một khả năng nhỏ điều đó có thể xảy ra, vì máy khách có thể ngắt kết nối ngay sau khi trình xử lý thông báo kiểm tra mã thông báo hủy nhưng trước khi mã API Web cấp cao hơn thực hiện kiểm tra tương tự. Nhưng tôi nghĩ nó sẽ giúp ích trong hầu hết các trường hợp.

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
Như một bản cập nhật, điều này nắm bắt một số yêu cầu. Chúng tôi vẫn thấy khá nhiều trong nhật ký của mình. Cảm ơn vì công việc xung quanh. Mong được sửa chữa.
Bates Westmoreland

2
@KiranChalla - Tôi có thể xác nhận rằng bản nâng cấp lên 5.2.2 vẫn còn những lỗi này.
KnightFox

2
Tôi vẫn nhận được lỗi. Tôi đã sử dụng gợi ý ở trên, bất kỳ manh mối nào khác.
M2012

3
Khi tôi thử đề xuất ở trên, tôi vẫn nhận được Ngoại lệ khi yêu cầu đã bị hủy ngay cả trước khi nó được chuyển đến SendAsync(bạn có thể mô phỏng điều này bằng cách nhấn giữ F5trong trình duyệt trên một url yêu cầu Api của bạn. Tôi đã giải quyết vấn đề này bằng cách cũng thêm if (cancellationToken.IsCancellationRequested)séc bên trên cuộc gọi tới SendAsync. Giờ đây, các ngoại lệ không còn hiển thị khi trình duyệt nhanh chóng hủy yêu cầu.
seangwright 4/1017

2
Tôi đã tìm thấy thêm một số chi tiết về thời điểm cả hai ngoại lệ xảy ra và phát hiện ra rằng cách giải quyết này chỉ hoạt động chống lại một trong số chúng. Dưới đây là một số chi tiết: stackoverflow.com/a/51514604/1671558
Ilya Chernomordik

17

Khi triển khai trình ghi ngoại lệ cho WebApi, bạn nên mở rộng System.Web.Http.ExceptionHandling.ExceptionLoggerlớp hơn là tạo một ExceptionFilter. Nội bộ WebApi sẽ không gọi phương thức Nhật ký của ExceptionLoggers cho các yêu cầu bị hủy (tuy nhiên, các bộ lọc ngoại lệ sẽ lấy chúng). Đây là do thiết kế.

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

Dường như vấn đề với phương pháp này là lỗi vẫn bật lên trong việc xử lý lỗi Global.asax ... tổ chức sự kiện dù nó không được gửi đến các xử lý ngoại lệ
Ilya Chernomordik

14

Đây là một giải pháp khác cho vấn đề này. Chỉ cần thêm phần mềm trung gian OWIN tùy chỉnh ở đầu đường dẫn OWIN bắt được OperationCanceledException:

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

2
Tôi đã nhận được lỗi này chủ yếu từ bối cảnh OWIN và điều này một mục tiêu tốt hơn
NitinSingh

3

Bạn có thể thử thay đổi hành vi xử lý ngoại lệ tác vụ TPL mặc định thông qua web.config:

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

Sau đó, có một staticlớp (với một hàm statictạo) trong ứng dụng web của bạn, lớp này sẽ xử lý AppDomain.UnhandledException.

Tuy nhiên, có vẻ như ngoại lệ này đang thực sự được xử lý ở đâu đó bên trong thời gian chạy API Web ASP.NET , trước khi bạn thậm chí có cơ hội xử lý nó bằng mã của mình.

Trong trường hợp này, bạn có thể nắm bắt nó như một ngoại lệ cơ hội đầu tiên, với AppDomain.CurrentDomain.FirstChanceException, đây là cách thực hiện . Tôi hiểu đây có thể không phải là thứ bạn đang tìm kiếm.


1
Cả hai đều không cho phép tôi xử lý ngoại lệ.
Bates Westmoreland

@BatesWestmoreland, thậm chí FirstChanceExceptionkhông? Bạn đã thử xử lý nó bằng một lớp tĩnh tồn tại trên các yêu cầu HTTP chưa?
mũi,

2
Vấn đề tôi đang cố gắng giải quyết để nắm bắt, và bỏ qua những ngoại lệ này. Sử dụng AppDomain.UnhandledExceptionhoặc AppDomain.CurrentDomain.FirstChanceExceptioncó thể cho phép tôi kiểm tra ngoại lệ, nhưng không bắt và bỏ qua. Tôi không thấy cách nào để đánh dấu các ngoại lệ này là được xử lý bằng cách sử dụng một trong hai cách tiếp cận này. Đúng nếu tôi đã sai lầm.
Bates Westmoreland

2

Đôi khi tôi nhận được 2 ngoại lệ giống nhau trong ứng dụng Web API 2 của mình, tuy nhiên tôi có thể bắt chúng bằng Application_Errorphương pháp trong Global.asax.csvà sử dụng bộ lọc ngoại lệ chung .

Tuy nhiên, điều buồn cười là tôi không muốn mắc phải những trường hợp ngoại lệ này, vì tôi luôn ghi lại tất cả các trường hợp ngoại lệ không được xử lý có thể làm hỏng ứng dụng (tuy nhiên, 2 trường hợp này không liên quan đến tôi và dường như không hoặc ít nhất không nên bị lỗi nó, nhưng tôi có thể sai). Tôi nghi ngờ những lỗi này xuất hiện do một số lỗi hết thời gian chờ hoặc sự hủy bỏ rõ ràng từ máy khách, nhưng tôi đã mong đợi chúng được xử lý bên trong khung ASP.NET và không được lan truyền ra bên ngoài nó như là các ngoại lệ không được xử lý.


Trong trường hợp của tôi, những ngoại lệ này xảy ra bởi vì trình duyệt hủy yêu cầu khi người dùng điều hướng đến một url mới.
Bates Westmoreland

1
Tôi hiểu rồi. Trong trường hợp của tôi, các yêu cầu được đưa ra thông qua WinHTTPAPI, không phải từ trình duyệt.
Gabriel S.

2

Tôi đã tìm thấy thêm một chút chi tiết về lỗi này. Có 2 trường hợp ngoại lệ có thể xảy ra:

  1. OperationCanceledException
  2. TaskCanceledException

Điều đầu tiên xảy ra nếu kết nối bị ngắt trong khi mã của bạn trong bộ điều khiển thực thi (hoặc có thể là một số mã hệ thống xung quanh đó). Trong khi điều thứ hai xảy ra nếu kết nối bị ngắt trong khi thực thi bên trong một thuộc tính (ví dụ AuthorizeAttribute:).

Vì vậy, giải pháp được cung cấp sẽ giúp giảm thiểu một phần ngoại lệ đầu tiên, nó không giúp được gì cho ngoại lệ thứ hai. Trong trường hợp thứ hai, TaskCanceledExceptionxảy ra trongbase.SendAsync chính cuộc gọi thay vì mã thông báo hủy được đặt thành true.

Tôi có thể thấy hai cách giải quyết sau:

  1. Chỉ cần bỏ qua cả hai ngoại lệ trong global.asax. Sau đó, đến câu hỏi liệu có thể đột ngột bỏ qua điều gì đó quan trọng không?
  2. Thực hiện thêm một lần thử / bắt trong trình xử lý (mặc dù nó không chống đạn + vẫn có khả năng TaskCanceledExceptionchúng tôi bỏ qua sẽ là một cái chúng tôi muốn ghi lại.

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

Cách duy nhất tôi tìm ra để chúng ta có thể cố gắng xác định các ngoại lệ sai là kiểm tra xem stacktrace có chứa một số nội dung Asp.Net hay không. Mặc dù có vẻ không mạnh mẽ lắm.

Tái bút Đây là cách tôi lọc những lỗi này:

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

1
Trong mã đề xuất của bạn, bạn tạo responsebiến bên trong lần thử và trả lại bên ngoài lần thử. Điều đó không thể có thể làm việc có thể nó? Ngoài ra, bạn sử dụng IsAspNetBugException ở đâu?
Schoof

1
Không, điều đó không thể hoạt động, tất nhiên nó chỉ phải được khai báo bên ngoài khối try / catch và được khởi tạo với một cái gì đó như nhiệm vụ đã hoàn thành. Dù sao thì đó cũng chỉ là một ví dụ về giải pháp không chống đạn. Đối với câu hỏi khác, bạn sử dụng nó trong trình xử lý Global.Asax OnError. Nếu bạn không sử dụng cái đó để ghi tin nhắn của mình, bạn cũng không cần phải lo lắng. Nếu bạn làm vậy, đây là một ví dụ về cách bạn lọc ra "không phải lỗi" khỏi hệ thống.
Ilya Chernomordik

1

Chúng tôi đã nhận được cùng một ngoại lệ, chúng tôi đã cố gắng sử dụng cách giải quyết của @ dmatson, nhưng chúng tôi vẫn gặp phải một số ngoại lệ. Chúng tôi đã xử lý nó cho đến gần đây. Chúng tôi nhận thấy một số nhật ký Windows đang phát triển với tốc độ đáng báo động.

Tệp lỗi nằm trong: C: \ Windows \ System32 \ LogFiles \ HTTPERR

Hầu hết các lỗi đều do "Timer_ConnectionIdle". Tôi đã tìm kiếm xung quanh và có vẻ như mặc dù cuộc gọi api web đã kết thúc, kết nối vẫn tồn tại trong hai phút sau kết nối ban đầu.

Sau đó, tôi nghĩ rằng chúng ta nên thử đóng kết nối trong phản hồi và xem điều gì sẽ xảy ra.

Tôi đã thêm vào response.Headers.ConnectionClose = true;SendAsync MessageHandler và từ những gì tôi có thể nói với khách hàng đang đóng các kết nối và chúng tôi không gặp sự cố nữa.

Tôi biết đây không phải là giải pháp tốt nhất, nhưng nó hoạt động trong trường hợp của chúng tôi. Tôi cũng khá chắc chắn về hiệu suất, đây không phải là điều bạn muốn làm nếu API của bạn nhận được nhiều cuộc gọi từ cùng một ứng dụng khách.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.