Here is a custom Progress<T>
implementation that enforces a minimum interval policy between consecutive progress reports. When a report message is emitted, it initiates a period of silence during which all subsequent report messages are dropped (ignored), except from the last one. The last report message of each period is buffered and emitted when the period ends, and then a new silent period is initiated etc. The duration of each silent period is configurable (dueTime
argument).
public class ThrottledProgress<T> : Progress<T>
{
private readonly TimeSpan _dueTime;
private readonly object _locker = new object();
private (T Value, bool HasValue) _current;
private Task _task;
public ThrottledProgress(Action<T> handler, TimeSpan dueTime) : base(handler)
{
if (dueTime < TimeSpan.Zero || dueTime.TotalMilliseconds > Int32.MaxValue)
throw new ArgumentOutOfRangeException(nameof(dueTime));
_dueTime = dueTime;
}
protected override void OnReport(T value)
{
lock (_locker)
{
if (_task == null)
{
base.OnReport(value);
_task = Task.Run(async () =>
{
while (true)
{
await Task.Delay(_dueTime);
lock (_locker)
{
if (_current.HasValue)
{
base.OnReport(_current.Value);
_current = (default, false);
}
else
{
_task = null;
break;
}
}
}
});
}
else
{
_current = (value, true);
}
}
}
public void Flush()
{
lock (_locker)
{
if (_current.HasValue)
{
base.OnReport(_current.Value);
_current = (default, false);
}
}
}
}
Usage example, based on code posted in a recent duplicate question:
async void Button_Click(object sender, RoutedEventArgs e)
{
_cts = new CancellationTokenSource();
var progress = new ThrottledProgress<string>(msg => TextBox.Text += msg,
TimeSpan.FromMilliseconds(50));
var tasks = Enumerable.Range(1, 10)
.Select(i => Task.Run(() => Worker(i, _cts.Token, progress)));
await Task.WhenAll(tasks);
progress.Flush();
}
The Flush
method can be called after the completion of the asynchronous operation, so that any progress message that may still be buffered and scheduled for future emission (presumably the last progress message), to be emitted immediately.