Observable.Interval 未以预期频率更新 UI [英] Observable.Interval not updating UI with expected frequency

查看:66
本文介绍了Observable.Interval 未以预期频率更新 UI的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

问题是:时钟"的运行速度比预期的要慢,即时钟值增加一秒需要一秒以上的时间.

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

我希望 observable 回调异步运行,并定期触发,可能并行触发,尽管执行需要时间,但似乎频率越大,滞后越多.

视图模型

<前>使用系统;使用 System.ComponentModel;使用 System.Reactive.Linq;命名空间间隔时钟{公共类 ViewModel:INotifyPropertyChanged{公共双时间秒{得到 { 返回 _timeSeconds;}放{_timeSeconds = 值;PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("TimeSeconds"));}}双 _timeSeconds;const double MILLIS_INTERVAL = 10;公共视图模型(){Observable.Interval(TimeSpan.FromMilliseconds(MILLIS_INTERVAL)).Subscribe(token => TimeSeconds += MILLIS_INTERVAL/1000);}公共事件 PropertyChangedEventHandler PropertyChanged;}}

主窗口:

<Window.DataContext><local:ViewModel/></Window.DataContext><StackPanel Horizo​​ntalAlignment="Center" VerticalAlignment="Center"><TextBlock Text="{Binding TimeSeconds, StringFormat=N3}" FontSize="30"/></StackPanel></窗口>

解决方案

Observable.Interval 运算符乘以运行每个 .OnNext 以触发的间隔.因此,如果 .OnNext 需要 0.1 秒并且您的间隔为 1.0 秒,那么您的有效周期为 1.1 秒.但当然 Windows 不是实时操作系统,所以它有漂移.您的实际时间可能相差更大 - 特别是当您的系统负载不足时.

这是我的做法:

var interval = TimeSpan.FromSeconds(1.0);可观察的.Create(o =>{var sw = Stopwatch.StartNew();返回可观察的.Timer(TimeSpan.Zero, TimeSpan.FromSeconds(interval.TotalSeconds/10.0)).Select(x => (long)sw.Elapsed.TotalSeconds).DistinctUntilChanged().订阅(o);})

StopWatch 的使用为您提供出色的时间精确度.计时器的触发频率大约是 interval 的 10 倍,因此它在触发时有 +/- 10% 的误差,但结果是相同的 - 您返回了一个非常准确的秒"值从这个 observable 来看,只是不是每一秒都精确返回.

I am trying to simulate the behavior of a clock. For that, I create an Observable.Interval that, after a specified time interval, updates the value of a property by the equivalent amount of time. The value is data-bound to the GUI.

The problem is: the "clock" runs quite slower than expected, that is, it takes longer than one second for the clock value to increase one second.

The finer the desired time resolution (the value of MILLIS_INTERVAL below), the worse the problem gets (I tested it with values of 1, 10, 100 and other submultiples of 1000, since TimeSpan.FromMilliseconds is documented to have a maximum resolution of 1ms).

I would expect the observable callback to run asynchronously, and be fired at regular intervals, possibly in parallel, despite the time it takes to execute, but it seems that the larger the frequency, the more it lags.

ViewModel

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;
    }
}

MainWindow:

<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>

解决方案

The Observable.Interval operator times the interval between running each .OnNext to fire. So if the .OnNext take 0.1s and your interval is 1.0s then you have an effective period of 1.1s. But of course Windows isn't a real-time operating system so it has drift. Your actual time can vary even more - especially when your system is under load.

Here's how I would do this:

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);
    })

The use of the StopWatch gives you excellent accuracy for the time elapsed. The timer fires roughly 10x more often than the interval so that it has a +/- 10% error on when it fires, but the result is the same - you have a very accurate "seconds" value returned from this observable, just not return precisely on every second.

这篇关于Observable.Interval 未以预期频率更新 UI的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆