In light of some of the comments, I should make it clear that this question is about why the TestScheduler is throwing a null-reference exception, not how to get the test to pass. An earlier example assumed that interaction with the TPL was the cause of the problem but I've now discovered that this is not required to trigger the behaviour so I've replaced the code with a simpler test case
I'm having some problems trying to combine the rx-testing 'virtual time' TestScheduler with a background thread. The simplest way I've found to demonstrate the problem is shown at the bottom of the post.
The code is simply running a background thread that subscribes to an observable sequence with a timeout.
The timeout is driven from a TestScheduler and as I advance this on the main thread, a null-reference exception is being generated:
Assert.Fail failed. Virtual time 00:00:00.1394720, exception System.NullReferenceException: Object reference not set to an instance of an object. at System.Reactive.Concurrency.VirtualTimeScheduler
2.GetNext() at System.Reactive.Concurrency.VirtualTimeSchedulerBase
2.AdvanceTo(TAbsolute time) at System.Reactive.Concurrency.VirtualTimeSchedulerBase`2.AdvanceBy(TRelative time) at UnitTestProject1.UnitTest1.d__6.MoveNext() in .......\UnitTest1.cs:line
The failure appears to be insensitive to the exact type of IObservable used but does seem to be dependent on the presence of the Timeout selector. Interestingly though, the test typically fails at a virtual time well before the timeout is due to fire.
Running the same code as a console application also appears to work although the problem appears to be quite easily perturbed (possibly a race-condition) so this may be a red-herring.
Further research and comments imply strongly that the behaviour is due to a race condition that occurs when the scheduler is advanced as the background thread schedules its timeout action
Thanks in advance for any light you can shed on this...
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
}
}
}
}