В свете некоторых комментариев я должен четко указать, что этот вопрос связан с тем, почему TestScheduler выбрасывает исключение с использованием NULL-ссылки, а не как пройти тест. В более раннем примере предполагалось, что причиной возникновения проблемы является взаимодействие с TPL, но теперь я обнаружил, что это не требуется для запуска поведения, поэтому я заменил код более простым тестовым примером
У меня возникли проблемы с попыткой комбинировать тестовое тестирование rx-test "Virtual Time" с фоновым потоком. Самый простой способ, который я нашел, чтобы продемонстрировать проблему, показан в нижней части сообщения.
Код просто запускает фоновый поток, который подписывается на наблюдаемую последовательность с таймаутом.
Тайм-аут запускается из TestScheduler, и по мере того, как я продвигаю это по основному потоку, генерируется исключение с нулевой ссылкой:
Ошибка Assert.Fail. Виртуальное время 00: 00: 00.1394720, exception System.NullReferenceException: Ссылка на объект не установлена в экземпляр объекта. в System.Reactive.Concurrency.VirtualTimeScheduler
2.GetNext() at System.Reactive.Concurrency.VirtualTimeSchedulerBase
2.AdvanceTo(TAbsolute time) в System.Reactive.Concurrency.VirtualTimeSchedulerBase'2.AdvanceBy (время траления времени) в UnitTestProject1.UnitTest1.d__6.MoveNext() в....... \UnitTest1.cs: строка
Ошибка, как представляется, нечувствительна к точному типу используемого IObservable, но, похоже, зависит от наличия селектора тайм-аута. Интересно, однако, что тест обычно терпит неудачу в виртуальное время задолго до истечения таймаута из-за пожара.
Выполнение того же кода, что и консольное приложение, также работает, хотя проблема, похоже, довольно легко встречается (возможно, состояние гонки), поэтому это может быть красно-селедка.
Дальнейшие исследования и комментарии сильно указывают на то, что поведение связано с состоянием гонки, которое возникает, когда планировщик продвигается, когда фоновый поток назначает свое действие таймаута
Спасибо заранее за любой свет, который вы можете пролить на это...
using Microsoft.Reactive.Testing;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using System.Reactive.Linq;
using System.Threading;
namespace UnitTestProject1
{
[TestClass]
public class UnitTest1
{
[TestMethod]
public void TestBackgroundThreadWithTestScheduler()
{
var scheduler = new TestScheduler();
var seq = Observable.Never<string>();
bool subscribed = false;
ThreadPool.QueueUserWorkItem(_ =>
{
Thread.Sleep(100); //wait a bit to give main thread chance to start advancing scheduler
seq.Timeout(TimeSpan.FromSeconds(10), scheduler)
.Subscribe(s => {/*never called*/});
subscribed = true; //signal we're subscribed
});
//---- Uncommenting this line to avoid the race condition appears to fix the test ----
//while (!subscribed) Thread.Yield();
//Advance the scheduer in small increments to maximise our chances of hitting the race
var watch = scheduler.StartStopwatch();
try
{
while (watch.Elapsed < TimeSpan.FromSeconds(20)) scheduler.AdvanceBy(10);
}
//NullReference is thrown unexpectedly
catch (NullReferenceException ex)
{
Assert.Fail("Virtual time {0}, exception {1}", watch.Elapsed, ex);
}
catch (TimeoutException)
{
//desired result is a TimeoutException so this is a test pass
}
}
}
}
Эта строка в stacktrace System.Reactive.Concurrency.VirtualTimeScheduler2.GetNext()
- это ключ к тому, что ошибка действительно заключается в том, что вы получаете доступ к планировщику из 2 потоков (TestScheduler не является потокобезопасным, в настоящее время существует ошибка в TestScheduler
при доступе к это из нескольких потоков).
Ваш первый комментарий, скорее всего, правильный: фоновая задача добавляет планировщик так же, как ваша другая задача продвигает планировщик.
Попробуйте добавить блокировку вокруг операторов, которые обращаются к планировщику, и посмотреть, разрешает ли это вашу проблему.
Реальное решение, конечно же, состоит в том, чтобы иметь фоновый сигнал задачи, как только он настроился, и перед тем, как продолжить, перед тем, как продолжить тест, подождите. Потому что, даже если у TestScheduler
не было своей ошибки, в вашем тесте есть условие гонки, которое тестовый поток может завершить до того, как фоновый поток когда-либо будет подписываться на наблюдаемый.
TestScheduler
должен быть потокобезопасным; NRE - это ошибка . Кроме того, SynchronizationContext
не имеет к этому никакого отношения; MSTest не предоставляет SynchronizationContext
.
Task
в переменнойtask
никогда не потерпит неудачу - ее задача - просто запустить другую задачу асинхронно? Кроме того, вы хотели продвигать планировщик испытаний на 10 тиков за раз? Если вы просто хотите проверить тайм-аут задачи, это достаточно просто - но я буду ждать разъяснений.