【问题标题】:How to calculate an ETA for a LINQ pipeline to complete?如何计算 LINQ 管道的 ETA 以完成?
【发布时间】:2018-01-20 23:35:00
【问题描述】:

我想通知用户完成一项操作的预计剩余时间。长操作发生在这样的序列中:

var processedItems = items.Select(x => Process(x));

每个Process(x) 调用可能需要几秒钟才能完成。

我想知道一种简单而干净的方法来动态估计剩余的可枚举值。

也许使用System.Reactive

【问题讨论】:

  • 虽然IEnumerable 没有Count 属性,但实际上它可以永远存在(例如,想象一个网络流)
  • 而 System.Reactive 实际上只是将 pull 模型更改为 push 模型,这根本不会改变根本问题。
  • 好的,你看到了问题。您是否知道任何机制来“采样”长流程的演变以提供 ETA?
  • 但是如果你不知道你循环了多少项目,你就无法预测它什么时候结束。

标签: c# .net linq time system.reactive


【解决方案1】:

首先,IEnumerable<T> 无法做到这一点,因为无法获取元素的数量。为此,您应该使用任何实现ICollection<T> 的东西,这样您就可以获得number of items

其次,您不能真正使用现有的Select 方法(当然也不是没有一些黑客),但您可以编写自己的方法。这是我敲出的东西,它将在投影期间为列表中的每个元素调用一个动作。

第一个类来保存当前进度的详细信息。

public class SelectProgress
{
    public decimal Percentage { get; set; }
    public TimeSpan TimeTaken { get; set; }
    public TimeSpan EstimatedTotalTime { get; set; }
}

还有自定义的Select 方法:

public static IEnumerable<TResult> Select<TSource, TResult>(
    this ICollection<TSource> source, 
    Func<TSource, TResult> selector, 
    Action<SelectProgress> timeRemaining)
{
    Stopwatch timer = new Stopwatch();
    timer.Start();
    var counter = 0;
    foreach (var element in source)
    {
        yield return selector(element);
        counter++;
        timeRemaining?.Invoke(new SelectProgress
        {
            Percentage = counter/(decimal)source.Count,
            TimeTaken = timer.Elapsed,
            EstimatedTotalTime = 
                TimeSpan.FromTicks(timer.Elapsed.Ticks/counter * source.Count)
        });
    }
}

然后这样称呼它:

//Let's have a list of numbers to play with
var list = Enumerable.Range(1, 20).ToList();

var results = list.Select(
    i => 
    {
        //Just an artificial delay
        Thread.Sleep(1000);
        //Return the string representation of the number, you know,
        //just something fun to do here really
        return i.ToString();
    }, 
    //Just going to output the values here, but you can choose to do whatever you like
    p => Console.WriteLine(
        $"{p.Percentage:P2}: Taken: {p.TimeTaken}, Total: {p.EstimatedTotalTime}"))
    .ToList();

这段代码会产生类似这样的输出:

5.00%: Time taken: 00:00:01.0007261, Estimated total: 00:00:20.0158420
10.00%: Time taken: 00:00:02.0015503, Estimated total: 00:00:20.0155100
15.00%: Time taken: 00:00:03.0017421, Estimated total: 00:00:20.0116180
<snip>
90.00%: Time taken: 00:00:18.0101580, Estimated total: 00:00:20.0112860
95.00%: Time taken: 00:00:19.0103062, Estimated total: 00:00:20.0108480
100.00%: Time taken: 00:00:20.0107314, Estimated total: 00:00:20.0107320

【讨论】:

  • 太棒了!顺便说一句,我认为秒表中的 Stop 方法应该在实际处理完成后(在 foreach 之后)调用,对吧?
  • 好吧,你可以这样做,但没有意义。无论如何,秒表都会被丢弃。
  • @DavidG - 你的代码中没有任何东西可以处理Stopwatch。我错过了什么吗?
  • @Enigmativity 没有什么可处置的,Stopwatch 没有实现 IDisposable 并且内部根本不使用任何资源。所以当变量超出范围时,它就消失了。
  • @Enigmativity 是的,你是对的。我在机场写了那些 cmets,并没有真正感觉到检查。需要明确的是,Stopwatch 没有非托管资源,因此不需要处置。由于对象的内部机制,不需要调用Stop 方法。我相信这是一个简单的性能计数器包装,只保留几个数字来确定已经过去了多少时间。是的,当对象超出范围时,它会被收集。 :)
猜你喜欢
  • 2021-11-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-04-28
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多