5

У меня есть метод расширения C#, который можно использовать с задачами, чтобы удостовериться, что выбрасываемые исключения были соблюдены как минимум, чтобы не сбой процесса хостинга. В .NET4.5 поведение немного изменилось, поэтому этого не произойдет, однако незаметное событие исключения все еще запущено. Моя задача здесь - написать тест, чтобы доказать, что метод расширения работает. Я использую NUnit Test Framework, а ReSharper - тестовый бегун.Тест для UnObserved Исключения

Я попытался:

var wasUnobservedException = false; 
TaskScheduler.UnobservedTaskException += (s, args) => wasUnobservedException = true; 
var res = TaskEx.Run(() => 
          { 
           throw new NaiveTimeoutException(); 
           return new DateTime?(); 
          }); 
GC.Collect(); 
GC.WaitForPendingFinalizers(); 
Assert.IsTrue(wasUnobservedException); 

Тест всегда терпит неудачу на Assert.IsTrue. Когда я запускаю этот тест вручную, в чем-то вроде LINQPad, я получаю ожидаемое поведение wasUnobservedException, возвращающееся как true.

Я угадываю, что тестовая среда улавливает исключение и наблюдает за ней так, что TaskScheduler.UnobservedTaskException никогда не запускается.

Я попытался изменить код следующим образом:

var wasUnobservedException = false; 
TaskScheduler.UnobservedTaskException += (s, args) => wasUnobservedException = true; 
var res = TaskEx.Run(async() => 
          { 
           await TaskEx.Delay(5000).WithTimeout(1000).Wait(); 
           return new DateTime?(); 
          }); 
GC.Collect(); 
GC.WaitForPendingFinalizers(); 
Assert.IsTrue(wasUnobservedException); 

попытку я сделал в этом коде был вызвать задачу, чтобы получить GC'd перед было брошено исключение, так что финализации увидят незатронутое исключение. Однако это привело к тому же отказу, описанному выше.

Действительно ли какой-либо обработчик исключений связан с тестовой платформой? Если да, есть ли способ обойти это? Или я просто полностью возился с чем-то, и есть лучший/более простой способ сделать это?

ответ

4

Я вижу несколько проблем с этим подходом.

Во-первых, есть определенное состояние гонки. Когда TaskEx.Run возвращает задачу, она просто поставила в очередь запрос к пулу потоков; задача еще не завершена.

Во-вторых, вы столкнулись с некоторыми деталями сборщика мусора. При компиляции в debug - и действительно, всякий раз, когда компилятор чувствует себя так - времена жизни локальных переменных (то есть, res) расширяются до конца метода.

С этими двумя проблемами в виду, я был в состоянии получить следующий код, чтобы пройти:

var wasUnobservedException = false; 
TaskScheduler.UnobservedTaskException += (s, args) => wasUnobservedException = true; 
var res = Task.Run(() => 
{ 
    throw new NotImplementedException(); 
    return new DateTime?(); 
}); 
((IAsyncResult)res).AsyncWaitHandle.WaitOne(); // Wait for the task to complete 
res = null; // Allow the task to be GC'ed 
GC.Collect(); 
GC.WaitForPendingFinalizers(); 
Assert.IsTrue(wasUnobservedException); 

Однако, есть еще две проблемы:

Там по-прежнему (технически) состояние гонки , Хотя UnobservedTaskException поднят в результате задания финализатора задачи, нет гарантии, что AFAIK поднял от задачи финализатора. В настоящее время это похоже, но это кажется мне очень неустойчивым решением (учитывая, насколько ограничены финализаторы , предположительно). Итак, в будущей версии фреймворка я не был бы слишком удивлен, узнав, что финализатор просто ставит в очередь UnobservedTaskException пулу потоков, а не выполняет его напрямую. И в этом случае вы больше не можете зависеть от того, что событие было обработано к моменту завершения задачи (подразумеваемое предположение, сделанное вышеприведенным кодом).

Существует также проблема изменения глобального состояния (UnobservedTaskException) в рамках единичного теста.

Принимая обе эти проблемы во внимание, я в конечном итоге с:

var mre = new ManualResetEvent(initialState: false); 
EventHandler<UnobservedTaskExceptionEventArgs> subscription = (s, args) => mre.Set(); 
TaskScheduler.UnobservedTaskException += subscription; 
try 
{ 
    var res = Task.Run(() => 
    { 
     throw new NotImplementedException(); 
     return new DateTime?(); 
    }); 
    ((IAsyncResult)res).AsyncWaitHandle.WaitOne(); // Wait for the task to complete 
    res = null; // Allow the task to be GC'ed 
    GC.Collect(); 
    GC.WaitForPendingFinalizers(); 
    if (!mre.WaitOne(10000)) 
     Assert.Fail(); 
} 
finally 
{ 
    TaskScheduler.UnobservedTaskException -= subscription; 
} 

Который также проходит, но имеет довольно сомнительную ценность, учитывая ее сложность.

+0

У меня не было возможности реализовать этот ответ и попробовать его, но он пока выглядит хорошо. –

+0

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

+1

Это не работает для меня. Установлены NUnit 2.6.4 и .NET 4.6 и тестовый таргетинг на проект 4.5.1. Событие просто никогда не поднималось. Я пробовал много разных вариантов без успеха. – angularsen

Смежные вопросы