IProgress <t>报告进度的频率</t>

时间:2013-10-29 14:35:07

标签: c# async-await progress

使用IProgress<T>报告进度时,应该是

  • 代码报告进度的责任,将其进度报告限制为“合理”的频率, - 或 -
  • IProgress<T>具体实施的责任是要注意进度报告的频率可能高于合理的报告进展情况。

问题的背景是我有一些代码使用IProgress<T>来报告进度,并以非常高的速度报告进度。我想用UI进度条显示进度。如果我使用提供的Progress<T>实现(将进度发布到UI SyncronizationContext),则会导致UI无响应(即,发送到消息队列的消息太多,用户甚至无法单击对话框中的“取消”按钮。

所以,

  • 我可以通过报告更少来解决这个问题,但如果我有一个IProgress<T>实现只是将进度写入日志文件(并且可以处理高报告频率),那该怎么办呢? -OR -
  • 我可以通过创建我自己的特定IProgress<T>实施来解决这个问题,该实施限制了我处理/报告进度的频率。据推测,此实现将记录非UI线程的最新进度,然后(可能)UI将根据计时器进行更新。

2 个答案:

答案 0 :(得分:13)

写一个限制调用的装饰器。通过这种方式,您可以将限制逻辑与实际报告分开,并且可以将其用于任何其他IProgress<T>实现。

如果要限制进度报告,请使用此装饰器。使用下面的类实例简单地包装您的进度报告器。

我已经把限制逻辑留给了你。您可以将其设置为基于时间,基于呼叫的数量或其他一些标准。

public class ProgressThrottler<T>: IProgress<T> {
    public ProgressThrottler(IProgress<T> progress) {
        _progress = progress ?? throw new ArgumentNullException("progress");
    }

    private readonly IProgress<T> _progress;

    public void Report(T value) {
        // Throttles the amount of calls
        bool reportProgressAfterThrottling = ...;

        if (reportProgressAfterThrottling) {
            _progress.Report(value);
        }
    }
}

答案 1 :(得分:-2)

这是一个自定义的 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);
            }
        }
    }
}

使用示例,基于最近发布的代码 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();
}

可以在异步操作完成后调用 Flush 方法,以便任何可能仍被缓冲并安排以供将来发射(大概是最后一个进度消息)的进度消息立即发射。< /p>