使用Observable.Interval以恒定帧速率刷新图像比它应该慢

时间:2017-11-22 19:33:01

标签: c# wpf system.reactive

更新

我将问题分解为其核心组件,并在另一个问题中将更短的最小工作示例隔离开来:

Observable.Interval not updating UI with expected frequency

我需要将一系列图像(从文件夹中读取)显示为"电影",以一个固定的,先前已知的帧率。

我在WPF中有一个Image控件,其Source被数据绑定到ViewModel属性Image。然后,我使用Observable.Interval事件源及时更新该图像。对于每个已用时间间隔,系统会调用UpdateImage,这会更新Task.Run()来电中的图像。

我的问题是:当我通过实验增加有效帧速率(取决于实际帧速率以及播放速度)时,播放保持正常速度上升给定值。高于该值,它开始看起来更慢。

我认为这与RaisePropertyChanged电话有关,但我不确定。我尝试使用SubscribeOnDispatcher(或者说它ObserveOnDispatcher?)但是无论如何它都没有效果。

问题是:   - 我做错了什么?   - 我如何调查并解决问题?

更新: 值得一提的是Image调用CreateImageForIndex()的getter,这是一种可能具有非平凡成本的方法。是否可以" async"它?

ViewModel(部分):

    CancellationTokenSource _cancelPlay;
    double _speed;

    public void Play(double speed)
    {
        _speed = speed;

        _cancelPlay = new CancellationTokenSource();
        Observable.Interval(TimeSpan.FromSeconds(1.0 / (Math.Abs(speed) * Exame.Model.Configurações.FrameRate)))
                  .Subscribe(t => ExecuteRun(), _cancelPlay.Token);
    }

    public void Stop()
    {
        _cancelPlay?.Cancel();
    }



    void ExecuteRun()
    {
        Task.Run(() =>
        {
            Index = Math.Max(0, Math.Min(_model.MaxIndex, Index + 1 * Math.Sign(_speed)));
        });
    }

    public int Index
    {
        get { return _model.Index; }
        set
        {
            _model.Index = value;
            RaisePropertyChanged(null); // this tells view to get a new value for `Image` too!
        }
    }

    public ImageSource Image => _model.CreateImageForIndex(Index);

1 个答案:

答案 0 :(得分:0)

我跟随@Enigmativity的提示(在我发布的另一个答案中),在Windows中,计时器的分辨率最多为15ms。

所以我测试了另一种策略:旋转一个带有“当经过时间”状态的循环,类似于游戏循环,用Stopwatch测量时间,现在一切正常。

    public void Play(double speed)
    {
        _speed = speed;

        _cancelPlay?.Cancel();
        _cancelPlay = new CancellationTokenSource();

        Task.Run(() => PlayLoop(_cancelPlay.Token), _cancelPlay.Token);
    }

    void PlayLoop(CancellationToken token)
    {
        var sw = Stopwatch.StartNew();
        double previous = sw.ElapsedMilliseconds;

        double timeInterval = 1000.0 / (Math.Abs(_speed) * _exame.Model.Configurações.FrameRate);

        while (!token.IsCancellationRequested)
        {
            var current = sw.ElapsedMilliseconds;
            var elapsed = current - previous;
            if (elapsed > timeInterval)
            {
                Índice = Math.Max(0, Math.Min(ÍndiceMáximo, Índice + 1 * Math.Sign(_speed)));
                previous = current;
            }
        }
    }