Observable.Interval未按预期频率更新UI

时间:2017-11-27 13:08:17

标签: c# wpf mvvm system.reactive

我正在尝试模拟时钟的行为。为此,我创建了一个Observable.Interval,在指定的时间间隔之后,用相等的时间量更新属性的值。该值与GUI绑定数据。

问题是:"时钟"运行速度比预期慢,也就是说,时钟值增加一秒需要超过一秒的时间。

所需的时间分辨率越精细(下面的MILLIS_INTERVAL的值),问题就越严重(我测试的值为1,10,100和1000的其他约数,因为TimeSpan.FromMilliseconds记录的最大分辨率为1毫秒。

我希望可观察的回调是异步运行的,并且可以定期触发,可能是并行的,尽管执行时间很长,但似乎频率越大,滞后越多。

视图模型

using System;
using System.ComponentModel;
using System.Reactive.Linq;

namespace IntervalClock
{
    public class ViewModel : INotifyPropertyChanged
    {

        public double TimeSeconds
        {
            get { return _timeSeconds; }
            set
            {
                _timeSeconds = value;
                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("TimeSeconds"));
            }
        }
        double _timeSeconds;


        const double MILLIS_INTERVAL = 10;

        public ViewModel()
        {
            Observable.Interval(TimeSpan.FromMilliseconds(MILLIS_INTERVAL))
                      .Subscribe(token => TimeSeconds += MILLIS_INTERVAL / 1000);
        }


        public event PropertyChangedEventHandler PropertyChanged;
    }
}

主窗口:

<Window x:Class="IntervalClock.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:IntervalClock"
        mc:Ignorable="d">

    <Window.DataContext>
        <local:ViewModel/>
    </Window.DataContext>

    <StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
        <TextBlock Text="{Binding TimeSeconds, StringFormat=N3}" FontSize="30"/>
    </StackPanel>
</Window>

2 个答案:

答案 0 :(得分:1)

计时器似乎在同一个线程上调用OnNext,因此回调不会被执行&#34;异步&#34;。

您可以创建自己的方法来调用线程池线程上的回调。以下是使用System.Timers.Timer类的基本示例:

public static IObservable<long> Interval(TimeSpan interval)
{
    return Observable.Create<long>(observer =>
    {
        long i = 0;
        Timer _timer = new Timer(interval.TotalMilliseconds);
        _timer.Elapsed += (s, e) => observer.OnNext(i++);
        _timer.Start();

        return Disposable.Create(() =>
        {
            _timer.Stop();
            _timer.Dispose();
        });
    });
}

用法(只需调用您自己的Interval方法而不是Observable.Interval):

Interval(TimeSpan.FromMilliseconds(MILLIS_INTERVAL))
                  .Subscribe(token => TimeSeconds += MILLIS_INTERVAL / 1000);

这应该使&#34;回调以异步方式运行,并且可以定期触发,可能是并行触发,尽管需要执行时间,...&#34;。

答案 1 :(得分:1)

Observable.Interval运算符计算运行每个.OnNext之间的时间间隔。因此,如果.OnNext取0.1秒而你的间隔为1.0秒,那么你的有效期为1.1秒。但当然Windows并不是一个实时操作系统,因此它有所不同。您的实际时间可能会有所不同 - 尤其是当您的系统负载不足时。

以下是我将如何做到这一点:

var interval = TimeSpan.FromSeconds(1.0);
Observable
    .Create<long>(o =>
    {
        var sw = Stopwatch.StartNew();
        return
            Observable
                .Timer(TimeSpan.Zero, TimeSpan.FromSeconds(interval.TotalSeconds / 10.0))
                .Select(x => (long)sw.Elapsed.TotalSeconds)
                .DistinctUntilChanged()
                .Subscribe(o);
    })

使用StopWatch可以让您在经过的时间内获得出色的准确度。计时器比interval大约多出10倍,因此它在触发时有+/- 10%的误差,但结果是相同的 - 你有一个非常精确的“秒”值从这个可观测量返回,不是每一秒都准确回归。