订阅 observable not 运行 on the expected thread/dispatcher

Subscription to an observable not running on the expected thread/dispatcher

最近练习学习ReactiveUI,写了一个简单的timer模块来 创建倒计时 UI 功能。为了保持 UI 响应,我引入了一些多任务代码,如下所示。但是代码没有按预期运行,更具体地说,没有 运行ning 预期 thread/scheduler.

我创建了 timeoutObservable 来生成一系列 TimeSpan 对象,然后我用一个简单的 lambda 表达式订阅了它,该表达式将 属性 绑定到 UI 文本块控件。我使用SubscribeOn(RxApp.MainThreadScheduler)来确保main/dispatcher线程上的订阅代码运行。

WndMainVm.cs

    public class WndMainVm : ReactiveObject
    {
        public WndMainVm()
        {
            ButtonDisplayString = $"Play! (Timeout: {GameTimeout.TotalSeconds}s)";
            StartGameCommand = ReactiveCommand.CreateFromTask(async _ =>
            {
                IsGameStarted = true;
                TimeLeft = GameTimeout;
                var lastRecordTime = DateTime.Now;
                await GameControlInteraction.StartGame.Handle(Unit.Default);
                var timeoutObservable = Observable
                    .Interval(UpdateInterval)
                    .Select(l =>
                    {
                        var newLastRecordTime = DateTime.Now;
                        var newTimeLeft = TimeLeft - (newLastRecordTime - lastRecordTime);
                        lastRecordTime = newLastRecordTime;
                        return newTimeLeft;
                    })
                    .Merge(Observable
                        .Timer(GameTimeout)
                        .Select(l => TimeSpan.Zero))
                    .TakeUntil(ts => ts == TimeSpan.Zero);
                timeoutObservable.
                    SubscribeOn(RxApp.MainThreadScheduler).
                    Subscribe(ts => 
                        TimeLeft = ts);
                await timeoutObservable;
                await GameControlInteraction.StopGame.Handle(Unit.Default);
                IsGameStarted = false;
            }, this.WhenAnyValue(x => x.IsGameStarted).Select(v => !v));

            this.WhenAnyValue(x => x.TimeLeft)
                .Select(v => $"Time left: {v.TotalMilliseconds}ms")
                .ToProperty(this, x => x.TimeoutDisplayString, out _timeoutDisplayString, scheduler: RxApp.MainThreadScheduler);
        }

        private readonly ObservableAsPropertyHelper<string> _timeoutDisplayString;

        public TimeSpan GameTimeout { get; } = TimeSpan.FromSeconds(10);

        public TimeSpan UpdateInterval { get; } = TimeSpan.FromMilliseconds(10);

        [Reactive]
        public bool IsGameStarted { get; set; }

        [Reactive]
        public TimeSpan TimeLeft { get; set; }

        [Reactive] 
        public string ButtonDisplayString { get; set; }

        public string TimeoutDisplayString => _timeoutDisplayString.Value;

        public ReactiveCommand<Unit, Unit> StartGameCommand { get; }
    }

WndMain.cs

    public partial class WndMain : ReactiveWindow<WndMainVm>
    {
        public WndMain()
        {
            InitializeComponent();
            ViewModel = new WndMainVm();
            this.WhenActivated(d =>
            {
                this.OneWayBind(ViewModel, x => x.ButtonDisplayString, x => x.BtnPlayStop.Content).DisposeWith(d);
                this.OneWayBind(ViewModel, x => x.TimeoutDisplayString, x => x.TbkTimeDisplay.Text).DisposeWith(d); \CountDownDisplay

                this.BindCommand(ViewModel, x => x.StartGameCommand, x => x.BtnPlayStop).DisposeWith(d);
            });
        }
    }

然而,当我测试代码时,我发现订阅代码总是运行ning 在线程池的线程上,导致TargetInvocationException。我知道当您尝试从 main/dispatcher 线程以外的线程更改控件的 属性 时会发生这种情况,所以我想知道我的代码是否有问题阻止它在正确的线程上执行.目前我试图通过创建依赖 属性 TimeoutDisplayString 来绕过这个问题并且它工作正常,但这个问题仍然让我感到困惑,我真的很想找出原因。

我对async/await关键字不是很熟悉,所以我猜是我没有正确使用它们,有没有好心人看看,指出我的错误,或者提供一个更好的倒计时解决方案?

通常当我们认为我们应该使用 SubscribeOn 时,我们实际上应该使用 ObserveOn

The SubscribeOn operator is similar, but it instructs the Observable to itself operate on the specified Scheduler, as well as notifying its observers on that Scheduler.

http://reactivex.io/documentation/operators/observeon.html