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 timer seems to call
OnNext
on the same thread so the callback isn't being executed "asynchronously".You could create your own method that invokes the callback on a thread pool thread. Here is a basic example using the
System.Timers.Timer
class:Usage (just call your own
Interval
method instead ofObservable.Interval
):This should make the "callback run asynchronously, and be fired at regular intervals, possibly in parallel, despite the time it takes to execute, ...".
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:
The use of the
StopWatch
gives you excellent accuracy for the time elapsed. The timer fires roughly 10x more often than theinterval
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.