这是一个自定义Progress<T>
实现,它在连续进度报告之间强制执行最小间隔策略。发出报告消息时,它会启动一段静默期,在此期间所有后续报告消息都将被丢弃(忽略),除了最后一条消息。每个周期的最后一条报告消息在周期结束时被缓冲并发出,然后启动一个新的静默期等。每个静默期的持续时间是可配置的(dueTime
参数)。
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);
}
}
}
}
使用示例,基于最近重复问题中发布的代码:
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();
}
可以在异步操作完成后调用该Flush
方法,以便任何可能仍被缓冲并计划将来发送的进度消息(可能是最后一个进度消息)立即发出。