Асинхронная обработка ошибок в фильтрах MVC 5

#c# #asp.net-mvc #async-await

#c# #asp.net-mvc #async-ожидание

Вопрос:

Я создал пользовательский атрибут фильтра, расширяющий HandleErrorAttribute , который улавливает все исключения.

Все мое приложение построено на основе асинхронной архитектуры по всему стеку. Это означает, что я каким-то образом должен использовать OnException(ExceptionContext contect) переопределение асинхронно. Что-то вроде этого, было бы так, как я себе это представлял (упрощено для удобства чтения):

 public async override void OnException(ExceptionContext context)
{
    base.OnException(context);
    var logId = await LogException(e, httpContext); //LogException calls an async method and returns the ID of the exception, after it has been saved to the database. I this ID in the filterContext, which is why I need the result of the method
}
  

Однако это привело бы к InvalidOperationException: An asynchronous operation cannot be started at this time. Asynchronous operations may only be started within an asynchronous handler or module or during certain events in the Page lifecycle .

Я не могу просто вызвать .Результат для задачи при синхронном вызове, поскольку это может привести к взаимоблокировкам.

Итак, я предполагаю, что мой вопрос таков: как мне создать фильтр в MVC5, который может регистрировать ошибки асинхронно?

Комментарии:

1. Вы должны быть в состоянии использовать стандарт override без async ключевых слов и await . Они все равно должны быть действительными, несмотря на то, что ваше приложение написано с использованием async , это не должно иметь значения. HandleErrorAttribute.OnException Все равно должно произойти попадание, как и следовало ожидать.

2. @DavidPine Я не могу использовать ключевое слово await без добавления async в метод. И без ключевого слова await я не могу безопасно получить возвращаемое значение метода logException без риска взаимоблокировки

3. А, понятно. Почему LogException должно быть async ?

4. Что ж. Я предполагаю, что это не так, на том же уровне, что ни один метод на самом деле не должен быть асинхронным. Но это обеспечивает наилучшее использование ресурсов сервера, и мне было бы немного грустно создавать набор синхронных интерфейсов, просто чтобы охватить этот конкретный вариант использования.

5. Я хотел бы надеяться, что этот код ведения журнала используется не часто, если у вас есть более серьезные проблемы, о которых стоит беспокоиться. Даже если это связано с вводом-выводом, предполагающим, что вы записываете журналы на диск, пропускная способность вызывает беспокойство только в том случае, если у вас есть тонна запросов, которые все завершаются сбоем. Я хочу сказать, что для регистрации ошибок было бы нормально, чтобы они записывались синхронно.

Ответ №1:

Поскольку обработка ошибок обычно выполняется не горячим кодом, здесь нет проблем с производительностью. Поэтому делайте то, что вам наиболее удобно.

Например, вы можете заблокировать задачу, возвращенную из LogException . Существует несколько способов надежно избежать взаимоблокировки. Простой способ заключается в Task.Run(() => LogException()).Wait(); . Это работает, потому что тело задачи выполняется без контекста синхронизации.

Вы также можете добавить ConfigureAwait(false) внутри LogException , но вы не можете пропустить ни одного места. Это делает это хрупким.

Комментарии:

1. Большое спасибо. Я этого не осознавал. Wait() помог мне избежать взаимоблокировок.

2. Этого не происходит, помогает только запуск. Ожидание, результат и GetResult ведут себя одинаково с точки зрения взаимоблокировки.

Ответ №2:

Я предлагаю вам изменить подход и внедрить собственный ExceptionLogger, потому что работа ExceptionLogger заключается в регистрации исключений

Мы предоставляем две новые пользовательские службы, IExceptionLogger и IExceptionHandler, для регистрации и обработки необработанных исключений. Сервисы очень похожи, с двумя основными отличиями: Мы поддерживаем регистрацию нескольких регистраторов исключений, но только одного обработчика исключений. Всегда вызываются регистраторы исключений, даже если мы собираемся прервать соединение. Обработчики исключений вызываются только тогда, когда мы все еще можем выбрать, какое ответное сообщение отправить. Обе службы предоставляют доступ к контексту исключения, содержащему соответствующую информацию с точки, где было обнаружено исключение, в частности, к HttpRequestMessage, HttpRequestContext, вызванному исключению и источнику исключения.

Вот пример кода

 public class ExceptionLogger : IExceptionLogger
{
    public virtual Task LogAsync(ExceptionLoggerContext context, 
                             CancellationToken cancellationToken)
   {


    return MyLogger.Log(context, cancellationToken);
   }   
}