using System;
using System.Diagnostics;
using System.Reactive.Concurrency;
using System.Reactive.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using ReactiveUI;
using ReactiveUI.Fody.Helpers;
namespace MyTests;
[TestClass]
public class ObserveOnTests
{
///
/// The scenario is that there is a business object which updates its properties
/// on background threads, a view model which observes the business object's
/// properties, and a view which binds to the view model. The view must be
/// updated on the UI thread only.
/// The business object has two properties, C1 and C2. The view model observes
/// C1 on the UI thread. If it also observes C2, but on a background thread,
/// the observer for C1 will sometimes run on a background thread.
///
[TestMethod]
//[Ignore("Characterization test")]
[DataRow(true, true)]
[DataRow(true, false)]
[DataRow(false, false)]
public async Task MultipleObserverThreadConfusion(bool observeC2, bool observeC2OnMainThread)
{
// For convenience, construct a thread to be the "UI thread" and point a scheduler at it.
Thread mainThread = null;
using var scheduler = new EventLoopScheduler(ts => mainThread = new Thread(ts) { Name = "EventThread" });
RxApp.MainThreadScheduler = scheduler;
var businessObject = new BusinessObject();
var viewModel = new ViewModel(businessObject, observeC2, observeC2OnMainThread);
var observedCount = 0;
scheduler.Schedule(() =>
{
viewModel.WhenAnyValue(vm => vm.Counter1)
.Subscribe(count =>
{
if (mainThread != Thread.CurrentThread)
Debugger.Break();
Assert.AreSame(mainThread, Thread.CurrentThread, $"Counter1 was updated to {count} on a background thread.");
observedCount = count;
});
});
const int finalCount = 10000;
await Task.Run(() => businessObject.CountTo(finalCount));
while (observedCount < finalCount)
await Task.Delay(10);
}
private class ViewModel : ReactiveObject
{
public ViewModel(BusinessObject component, bool observeC2, bool observeC2OnMainThread)
{
Component = component;
_counter1 = this.WhenAnyValue(vm => vm.Component.Counter)
.ObserveOn(RxApp.MainThreadScheduler)
.ToProperty(this, vm => vm.Counter1);
if (observeC2)
_counter2 = this.WhenAnyValue(vm => vm.Component.Counter)
.ObserveOn(observeC2OnMainThread ? RxApp.MainThreadScheduler : CurrentThreadScheduler.Instance)
.ToProperty(this, vm => vm.Counter2);
else
_counter2 = ObservableAsPropertyHelper.Default();
}
public int Counter1 => _counter1.Value;
private readonly ObservableAsPropertyHelper _counter1;
public int Counter2 => _counter2.Value;
private readonly ObservableAsPropertyHelper _counter2;
private BusinessObject Component { get; }
}
private class BusinessObject : ReactiveObject
{
[Reactive] public int Counter { get; private set; }
// Repeated updates to perform on a background thread.
public void CountTo(int stopCount)
{
while (Counter < stopCount)
{
Counter++;
}
}
}
}