-
Notifications
You must be signed in to change notification settings - Fork 130
Throttling to improve responsiveness
Throttling helps to improve the responsiveness of an application by delaying a method call and skipping all additional calls of this method during the delay.
The Waf Music Manager uses the WPF Slider Control to represent the current position of the playing music file. The Slider
provides the ValueChanged
event to listen for the slider position changes. But this event is raised very often when a user moves the slider. However, repositioning the current playing music file via the MediaPlayer
should not be done multiple times within some milliseconds. Throttling is used so that at maximum every 100 milliseconds the position of the MediaPlayer
is updated.
private readonly ThrottledAction throttledSliderValueChangedAction;
private double lastUserSliderValue;
public PlayerView()
{
throttledSliderValueChangedAction = new ThrottledAction(
ThrottledSliderValueChangedAction, TimeSpan.FromMilliseconds(100));
}
private void PositionSliderValueChanged(object sender,
RoutedPropertyChangedEventArgs<double> e)
{
lastUserSliderValue = e.NewValue;
throttledSliderValueChangedAction.InvokeAccumulated();
}
private void ThrottledSliderValueChangedAction()
{
mediaPlayer.Position = TimeSpan.FromSeconds(lastUserSliderValue);
}
The ThrottledAction class is a simple implementation of throttling. It requires the method which will be called throttled and the delay time. The InvokeAccumulated
method is used to call the specified method delayed. Multiple calls of the InvokeAccumulated
method within the delay time results in just one call of the throttled method.
public class ThrottledAction
{
private readonly TaskScheduler taskScheduler;
private readonly object timerLock = new object();
private readonly Timer timer;
private readonly Action action;
private readonly TimeSpan dueTime;
private volatile bool isRunning;
public ThrottledAction(Action action)
: this(action, TimeSpan.FromMilliseconds(10))
{
}
public ThrottledAction(Action action, TimeSpan dueTime)
{
this.taskScheduler = TaskScheduler
.FromCurrentSynchronizationContext();
this.timer = new Timer(TimerCallback);
this.action = action;
this.dueTime = dueTime;
}
public bool IsRunning { get { return isRunning; } }
public void InvokeAccumulated()
{
lock (timerLock)
{
if (!isRunning)
{
isRunning = true;
timer.Change(dueTime, Timeout.InfiniteTimeSpan);
}
}
}
private void TimerCallback(object state)
{
lock (timerLock)
{
isRunning = false;
}
Task.Factory.StartNew(action, CancellationToken.None,
TaskCreationOptions.DenyChildAttach, taskScheduler);
}
}
The WPF Application Framework (WAF) comes with a more advanced implementation of the ThrottledAction class since version 3.1.
- The method call will run synchronized when a synchronization context exists. Therefore, the method call will run in the Dispatcher UI thread when the ThrottledAction is used in a WPF application.
- It is not possible to pass method arguments within the
InvokeAccumulated
method. This design decision was made because the passed argument values might be outdated at the time when the throttled method will be called. - The implementation does not implement the
IDisposable
interface although the used Timer does. I do not like to implement the IDisposable pattern in all classes that are using the ThrottledAction. As a result the timer will be disposed when the garbage collector collects the ThrottledAction with its Timer instance.
- The Reactive Extensions (Rx) library provides the Observable.Throttle Method
- The WPF Dispatcher knows different priorities. This concept results also in a throttled behavior.
Example: Not every raise of the PropertyChanged event does immediately update the Binding Targets. Binding has its own Dispatcher priorityDataBind
so that updating the target is done delayed.