简体   繁体   English

将 ETA 添加到嵌入式循环序列

[英]Adding an ETA to an embedded loop sequence

UPDATE 1:更新 1:

Some of the solutions offered below seem good.下面提供的一些解决方案似乎不错。 However, I only know the amount of times a loop will iterate after it's parent loop's iterations have been determined.但是,我只知道一个循环在它的父循环的迭代被确定后将迭代的次数。 So I can't count all the iterations beforehand.所以我不能事先计算所有的迭代。

ORIGINAL QUESTION:原始问题:

I have embedded loops in a program similar to this:我在与此类似的程序中嵌入了循环:

Prog1:程序1:

using System;
using System.Threading;

namespace time_remaining_loop_strip
{
    class Program
    {
        static void Main(string[] args)
        {
            var random = new Random();
            Console.Clear();

            // Simulate initiation delay
            Console.WriteLine("initiate");
            Thread.Sleep(random.Next(100, 1000));

            int intCount = random.Next(1, 10);

            for (int loop1 = 0; loop1 <= intCount; loop1++) 
            {
                // Simulate loop1 delay
                Console.WriteLine("\tloop1");
                Thread.Sleep(random.Next(100, 1000));

                for (int loop2 = 0; loop2 <= random.Next(1, 10); loop2++) 
                {
                    // Simulate loop2 delay
                    Console.WriteLine("\t\tloop2");
                    Thread.Sleep(random.Next(100, 1000));

                    for (int loop3 = 0; loop3 <= random.Next(1, 10); loop3++) 
                    {
                        // Simulate loop3 delay
                        Console.WriteLine("\t\t\tloop3");
                        Thread.Sleep(random.Next(100, 1000));

                        for (int loop4 = 0; loop4 <= random.Next(1, 10); loop4++) 
                        {
                            // Simulate loop4 delay
                            Console.WriteLine("\t\t\t\tloop4");
                            Thread.Sleep(random.Next(100, 1000));
                        }
                    }
                }
            }
        }
    }
}

I am trying to display Processing Time Remaining (ETA), so I can see a rough estimate of the amount of time remaining before the loop sequence above completes我正在尝试显示剩余处理时间 (ETA),因此我可以粗略估计在上述循环序列完成之前剩余的时间量

I now have another bit of code which does display an ETA which works fine when the loop is very simplistic:我现在有另一段代码,它确实显示了一个 ETA,当循环非常简单时它可以正常工作:

Prog2:程序2:

using System;
using System.Threading;

namespace time_remaining
{
    class Program
    {
        public static TimeSpan ComputeRemaining((int count, DateTime time) start, (int count, DateTime time) current, int end) =>
            current.count - start.count == 0
            ? TimeSpan.MaxValue
            : TimeSpan.FromSeconds((end - current.count) * current.time.Subtract(start.time).TotalSeconds / (current.count - start.count));

        static void Main(string[] args)
        {
            Console.Clear();

            var random = new Random();
            int Count = random.Next(10, 60);
            DateTime startTime = DateTime.Now;

            for (int i = 0; i <= Count; i++)
            {
                Thread.Sleep(random.Next(100, 2000));
                TimeSpan timeRemaining = ComputeRemaining((0, startTime), (i, DateTime.Now), Count);

                Console.SetCursorPosition(0,0);
                Console.Write("ETA: ");
                Console.Write(String.Format("{0} Days, {1} Hours, {2} Minutes, {3} Seconds", timeRemaining.Days.ToString().PadLeft(3,'0'), timeRemaining.Hours.ToString().PadLeft(2,'0'), timeRemaining.Minutes.ToString().PadLeft(2,'0'), timeRemaining.Seconds.ToString().PadLeft(2,'0')));
            }
        }
    }
}

When I try to combine the ETA aspect of Prog1 into Prog2, it does not seem to work well:当我尝试将 Prog1 的 ETA 方面结合到 Prog2 中时,它似乎效果不佳:

Prog3 = Prog1+Prog2:程序 3 = 程序 1+程序 2:

using System;
using System.Threading;

namespace time_remaining_loop_strip
{
    class Program
    {
        public static TimeSpan ComputeRemaining((int count, DateTime time) start, (int count, DateTime time) current, int end) =>
            current.count - start.count == 0
            ? TimeSpan.MaxValue
            : TimeSpan.FromSeconds((end - current.count) * current.time.Subtract(start.time).TotalSeconds / (current.count - start.count));

        static void Main(string[] args)
        {
            DateTime startTime = DateTime.Now;
            
            var random = new Random();
            Console.Clear();

            // Simulate initiation delay
            //Console.WriteLine("initiate");
            Thread.Sleep(random.Next(100, 1000));

            int intCount = random.Next(1, 10);

            for (int loop1 = 0; loop1 <= intCount; loop1++) 
            {
                // Simulate loop1 delay
                //Console.WriteLine("\tloop1");
                Thread.Sleep(random.Next(100, 1000));

                for (int loop2 = 0; loop2 <= random.Next(1, 10); loop2++) 
                {
                    // Simulate loop2 delay
                    //Console.WriteLine("\t\tloop2");
                    Thread.Sleep(random.Next(100, 1000));

                    for (int loop3 = 0; loop3 <= random.Next(1, 10); loop3++) 
                    {
                        // Simulate loop3 delay
                        //Console.WriteLine("\t\t\tloop3");
                        Thread.Sleep(random.Next(100, 1000));

                        for (int loop4 = 0; loop4 <= random.Next(1, 10); loop4++) 
                        {
                            // Simulate loop4 delay
                            //Console.WriteLine("\t\t\t\tloop4");
                            Thread.Sleep(random.Next(100, 1000));
                        }
                    }
                }

                TimeSpan timeRemaining = ComputeRemaining((0, startTime), (loop1, DateTime.Now), intCount);

                Console.SetCursorPosition(0,0);
                Console.Write("ETA: ");
                Console.Write(String.Format("{0} Days, {1} Hours, {2} Minutes, {3} Seconds", timeRemaining.Days.ToString().PadLeft(3,'0'), timeRemaining.Hours.ToString().PadLeft(2,'0'), timeRemaining.Minutes.ToString().PadLeft(2,'0'), timeRemaining.Seconds.ToString().PadLeft(2,'0')));
            }
        }
    }
}

This doesn't seem to work very well at all.这似乎根本不工作。 It does display an ETA, but it has long delays before it shows anything, because of the way the loop is structured.它确实显示了一个 ETA,但由于循环的结构方式,它在显示任何内容之前有很长的延迟。

How can I update this so the ETA code displays an ETA more accurately and at more predictive intervals, as in every second?我如何更新它,以便 ETA 代码更准确地显示 ETA,并以更具预测性的间隔显示,就像每秒一样?

With the concession that you've just built a simple model of what's actually happening (you have some series of nested process of variable delay and count, which cannot even be determined until runtime), what you're asking for currently is a random number predictor.有了让步,您刚刚构建了一个简单的 model 实际发生的事情(您有一些可变延迟和计数的嵌套过程,甚至在运行时才能确定),您目前要求的是一个随机数预测器。

There will be between n & m cycles (1 & 10 in your example) of between o & p duration (100 & 1000ms), but still random.. well, random quantized into bands.op持续时间(100 和 1000 毫秒)之间会有nm个周期(在您的示例中为 1 和 10),但仍然是随机的......好吧,随机量化成波段。 The way you've written it, this is dice-roll random (a dice has no memory), although in practice it seems more likely the duration of one cycle must somewhat imply the duration of the next, (which is how you've written ComputeRemaining ) and within one band the count of one loop must help with the count of the next.您编写它的方式,这是随机掷骰子(骰子没有记忆),尽管在实践中似乎更有可能一个周期的持续时间必须在某种程度上暗示下一个周期的持续时间,(这就是您的方式写ComputeRemaining )并且在一个带内,一个循环的计数必须有助于计算下一个循环。

So despite its apparent simplicity Prog2 covers our examples.. given a known loop count, where each cycle takes a random duration (which is in fact pick(n,m)^3*pick(o,p) .. but this is still just a random number) - predict the end.因此,尽管Prog2看起来很简单,但它涵盖了我们的示例.. 给定一个已知的循环计数,其中每个循环需要一个随机持续时间(实际上是pick(n,m)^3*pick(o,p) .. 但这仍然是只是一个随机数) - 预测结束。 For reporting purposes, you'll want to refactor this to consider the inner loops as well too, but it's effectively the same process.出于报告目的,您还需要对其进行重构以考虑内部循环,但这实际上是相同的过程。 ( ^3 is a simplification, it's actually a series of independent picks multiplied ) ^3 是一种简化,实际上是一系列独立的选择相乘

Ok, so we don't need to talk about time/delays (I mean.. you clearly want that, but it's just some number that represents the future - a TimeSpan is an long count of x ticks since some time... a finish time is just Now + x*tick ).好的,所以我们不需要谈论时间/延迟(我的意思是..你显然想要那个,但这只是代表未来的一些数字 - TimeSpan是从一段时间以来 x 滴答声的long计数......完成时间就是Now + x*tick )。 So we can simplify this into a long predictor.所以我们可以将其简化为一个long预测器。

Setup设置

interface IAvger
{
    public double Avg { get; }
}
interface IAdder
{
    void Add(long value);
}
class Mean
{
    private int _count = 0;

    public double Total { get; private set; } = 0;
    public double? Avg => _count == 0 ? null : (double?)(Total / _count);

    public void Add(double val)
    {
        Total += val;
        _count++;
    }
}

You can ignore the interfaces (I used them when switching out potential solutions).您可以忽略接口(我在切换潜在解决方案时使用它们)。 Class Mean should be familiar... it calculates the mean average of a number of values, scaling/adapting as more values are found. Class Mean应该很熟悉......它计算多个值的平均值,随着找到更多值进行缩放/调整。

/// <summary>
/// Equivalent to your ComputeRemaining
/// </summary>
class RunningAvg : IAvger, IAdder
{
    private Mean _mean = new Mean();
    private readonly double _guess;
    public RunningAvg(double guess)
    {
        _guess = guess;
    }

    public double Avg => _mean.Avg ?? _guess;

    public void Add(long value) => _mean.Add(value);
}

Here's an equivalent to your ComputeRemaining .这是您的ComputeRemaining的等价物。 The value of guess helps an early prediction when nothing else is known (vs the equivalent of TimeSpan.Max ) guess的值有助于在其他未知的情况下进行早期预测(与TimeSpan.Max的等价物相比)

/// <summary>
/// Drop the lowest and highest value
/// - Fairly typical in stats, however note this is only one biggest/smallest
/// (will work best when the standard devation is low, and outliers
/// are rare)
/// </summary>
class IgnoreExtremes : IAvger, IAdder
{
    private long? _worst;
    private long? _best;
    private Mean _mean = new Mean();
    private readonly int _wt;
    private readonly double _guess;
    public IgnoreExtremes(double guess, int weight = 4)
    {
        _wt = weight;
        _guess = guess;
    }

    public long Best => _best ?? (long)Math.Round(_guess);
    public long Worst => _worst ?? (long)Math.Round(_guess);

    public double Avg
    {
        get
        {
            var avg = _mean.Avg;
            if (!avg.HasValue) return _guess;
            return (Best + _wt * avg.Value + Worst) / (2 + _wt);
        }
    }

    public void Add(long value)
    {
        if (!_best.HasValue)
        {
            _best = value;
        }
        else if (value < _best)
        {
            _mean.Add(_best.Value);
            _best = value;
        }
        else if (!_worst.HasValue)
        {
            _worst = value;
        }
        else if (value > _worst)
        {
            _mean.Add(_worst.Value);
            _worst = value;
        }
        else
        {
            _mean.Add(value);
        }
    }
}

Finally some stats!终于有一些数据了! IgnoreExtremes suppresses the highest and lowest (single) value. IgnoreExtremes抑制最高和最低(单个)值。 In scientific sampling it's fairly typical to ignore these, however with a real random distribution of numbers (eg dice roll, or random.Next ) only one extreme will be discarded.在科学抽样中,忽略这些是相当典型的,但是对于数字的真正随机分布(例如掷骰子或random.Next ),只有一个极端会被丢弃。 This should predict better numbers than RunningAvg .这应该比RunningAvg预测更好的数字。 Note this is a form of weighted average, you can tune it (slightly) by supplying a weight value at construction ( wt=4 is fairly common), or tying _wt to _mean.count (some code changes required)请注意,这是加权平均的一种形式,您可以通过在构造时提供weight值( wt=4相当常见)或将_wt_mean.count (需要一些代码更改)来调整它(稍微)

class IgnoreStdDevOutlier : IAvger, IAdder
{
    private const int AT_LEAST = 5;
    private Mean _mean = new Mean();
    private readonly List<long> _vals = new List<long>();
    private readonly double _guess;
    //private long _tot;
    private readonly double _outlierStdDevMulti;
    public IgnoreStdDevOutlier(double guess, double outlierStdDevMulti = 2)
    {
        _guess = guess;
        _outlierStdDevMulti = outlierStdDevMulti;
    }

    private double StdDev()
    {
        var avg = Avg;
        double tot = 0;
        foreach (var item in _vals)
            tot += (item - avg) * (item - avg);
        return Math.Sqrt(tot / (_vals.Count - 1));
    }

    public void Add(long value)
    {
        _vals.Add(value);
        if (_vals.Count > AT_LEAST)
        {
            var avg = Avg;
            var sd = StdDev();
            var min = avg - _outlierStdDevMulti * sd;
            var max = avg + _outlierStdDevMulti * sd;
            //Ignore outliers
            if (value < min || value > max) return;
        }
        _mean.Add(value);
    }

    public double Avg => _mean.Avg ?? 0;
}

Another statistics approach is to ignore values more than n*StandardDeviation from average, where n is often 2 or 3 (you'll find conflicting opinions).另一种统计方法是从平均值中忽略大于n*StandardDeviation的值,其中n通常为 2 或 3(您会发现相互矛盾的意见)。 All values seen are part of the standard deviation, but only those that aren't outliers are considered part of the average.看到的所有值都是标准偏差的一部分,但只有那些不是异常值的值才被视为平均值的一部分。 This ends up acting like a suppression factor, preventing the estimate swinging too much.这最终起到了抑制因素的作用,防止估计值波动太大。

Alright, so to run a test we need some sort of measuring class:好的,所以要运行测试,我们需要某种测量 class:

class Performance
{
    private readonly List<long> _set = new List<long>();
    private long _actual;

    public void Add(long item) => _set.Add(item);

    public void SetFinal(long final) => _actual = final;

    public void Report()
    {
        foreach (var item in _set)
        {
            Console.WriteLine("{0} {1} = {2}", item, _actual, (item / (double)_actual - 1) * 100);
        }
    }
}

A real guess can't know the final ( _actual ) value, but this class allows us to see how the guess is doing so far.真正的猜测无法知道最终的 ( _actual ) 值,但是这个 class 让我们可以看到猜测到目前为止的情况。

Finally the program class:最后是程序 class:

class Program
{
    const int MIN_MSEC = 100;
    const int MAX_MSEC = 1000;
    const int MIN_LOOP = 10;
    const int MAX_LOOP = 50;

    static void C(Random random)
    {
        int nm = random.Next(MAX_LOOP, MAX_LOOP);
        var guess = (double)((MAX_LOOP + MIN_LOOP) / 2 * (MAX_MSEC + MIN_MSEC) / 2);

        var predict = new RunningAvg(guess);
        //var predict = new IgnoreExtremes(guess);
        //var predict = new IgnoreStdDevOutlier(guess,3);
        var per = new Performance();
        long tot = 0;
        for (int i = 0; i <= nm; i++)
        {
            var op = random.Next(MIN_MSEC, MAX_MSEC);
            predict.Add(op);
            per.Add((long)Math.Round(predict.Avg * nm));
            tot += op;
        }
        per.SetFinal(tot);
        per.Report();
    }

    static void Main(string[] args)
    {
        var random = new Random();
        C(random);
    }
}

You can ignore the work is done in a method called C (just a side effect of the code - A was your Prog1, while B was Prog2).您可以忽略在名为C的方法中完成的工作(只是代码的副作用 - A是您的 Prog1,而B是 Prog2)。 Within C try changing which of RunningAvg , IgnoreExtremes or IgnoreStdDevOutlier is uncommented.C尝试更改RunningAvgIgnoreExtremesIgnoreStdDevOutlier中的哪一个未注释。 Because, again, what's written is dice-roll random, you cannot take a single run as a good benchmark.因为,再一次,所写的是随机掷骰子,你不能将单次运行作为一个好的基准。 The next stage is to wrap this in repeat-runs and take the average of the standard deviation of the predictions (or perhaps only the late predictions - a user probably doesn't mind if an early estimate is far off, as long as by the end it isn't jumping around) - but I ran out of time.下一阶段是将其包装在重复运行中并取预测的标准偏差的平均值(或者可能只是后期预测 - 用户可能不介意早期估计是否遥远,只要结束它没有跳来跳去) - 但我没时间了。 I find IgnoreStdDevOutlier , on average, converges on the right answer fairly swiftly being off by 0-1% by the end.我发现IgnoreStdDevOutlier平均而言会很快收敛到正确的答案,到最后会减少 0-1%。 IgnoreExtremes suffers from only ignoring one extreme (in each direction), so it's sort of a light version of IgnoreStdDevOutlier . IgnoreExtremes只会忽略一个极端(在每个方向上),所以它是IgnoreStdDevOutlier的轻量级版本。 If your data isn't random, and there are only occasionally extreme cases - it'll do just fine.如果您的数据不是随机的,并且偶尔会出现极端情况 - 它会做得很好。 RunningAvg doesn't actually perform terribly some of the time, other times it's off by double digit percentages all the way through. RunningAvg在某些时候实际上并没有表现得非常好,其他时候它一直以两位数的百分比关闭。 Random numbers, if only they were easy to predict.随机数,只要它们易于预测。

Note on usage使用注意事项

  • Timespan.Ticks is a long. Timespan.Ticks很长。 All of this is written to predict a long, which can be considered the difference between then and now.所有这些都是为了预测而写的,这也算是当时和现在的区别。 To directly switch use new Timespan(long ticks) to build the spans, and DateTime.Now.Subtract(startTime).Ticks to get a long from a span.要直接切换,请使用new Timespan(long ticks)来构建跨度,并使用DateTime.Now.Subtract(startTime).Ticks从跨度中获取 long。 Obviously all these classes could be re-written for TimeSpan instead of long .. unfortunately there isn't an easy generic where constraint that includes both long and TimeSpan显然,所有这些类都可以重写为TimeSpan而不是long .. 不幸的是,没有一个简单的泛型where约束同时包含longTimeSpan

You can know how many times(t) loop 4 (L4) will execute multiplying L1t L2t L3t*L4t = total.您可以知道循环 4 (L4) 将执行多少次 (t) L1t L2t L3t*L4t = 总计。 Now at the very beginning you declare现在一开始你声明

dt = DateTime.Now;
count = 0L;

Now inside L4 you increase count and calculates how much time is elapsed现在在 L4 内,您增加计数并计算经过了多少时间

et= DateTime.Now - dt;
count++;

And there using rule of three you calculate how many seconds are left to reach the total iterations.并使用三规则计算剩余多少秒才能达到总迭代次数。

remainingSeconds = (total*et.TotalSeconds/count) - et.TotalSeconds;

Now the ETA is现在ETA是

DateTime.Now.AddSeconds(remainingSeconds);

I used below logic to solve this.我使用下面的逻辑来解决这个问题。

  1. Before first foreach each loop I calculated the all different loops count.在第一次 foreach 每个循环之前,我计算了所有不同的循环计数。

  2. After that I created 4 different variables to hold sleep time for each loop which is settings inside them.之后,我创建了 4 个不同的变量来保持每个循环的睡眠时间,这是它们内部的设置。

  3. Now before first for loop I calculated the total time in milliseconds that will be taken be all loops using below logic:现在,在第一个 for 循环之前,我使用以下逻辑计算了所有循环的总时间(以毫秒为单位):

    var totalTime = (firstLoopCount * firstLoopSleepTime) + (firstLoopCount * secondLoopCount * secondLoopSleepTime) + (firstLoopCount * secondLoopCount * thirdLoopCount * thirdLoopSleepTime) + (firstLoopCount * secondLoopCount * thirdLoopCount * fourthLoopCount * fourthLoopSleepTime); var totalTime = (firstLoopCount * firstLoopSleepTime) + (firstLoopCount * secondLoopCount * secondLoopSleepTime) + (firstLoopCount * secondLoopCount *thirdLoopCount *thirdLoopSleepTime) + (firstLoopCount * secondLoopCount *thirdLoopCount * FourthLoopCount * FourthLoopSleepTime);

Note : I added 1 in function call (GetTotoalTimeinMilliSecond) where loops count is passing because loop is starting from 0 and ending with different loops count including itself.注意:我在 function 调用 (GetTotoalTimeinMilliSecond) 中添加了 1,其中循环计数正在传递,因为循环从 0 开始并以包括自身在内的不同循环计数结束。

  1. Now before loop start, printed total time taken现在在循环开始之前,打印总时间
  2. Inside each loop, after thread sleep, subtract thread sleep time from total time and print that.在每个循环内,在线程睡眠后,从总时间中减去线程睡眠时间并打印出来。 And set new calculated time as total time.并将新计算的时间设置为总时间。
  3. Repeat this inside every loop.在每个循环中重复此操作。

Below is the code:下面是代码:

 class Program
 {

    static void Main(string[] args)
    {
        DateTime startTime = DateTime.Now;

        var random = new Random();
        Console.Clear();

        // Simulate initiation delay
        //Console.WriteLine("initiate");
        Thread.Sleep(random.Next(100, 1000));

        int firstLoopCount =  random.Next(1, 10);
        int secondLoopCount = random.Next(1, 10);
        int thirdLoopCount = random.Next(1, 10);
        int fourthLoopCount = random.Next(1, 10);

        int firstLoopSleepTime = random.Next(100, 1000);
        int secondLoopSleepTime =random.Next(100, 1000);
        int thirdLoopSleepTime = random.Next(100, 1000);
        int fourthLoopSleepTime = random.Next(100, 1000);
        //**************Added 1 because loop is starting from 0 and ending with different loops count including itself.
        var totalTimeinMillSec = GetTotoalTimeinMilliSecond(firstLoopCount + 1, secondLoopCount + 1, thirdLoopCount + 1, fourthLoopCount + 1, firstLoopSleepTime, secondLoopSleepTime, thirdLoopSleepTime, fourthLoopSleepTime);

        PrintAndGetTimeRemaining(totalTimeinMillSec);        
        for (int loop1 = 0; loop1 <= firstLoopCount; loop1++)
        {
            // Simulate loop1 delay
            //Console.WriteLine("\tloop1");

            Thread.Sleep(firstLoopSleepTime);
            totalTimeinMillSec = PrintAndGetTimeRemaining(totalTimeinMillSec - firstLoopSleepTime);
            
            for (int loop2 = 0; loop2 <= secondLoopCount; loop2++)
            {
                // Simulate loop2 delay
                //Console.WriteLine("\t\tloop2");
                Thread.Sleep(secondLoopSleepTime);                   
                totalTimeinMillSec = PrintAndGetTimeRemaining(totalTimeinMillSec - secondLoopSleepTime);
                for (int loop3 = 0; loop3 <= thirdLoopCount; loop3++)
                {
                    // Simulate loop3 delay
                    //Console.WriteLine("\t\t\tloop3");
                    Thread.Sleep(thirdLoopSleepTime);                      
                    totalTimeinMillSec = PrintAndGetTimeRemaining(totalTimeinMillSec - thirdLoopSleepTime);
                    for (int loop4 = 0; loop4 <= fourthLoopCount; loop4++)
                    {
                        // Simulate loop4 delay
                        //Console.WriteLine("\t\t\t\tloop4");
                        Thread.Sleep(fourthLoopSleepTime);                           
                        totalTimeinMillSec = PrintAndGetTimeRemaining(totalTimeinMillSec - fourthLoopSleepTime);
                    }
                }
            }
        }
    }

    private static int PrintAndGetTimeRemaining(int totalTimeinMillSec)
    {
        TimeSpan timeRemaining = TimeSpan.FromMilliseconds(totalTimeinMillSec);

        Console.SetCursorPosition(0, 0);
        Console.WriteLine("ETA: ");
        Console.WriteLine(String.Format("{0} Days, {1} Hours, {2} Minutes, {3} Seconds, {4} Milliseconds", timeRemaining.Days.ToString().PadLeft(3, '0'), timeRemaining.Hours.ToString().PadLeft(2, '0'), timeRemaining.Minutes.ToString().PadLeft(2, '0'), timeRemaining.Seconds.ToString().PadLeft(2, '0'), timeRemaining.Milliseconds.ToString().PadLeft(2, '0')));
        return totalTimeinMillSec;
    }

    private static int GetTotoalTimeinMilliSecond(int firstLoopCount, int secondLoopCount, int thirdLoopCount, int fourthLoopCount, int firstLoopSleepTime, int secondLoopSleepTime, int thirdLoopSleepTime, int fourthLoopSleepTime)
    {
        var totalTime = (firstLoopCount * firstLoopSleepTime +
                                  firstLoopCount * secondLoopCount * secondLoopSleepTime +
                                  firstLoopCount * secondLoopCount * thirdLoopCount * thirdLoopSleepTime +
                                  firstLoopCount * secondLoopCount * thirdLoopCount * fourthLoopCount * fourthLoopSleepTime);
        
        return totalTime;
    }

}


}

Please let me know If I missed something.如果我错过了什么,请告诉我。

I think this may work for you;我认为这可能对您有用; in order to accomplish the solution I created a few classs to help.为了完成解决方案,我创建了几个类来提供帮助。

The code itself has some comments.代码本身有一些注释。

First an enum, to know in which loop we are;首先是一个枚举,以了解我们在哪个循环中; it is not fully necessary, but it can be worth for a refactor later on.这不是完全必要的,但值得稍后进行重构。

public enum LoopEnum
{
    loop1,
    loop2,
    loop3,
    loop4
}

Then i created a class called EtaLoop which will contain the entire loop information/logic to know how long a single iteration of a loop takes, I use StopWatch :然后我创建了一个名为EtaLoop的 class ,它将包含整个循环信息/逻辑,以了解循环的单次迭代需要多长时间,我使用StopWatch

public class EtaLoop
{
    public readonly int TotalIterations;
    private readonly List<long> _loopsTimesInMiliseconds;
    private readonly Stopwatch _stopwatch;

    public EtaLoop(int totalIterations)
    {
        //+1 as the loops starts on 0
        TotalIterations = totalIterations+1;
        _loopsTimesInMiliseconds = new List<long>();
        _stopwatch = new Stopwatch();

    }
    public double AvgExecution()
    {
        return _loopsTimesInMiliseconds.Any() ? _loopsTimesInMiliseconds.Average(a => a) : 0;
    }

    public void Start()
    {
        if(!_stopwatch.IsRunning)
            _stopwatch.Start();
    }

    public void Stop()
    {
        _stopwatch.Stop();
        _loopsTimesInMiliseconds.Add(_stopwatch.ElapsedMilliseconds);
        ResetStopWatch();
    }
    public int CurrentIteration()
    {
        return _loopsTimesInMiliseconds.Count();
    }

    public long EstimatedCurrentIteration()
    {
        return Convert.ToInt64(_loopsTimesInMiliseconds.Average(a => a) * TotalIterations);
    }

    private void ResetStopWatch()
    {
        _stopwatch.Reset();
    }
}

the methods I think are clear enough without more explanation.我认为的方法很清楚,无需更多解释。

another class called EtaTime which will contain more logic, this class is a wrapper for EtaLoop for example, if a for has 5 iterations, it will contain 5 elements on the list.另一个名为EtaTime的 class 将包含更多逻辑,这个 class 是EtaLoop的包装器,例如,如果一个 for 有 5 次迭代,它将包含列表中的 5 个元素。

each element ( EtaLoop ) is added to the list, once we finish (all the iterations are finished) It has some more methods, one of them, the "hard one" it is explained每个元素( EtaLoop )都被添加到列表中,一旦我们完成(所有迭代都完成)它还有一些方法,其中之一,它解释的“硬方法”

public class EtaTime
{
    public readonly List<EtaLoop> Loops;
    public readonly LoopEnum Loop;
    private EtaLoop _currentLoop;

    public EtaTime(LoopEnum loop)
    {
        Loops = new List<EtaLoop>();
        Loop = loop;
    }

    public void SetUpTotal(int totalIterations)
    {
        _currentLoop = new EtaLoop(totalIterations);
    }

    public void StartLoop()
    {
        _currentLoop.Start();

    }

    public void EndLoop()
    {
        _currentLoop.Stop();
    }

    public void RegisterLoop()
    {
        Loops.Add(_currentLoop);
    }

    /// <summary>
    /// Get the average time per execution, and the average number of loops per parent loop.
    /// The idea is to know how many times (and how long) the loop x is executed per each x-1
    /// </summary>
    /// <returns></returns>
    public (double, double) GetAvgTimeAndAvgExTimes()
    {
        double avgTimePerLoop = Loops.Any() ? Loops.Average(a => a.AvgExecution()) : _currentLoop.AvgExecution();
        double avgTotalLoopsIteration = Loops.Any() ? Loops.Average(a => a.TotalIterations) : _currentLoop.TotalIterations;
        return (avgTimePerLoop, avgTotalLoopsIteration);
    }

    public int GetCurrentIteration()
    {
        return _currentLoop.CurrentIteration();
    }

    public int TotalIterations()
    {
        return _currentLoop.TotalIterations;
    }
}

Finally the wrapper for the EtaTimeHelper which will contain all the EtaTimes ;最后是EtaTimeHelper的包装器,它将包含所有EtaTimes originally i was thinking to make a list, thats why the enum was for, but, i think like this is more clear.最初我想列出一个列表,这就是enum的原因,但是,我认为这样更清楚。 Note: this class can be split/moved to extension methods.注意:此 class 可以拆分/移动到扩展方法。

The main points here are RegisterLoop every time we finish a loop we have to call that method;这里的要点是RegisterLoop ,每次我们完成一个循环时,我们都必须调用该方法; and it needs to be here because i need the information from the other loops.它需要在这里,因为我需要来自其他循环的信息。

public class EtaTimeHelper
{
    //This part can be done in a list, but i think it is easier to see like this.
    public readonly EtaTime Loop1;
    public readonly EtaTime Loop2;
    public readonly EtaTime Loop3;
    public readonly EtaTime Loop4;
    public readonly DateTime StartTime;
    private DateTime _lastPrintTime;
    private const int TimeBetweenPrintsInSeconds = 10;


    public EtaTimeHelper()
    {
        Loop1 = new EtaTime(LoopEnum.loop1);
        Loop2 = new EtaTime(LoopEnum.loop2);
        Loop3 = new EtaTime(LoopEnum.loop3);
        Loop4 = new EtaTime(LoopEnum.loop4);
        StartTime = DateTime.Now;
        _lastPrintTime = DateTime.MinValue;
    }

    public void RegisterLoop(LoopEnum loopNumber)
    {

        switch (loopNumber)
        {
            case LoopEnum.loop1:
                Loop1.RegisterLoop();
                break;
            case LoopEnum.loop2:
                Loop2.RegisterLoop();
                break;
            case LoopEnum.loop3:
                Loop3.RegisterLoop();
                break;
            case LoopEnum.loop4:
                Loop4.RegisterLoop();
                break;
            default:
                throw new NotImplementedException("please register the loop");

        }

        PrintCompletionTime(DateTime.Now, loopNumber);
    }


    public void PrintCompletionTime(DateTime printTime, LoopEnum loopNumber)
    {
        if(_lastPrintTime.AddSeconds(TimeBetweenPrintsInSeconds) < printTime)
        {
            var time = CalculatePredictionTime(loopNumber);
            Print(time);
            _lastPrintTime = printTime;
        }
    }

    private long CalculatePredictionTime(LoopEnum loopNumber)
    {
        switch (loopNumber)
        {
            case LoopEnum.loop1: 
                return LoopPrediction(Loop1.GetAvgTimeAndAvgExTimes());
            case LoopEnum.loop2:
                return Loop2Prediction(Loop1, Loop2);
            case LoopEnum.loop3:
                return Loop3Prediction(Loop1, Loop2, Loop3);
            case LoopEnum.loop4:
                return Loop4Prediction(Loop1, Loop2, Loop3, Loop4);
            default:
                throw new NotImplementedException("please register the loop");

        }


        //If all loops in #1 are finished, all sub loops are also finished. which means, it is the "end of the loop"
        long LoopPrediction((double, double) avgTimeAndAvgExTimes)
        {
            double avgTimePerLoop = avgTimeAndAvgExTimes.Item1;
            double avgIterations = avgTimeAndAvgExTimes.Item2;
            return Convert.ToInt64(avgTimePerLoop * avgIterations);
        }

        long Loop2Prediction(EtaTime loop1, EtaTime loop2)
        {
            
            var loop1Prediction = LoopPrediction(loop1.GetAvgTimeAndAvgExTimes());

            var loop2Values = loop2.GetAvgTimeAndAvgExTimes();
            long avgPerLoop = LoopPrediction(loop2Values);

            var loop1AvgIterations = loop1.GetAvgTimeAndAvgExTimes().Item2;
            var expectedLoop2Iterations = loop1AvgIterations;

            double loop2Predictions = avgPerLoop * expectedLoop2Iterations;

            if (loop1Prediction == 0)
            {
                return Convert.ToInt64(loop2Predictions);
            }
            else
            {
                //1+current iteration
                return loop1Prediction + loop2.GetCurrentIteration();
            }
        }

        long Loop3Prediction(EtaTime loop1, EtaTime loop2, EtaTime loop3)
        {
            var loop1_2Prediction = Loop2Prediction(loop1, loop2);

            var loop3Values = loop3.GetAvgTimeAndAvgExTimes();
            long avgPerLoop = LoopPrediction(loop3Values);

            var loop2AvgIterations = loop2.GetAvgTimeAndAvgExTimes().Item2;
            var loop1AvgIterations = loop1.GetAvgTimeAndAvgExTimes().Item2;
            var expectedLoop3Iterations = loop2AvgIterations * loop1AvgIterations;

            double loop3Predictions = avgPerLoop * expectedLoop3Iterations;

            if (loop1_2Prediction == 0)
            {
                return Convert.ToInt64(loop3Predictions);
            }
            else
            {
                //1-2+current iteration
                return loop1_2Prediction+ loop3.GetCurrentIteration();
            }
        }

        long Loop4Prediction(EtaTime loop1, EtaTime loop2, EtaTime loop3, EtaTime loop4)
        {
            var loop1_2_3Prediction = Loop3Prediction(loop1, loop2, loop3);

            var loop4Values = loop4.GetAvgTimeAndAvgExTimes();
            long avgPerLoop = LoopPrediction(loop4Values);

            var loop2AvgIterations = loop2.GetAvgTimeAndAvgExTimes().Item2;
            var loop1AvgIterations = loop1.GetAvgTimeAndAvgExTimes().Item2;
            var loop3AvgIterations = loop3.GetAvgTimeAndAvgExTimes().Item2;
            var expectedLoop4Iterations = loop2AvgIterations * loop1AvgIterations* loop3AvgIterations;

            double loop4Predictions = avgPerLoop * expectedLoop4Iterations;

            if (loop1_2_3Prediction == 0)
            {
                return Convert.ToInt64(loop4Predictions);
            }
            else
            {
                //1-2-3+current iteration
                return loop1_2_3Prediction + loop4.GetCurrentIteration();
            }
        }
    }

    private void Print(long ms)
    {
        DateTime estimatedCompletionTime = StartTime.AddMilliseconds(ms);

        TimeSpan leftTime = (estimatedCompletionTime - DateTime.Now);

        Console.WriteLine("ETA: ");
        Console.WriteLine($"{leftTime.Days} Days, {leftTime.Hours} Hours, {leftTime.Minutes} Minutes, {leftTime.Seconds} Seconds");//, leftTime.Days.ToString().PadLeft(3, '0'), leftTime.Hours.ToString().PadLeft(2, '0'), leftTime.Minutes.ToString().PadLeft(2, '0'), leftTime.Seconds.ToString().PadLeft(2, '0')));
        Console.WriteLine($"on {estimatedCompletionTime.ToString("yyyy/MM/dd HH:mm:ss")}");
        Console.WriteLine($"Current Time: {DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss")}");
    }

}

and the print.和印刷品。 as you mention you want some mecanisim to print, but you did not specify if it is a consol app a web or a winforms app.正如您提到的,您想要打印一些 mecanisim,但您没有指定它是 consol 应用程序、web 还是 winforms 应用程序。 Probably it is winforms if it is a desktop app you probably can do something like myLabel.SetPropertyThreadSafe to modify the label you want to update.如果它是桌面应用程序,可能是 winforms,您可能可以执行类似myLabel.SetPropertyThreadSafe之类的操作来修改您要更新的 label。 The algorithim which calculates the time it is on CalculatePredictionTime I tried to make it a single method for n number of loops but I couldnt.计算它在CalculatePredictionTime上的时间的算法我试图使它成为 n 个循环的单一方法,但我做不到。

Now the main.现在主要。

I did a few changes on the Thread.Sleep as it is easier for me to figure it out if makes sense while i was testing.我对Thread.Sleep做了一些更改,因为在我测试时我更容易弄清楚它是否有意义。 also, a range 10-100 (10x) I thought it was very big, i reduced it for testing reasons.另外,范围 10-100 (10x) 我认为它非常大,出于测试原因我将其减小了。

the code follows a pattern, first you need to instanciate the EtaTimeHelper class.代码遵循一个模式,首先你需要实例化EtaTimeHelper class。

  • before each loop you have to setUpTotal which means you have to pass the number of iterations are going to be.在每个循环之前,您必须setUpTotal这意味着您必须传递将要进行的迭代次数。
  • The first line of the loop will be .StartLoop() this will start the StopWatch and the last line of the loop (before the } ) will be a .EndLoop() ;循环的第一行将是.StartLoop()这将启动StopWatch ,循环的最后一行(在}之前)将是.EndLoop()
  • after the closing bracket } you have to indicate the etaTimeHelper.RegisterLoop(loopNumber);在右括号}之后,您必须指出etaTimeHelper.RegisterLoop(loopNumber); passing as parameter the loop number this will print if necessary the time.将循环号作为参数传递,这将在必要时打印时间。
static void Main(string[] args)
{

    var random = new Random();
    Console.Clear();

    // Simulate initiation delay
    //Console.WriteLine("initiate");

    EtaTimeHelper etaTimeHelper = new EtaTimeHelper();


    int intCount = random.Next(1, 10);
    etaTimeHelper.Loop1.SetUpTotal(intCount);
    for (int loop1 = 0; loop1 <= intCount; loop1++)
    {
        etaTimeHelper.Loop1.StartLoop();

        // Simulate loop1 delay
        Console.WriteLine("\tloop1");
        Thread.Sleep(random.Next(40, 50));

        //number of times the loop 2 is going to execute inside this loop 1;
        int loop2times = random.Next(1, 10);
        etaTimeHelper.Loop2.SetUpTotal(loop2times);
        for (int loop2 = 0; loop2 <= loop2times; loop2++)
        {
            etaTimeHelper.Loop2.StartLoop();

            // Simulate loop2 delay
            //Console.WriteLine("\t\tloop2");
            Thread.Sleep(random.Next(30, 40));
            
            //number of times the loop 3 is going to execute inside this loop 3;
            int loop3times = random.Next(1, 10);
            etaTimeHelper.Loop3.SetUpTotal(loop3times);
            for (int loop3 = 0; loop3 <= loop3times; loop3++)
            {
                etaTimeHelper.Loop3.StartLoop();

                // Simulate loop3 delay
                //Console.WriteLine("\t\t\tloop3");
                Thread.Sleep(random.Next(10, 20));


                var loop4Times = random.Next(1, 10);
                etaTimeHelper.Loop4.SetUpTotal(loop4Times);
                for (int loop4 = 0; loop4 <= loop4Times; loop4++)
                {
                    etaTimeHelper.Loop4.StartLoop();
                    // Simulate loop4 delay
                    //Console.WriteLine("\t\t\t\tloop4");
                    Thread.Sleep(random.Next(20, 30));

                    etaTimeHelper.Loop4.EndLoop();
                }
                etaTimeHelper.RegisterLoop(LoopEnum.loop4);
                etaTimeHelper.Loop3.EndLoop();
            }
            etaTimeHelper.RegisterLoop(LoopEnum.loop3);
            etaTimeHelper.Loop2.EndLoop();
        }
        etaTimeHelper.RegisterLoop(LoopEnum.loop2);
        etaTimeHelper.Loop1.EndLoop();
    }
    etaTimeHelper.RegisterLoop(LoopEnum.loop1);
}

Here is a "working" fiddle https://dotnetfiddle.net/Z06W4g note: it times out after a few seconds, in your local machine works fine.这是一个“工作”的小提琴https://dotnetfiddle.net/Z06W4g注意:它会在几秒钟后超时,在您的本地机器上工作正常。

In order to assert the ETA every second, what I have done is attaching an event to a System.Timers.Timer object.为了每秒断言 ETA,我所做的是将事件附加到System.Timers.Timer object。 The event will be triggered every second in order to continuously providing the user with the ETA feedback.该事件将每秒触发一次,以便不断向用户提供 ETA 反馈。

            System.Timers.Timer aTimer = new System.Timers.Timer(1000);
            aTimer.Elapsed += (sender, e) => ATimer_Elapsed(sender, e, new MyEventArguments
            {
                Loop1TotalIterations = intCount,
                CurrentIndex = loop1,
                Start = startTime

            });
            aTimer.AutoReset = true;
            aTimer.Enabled = true;

In your example we are using the Thread.Sleep in order simulate a delay.在您的示例中,我们使用Thread.Sleep来模拟延迟。

Using Thread.Sleep will put everything to sleep - therefor the time logic will be inaccurate.使用Thread.Sleep将使一切进入睡眠状态——因此时间逻辑将不准确。

What I have done is creating a Multi-thread application - in order to run the logic on one thread and the time on another.我所做的是创建一个多线程应用程序——以便在一个线程上运行逻辑并在另一个线程上运行时间。 Therefor when we pause the time checking and ETA will continue to run.因此,当我们暂停时间检查时,ETA 将继续运行。

Putting it all together we have the following:综上所述,我们有以下几点:

namespace ConsoleApp10
{
    #region Usings

    using System;
    using System.Threading;
    using System.Threading.Tasks;
    using System.Timers;

    #endregion

    /// <summary>
    /// Event class to be passed to ElapsedEventHandler
    /// </summary>
    public class MyEventArguments
    {
        public int Loop1TotalIterations { get; set; }

        public DateTime Start { get; set; }

        public DateTime CurrentDate => DateTime.Now;

        public int CurrentIndex { get; set; }
    }

    class Program
    {
        /// <summary>
        ///  Compute the remaining time
        /// </summary>
        public static void ComputeRemaining((int count, DateTime time) start, (int count, DateTime time) current, int end)
        {
            var T = current.count - start.count == 0
                ? TimeSpan.MaxValue
                : TimeSpan.FromSeconds((end - current.count) * current.time.Subtract(start.time).TotalSeconds / (current.count - start.count));

            Console.Clear();
            Console.SetCursorPosition(0, 0);
            Console.ForegroundColor = (ConsoleColor)15;
            Console.WriteLine(String.Format("ETA: {0} Days, {1} Hours, {2} Minutes, {3} Seconds",
                T.Days.ToString().PadLeft(3, '0'),
                T.Hours.ToString().PadLeft(2, '0'),
                T.Minutes.ToString().PadLeft(2, '0'),
                T.Seconds.ToString().PadLeft(2, '0')));

            Console.WriteLine();
        }
        
        static void Main(string[] args)
        {
            DateTime startTime = DateTime.Now;
            Random random = new Random();
            int intCount = random.Next(1, 10);
            int loop1 = 0;

            var time = Task.Factory.StartNew(() =>
            {
                System.Timers.Timer aTimer = new System.Timers.Timer(1000);
                aTimer.Elapsed += (sender, e) => ATimer_Elapsed(sender, e, new MyEventArguments
                {
                    Loop1TotalIterations = intCount,
                    CurrentIndex = loop1,
                    Start = startTime

                });
                aTimer.AutoReset = true;
                aTimer.Enabled = true;

            });

            var logic = Task.Factory.StartNew(() =>
            {
                PrintAndSimulate("Initiate");

                for (loop1 = 0; loop1 <= intCount; loop1++)
                {
                    PrintAndSimulate("\tloop1");

                    for (int loop2 = 0; loop2 <= random.Next(1, 10); loop2++)
                    {
                        PrintAndSimulate("\t\tloop2");

                        for (int loop3 = 0; loop3 <= random.Next(1, 10); loop3++)
                        {
                            PrintAndSimulate("\t\t\tloop3");

                            for (int loop4 = 0; loop4 <= random.Next(1, 10); loop4++)
                            {
                                PrintAndSimulate("\t\t\t\tloop4");
                            }
                        }
                    }
                }
            });

            Task.WaitAll(new[] { time, logic });
        }

        /// <summary>
        ///  Display the info string and simulates a delay
        /// </summary>
        private static void PrintAndSimulate(string info)
        {
            int time = new Random().Next(100, 1000);
            Console.SetCursorPosition(5, 5);
            Console.ForegroundColor = (ConsoleColor)new Random().Next(15);
            Console.WriteLine(info);
            Thread.Sleep(time);
        }

        /// <summary>
        /// ElapsedEventHandler
        /// </summary>
        private static void ATimer_Elapsed(object sender, ElapsedEventArgs e, MyEventArguments myEventArguments)
        {
            ComputeRemaining((0, myEventArguments.Start), (myEventArguments.CurrentIndex, myEventArguments.CurrentDate), myEventArguments.Loop1TotalIterations);
        }
    }
}

I have added some colours to the console feedback - making a change more evident.我在控制台反馈中添加了一些颜色 - 使更改更加明显。

I think you will have to benchmark it a few times for the operation that you need and then extrapolate.我认为您必须针对所需的操作对其进行几次基准测试,然后再进行推断。 There are variables here.这里有变数。 The compute power and storage type (if involved) and network (if involved) of the target will all contribute to the ETA and every execution will have a different execution time.目标的计算能力和存储类型(如果涉及)和网络(如果涉及)都将有助于 ETA,并且每次执行都会有不同的执行时间。 But ETA can be closely predicted after some benchmarking.但经过一些基准测试后,可以准确预测 ETA。 And if you want to be really clever, you can show a warning that it may take more time than the displayed ETA because of x, y and z eg CPU utilisation by other processes running on the target.如果你想变得更聪明,你可以显示一个警告,它可能比显示的 ETA 花费更多的时间,因为 x、y 和 z 例如目标上运行的其他进程的 CPU 利用率。

Jon Skeet is an authority on this topic and he has a good resource here: link Jon Skeet 是这个主题的权威,他在这里有一个很好的资源:链接

Also read here for how the types in your operation and bitness of the CPU will affect your ETA: link另请阅读此处了解您的操作类型和 CPU 位数将如何影响您的 ETA: 链接

This may not be ideal solution but has potential to solve the issue.这可能不是理想的解决方案,但有可能解决问题。
First you build a Tree of Action containing what you are going to execute and then execute the Actions by iterating the tree.首先,您构建一个包含您将要执行的内容的Action Tree ,然后通过迭代树来执行Actions
The tree acts as index of what you are going to execute.树充当您将要执行的内容的索引。 Even you can track what is being executed.甚至您也可以跟踪正在执行的操作。 Whats done and whats pending.已完成的和未决的。

I have nested the actions in single level in the tree for demo purpose but you can nest the tree as much as possible, inside loops etc.(pseudo code ahead but poc builds and executes well)出于演示目的,我已将操作嵌套在树中的单层中,但您可以在循环内部等尽可能多地嵌套树。(前面的伪代码,但 poc 构建和执行良好)

class Program
{
    static void Main(string[] args)
    {
        var random = new Random();

        TreeNode<Action> root = new TreeNode<Action>(() => { });

        var loop1 = root.AddChild(() =>
        {
            int Count = random.Next(1, 3);

            for (int i = 0; i <= Count; i++)
                Thread.Sleep(random.Next(100, 2000));
        });

        var loop2 = loop1.AddChild(() =>
        {
            int Count = random.Next(1, 3);

            for (int i = 0; i <= Count; i++)
                Thread.Sleep(random.Next(100, 2000));
        });

        var loop3 = loop2.AddChild(() =>
        {
            int Count = random.Next(1, 3);

            for (int i = 0; i <= Count; i++)
                Thread.Sleep(random.Next(100, 2000));
        });

        var loop4 = loop3.AddChild(() =>
        {
            int Count = random.Next(1, 3);

            for (int i = 0; i <= Count; i++)
                Thread.Sleep(random.Next(100, 2000));
        });

        var loop5 = loop4.AddChild(() =>
        {
            int Count = random.Next(1, 3);

            for (int i = 0; i <= Count; i++)
                Thread.Sleep(random.Next(100, 2000));
        });

        var loop6 = loop5.AddChild(() =>
        {
            int Count = random.Next(1, 3);

            for (int i = 0; i <= Count; i++)
                Thread.Sleep(random.Next(100, 2000));
        });

        root.Execute(DateTime.Now);
        Console.ReadLine();
    }
}
public static class Extensions
{
    public static string Humanize(this TimeSpan timeRemaining)
    {
        return String.Format("{0} Days, {1} Hours, {2} Minutes, {3} Seconds", timeRemaining.Days.ToString().PadLeft(3, '0'), timeRemaining.Hours.ToString().PadLeft(2, '0'), timeRemaining.Minutes.ToString().PadLeft(2, '0'), timeRemaining.Seconds.ToString().PadLeft(2, '0'));
    }

    public static void PrintETA(this TimeSpan timeRemaining)
    {
        //Console.SetCursorPosition(0, 0);
        Console.Write("ETA: ");
        Console.Write(timeRemaining.Humanize());
    }

    public static TimeSpan ComputeRemaining((int count, DateTime time) start, (int count, DateTime time) current, int end) =>
        current.count - start.count == 0
        ? TimeSpan.MaxValue
        : TimeSpan.FromSeconds((end - current.count) * current.time.Subtract(start.time).TotalSeconds / (current.count - start.count));

    public static void Execute(this TreeNode<Action> root, DateTime startTime)
    {
        var current = root;
        var end = current.Count();
        var currentCount = 1;
    Iterate:
        var Count = current.Children.Count();
        for (int i = 0; i < Count; i++)
        {
            TreeNode<Action> node = current.Children.ElementAt(i);
            node.Data();

            TimeSpan timeRemaining = ComputeRemaining((0, startTime), (currentCount++, DateTime.Now), end-1);
            timeRemaining.PrintETA();
            Console.WriteLine();
            Console.WriteLine("Processed {0} of {1}", currentCount - 1, end-1);

            if (node.Children.Count() > 0)
            {
                current = node;
                goto Iterate;
            }
        }
    }
}

Ref: TreeNode.cs参考: TreeNode.cs

Yet another tree structure: https://github.com/gt4dev/yet-another-tree-structure又一个树形结构: https://github.com/gt4dev/yet-another-tree-structure

Updated.更新。 The first version of the answer calculated the values of RemainingTime and TotalTime at the end of every iteration.答案的第一个版本在每次迭代结束时计算 RemainingTime 和 TotalTime 的值。 Given the way the for loops are nested, it could cause long delays between updates.鉴于 for 循环的嵌套方式,它可能会导致更新之间的长时间延迟。 In order to read those values at given intervals, some changes are made.为了以给定的时间间隔读取这些值,需要进行一些更改。

Let's start with the Loop class.让我们从循环 class 开始。 It is used to keep track of the details of every for loop, like the total amount of iterations, the current iteration and the time consumed in every iteration.它用于跟踪每个 for 循环的细节,例如迭代总量、当前迭代和每次迭代消耗的时间。 To acomplish the later, two System.Diagnostic Stopwatch are used.为了完成后者,使用了两个 System.Diagnostic Stopwatch。 One is kept running free, not reseted, to ease the calculation of the average time for an iteration.一个是保持自由运行,而不是重置,以简化迭代平均时间的计算。 The other clock is reseted on every iteration to provide a value for LoopTime, used in calculations on the fly of RemainingTime and TotalTime, when accesed via the property getters.当通过属性获取器访问时,另一个时钟在每次迭代时重置以提供 LoopTime 的值,用于实时计算 RemainingTime 和 TotalTime。 When the iteration ends, signaled by the method StopClock(), the average loop time and the related properties are updated.当迭代结束时,由方法 StopClock() 发出信号,平均循环时间和相关属性被更新。 The values obtained here are more accurate than the ones calculated on the fly.此处获得的值比动态计算的值更准确。

Its parent, LoopTimer class, is in charge of creating and storing references of Loop instances, start and stop clocks and calculate the global ETA.其父 LoopTimer class 负责创建和存储 Loop 实例的引用、启动和停止时钟以及计算全局 ETA。 The method EnterLoop() is used at the beginning of the for loop.方法 EnterLoop() 用于 for 循环的开头。 It creates a new Loop.它创建一个新的循环。 The overload EnterLoop() with a single parameter is used in the rest of iterations to retrieve Loop instances stored in a Dictionary.具有单个参数的重载 EnterLoop() 用于迭代的 rest 以检索存储在 Dictionary 中的 Loop 实例。 The method ExitLoop(), at the end of the loop, stops the clock and updates calculations. ExitLoop() 方法在循环结束时停止时钟并更新计算。

Functions ExitLoopRetETA() and ExitLoopRetTotalEta() are provided as replacements of ExitLoop(), to print data at the end of the loop, for testing or debugging.提供函数 ExitLoopRetETA() 和 ExitLoopRetTotalEta() 作为 ExitLoop() 的替代品,用于在循环结束时打印数据,用于测试或调试。 Method Bonus() shows how to use it.方法 Bonus() 展示了如何使用它。 Method ShowStatus() and function GetStatus provide internal information on the Loop objects.方法 ShowStatus() 和 function GetStatus 提供有关 Loop 对象的内部信息。

To show the values periodically, a Task is used, to run DoUpdate() (or DoUpdateTotal() or DoUpdateStatus())in a separate thread.为了定期显示这些值,使用了一个任务,在单独的线程中运行 DoUpdate()(或 DoUpdateTotal() 或 DoUpdateStatus())。

The target framework is.Net 4.0目标框架是.Net 4.0

The working classes:工人阶级:


using System;
using System.Collections.Generic;
using System.Diagnostics;

namespace LoopTimer
{
  public struct TotalEta
  {
      public TimeSpan Eta;
      public TimeSpan Total;
  }

  internal class LoopTimer
  {
      // Private helper class
      private class Loop
      {
          // Declarations
          private Stopwatch _clock;
          private Stopwatch _interval_clock;

          // Constructor
          public Loop(int index, int count)
          {
              Index = index;
              Count = count;

              _clock = new Stopwatch();
              _interval_clock = new Stopwatch();
          }

          // Properties
          internal int Index { get; set; }

          internal int Count { get; private set; }

          private double _loopTimeMs;
          internal double LoopTimeMs
          {
              get
              {
                  if (_clock.IsRunning)
                  {
                      return _interval_clock.Elapsed.TotalMilliseconds;
                  }
                  else
                      return _loopTimeMs; ;
              }
          }

          private double _remainingTimeMs;
          internal double RemainingTimeMs
          {
              get
              {
                  if (_clock.IsRunning)
                      return CalculateRemainingTime();
                  else
                      return _remainingTimeMs;
              }
          }

          private double _totalTimeMs;
          internal double TotalTimeMs
          {
              get
              {
                  if (_clock.IsRunning)
                      return CalculateTotalTime();
                  else
                      return _totalTimeMs;
              }
          }

          internal TimeSpan LoopTime
          {
              get { return TimeSpan.FromMilliseconds(LoopTimeMs); }
          }

          internal TimeSpan TotalTime
          {
              get { return TimeSpan.FromMilliseconds(TotalTimeMs); }
          }

          internal TimeSpan RemainingTime
          {
              get { return TimeSpan.FromMilliseconds(RemainingTimeMs); }
          }


          // Methods

          internal void StartClock()
          {
              _clock.Start();
              _interval_clock.Start();

          }

          internal void StopClock()
          {
              _clock.Stop();
              _interval_clock.Stop();

              UpdateTimes();

              _interval_clock.Reset();

          }

          private void UpdateTimes()
          {
              //  reading clock
              double elapsed = _clock.Elapsed.TotalMilliseconds;

              // Calculating average loop time. The Stopwatch is not reseted between iterations.
              _loopTimeMs = elapsed / (Index + 1);

              // Calculating estimated remaining time =  average loop time * remaining iterations.
              _remainingTimeMs = CalculateRemainingTime();

              // Calculating estimated total time =  average loop time * iterations.
              _totalTimeMs = CalculateTotalTime();
          }

          private double CalculateRemainingTime()
          {
              // Calculating estimated remaining time =  average loop time * remaining iterations.

              double time;
              int countt = Count - Index;

              if (countt > 1)
                  time = LoopTimeMs * countt;
              else if (countt == 1)
                  time = LoopTimeMs;
              else
                  time = 0;

              return time;
          }

          private double CalculateTotalTime()
          {
              return LoopTimeMs * Count;
          }

          
      }
      // End Private helper class

      // Declarations
      private Dictionary<int, Loop> _loopDict;
      private int _loopIndex;

      // Constructor
      public LoopTimer()
      {
          _loopDict = new Dictionary<int, Loop>();
          _loopIndex = -1;
      }

      // Properties
      internal TimeSpan TotalTime
      {
          get { return TimeSpan.FromMilliseconds(TotalTimeMs); }
      }

      internal TimeSpan RemainingTime
      {
          get { return TimeSpan.FromMilliseconds(RemainingTimeMs); }
      }

      private double TotalTimeMs
      { get { return CalculateTotalTime(); } }

      private double RemainingTimeMs
      { get { return CalculateRemainingTime(); } }

      // Methods
      internal void EnterLoop(int index, int count)
      {
          Loop newLoop;

          // increase index
          _loopIndex++;

          if (!_loopDict.ContainsKey(_loopIndex))
          {
              // create new Loop
              newLoop = new Loop(index, count);
              _loopDict[_loopIndex] = newLoop;
          }
          else
          {   // retrieve Loop from Dict
              newLoop = _loopDict[_loopIndex];
          }

          newLoop.StartClock();
      }
 
      internal void EnterLoop(int index)
      {
          // increase index
          _loopIndex++;

          // retrive loop & start clock
          _loopDict[_loopIndex].Index = index;
          _loopDict[_loopIndex].StartClock();
      }

      internal void ExitLoop()
      {   // retrive loop & stop clock
          _loopDict[_loopIndex].StopClock();

          // decrease index
          _loopIndex--;
      }

      // bonus method
      internal TimeSpan ExitLoopRetETA()
      {  // retrive loop & stop clock
          _loopDict[_loopIndex].StopClock();

          // decrease index
          _loopIndex--;

          return RemainingTime;
      }

      // bonus method
      internal TotalEta ExitLoopRetTotalEta()
      {
          TotalEta retval;

          retval.Eta = ExitLoopRetETA();
          retval.Total = TotalTime;

          return retval;
      }

      // debug method
      internal void ShowStatus()
      {
          Console.WriteLine("Status:");
          Console.WriteLine(" RemainingTime:");
          for (int i = 0; i < _loopDict.Count; i++)
          {
              TimeSpan time = _loopDict[i].RemainingTime;
              Console.WriteLine(string.Format(" Loop: {0} Value: {1}", i, time.ToString()));
          }
          Console.WriteLine();
      }

      // debug method
      internal TotalEta[] GetStatus()
      {
          TotalEta[] retArr = new TotalEta[_loopDict.Count];
          TotalEta retval;

          for (int i = 0; i < _loopDict.Count; i++)
          {
              retval = new TotalEta();

              retval.Eta = _loopDict[i].RemainingTime;
              retval.Total = _loopDict[i].TotalTime;

              retArr[i] = retval;
          }
          return retArr;
      }

      private double CalculateRemainingTime()
      {
          double max, time;
          max = 0;

          // Remaining Time, the greater of all
          for (int i = 0; i < _loopDict.Count; i++)
          {
              time = _loopDict[i].RemainingTimeMs;
              if (time > max)
                  max = time;
          }

          return max;
      }

      // Total Time, bonus
      private double CalculateTotalTime()
      {
          double max, time;
          max = 0;

          // Total Time, the greater of all  
          for (int i = 0; i < _loopDict.Count; i++)
          {
              time = _loopDict[i].TotalTimeMs;
              if (time > max)
                  max = time;
          }

          return max;
      }
  }
}

The sample program:示例程序:

using System;
using System.Threading;
using System.Threading.Tasks;


namespace LoopTimer
{
  class Solution
  {
      static CancellationTokenSource ts;

      static void Main(string[] args)
      {
          Console.Clear();

          LoopTimer lm = new LoopTimer();

          var random = new Random();

          // For easy change test parameters
          int minRndCount = 1;
          int maxRndCount = 10;
          int minRndSleep = 100;
          int maxRndSleep = 1000;

          // A task to update console, with cancellation token
          ts = new CancellationTokenSource();
          Task updater = new Task(() => DoUpdate(lm), ts.Token);
          // Uncomment to show estimated total time.
          //Task updater = new Task(() => DoUpdateTotal(lm), ts.Token);
          // Uncomment to show estimated total time and internal values of every loop.
          //Task updater = new Task(() => DoUpdateStatus(lm), ts.Token);

          // Simulate initiation delay
          Thread.Sleep(random.Next(minRndSleep, maxRndSleep));
          // Console.WriteLine("initiate");

          updater.Start();

          int intCountL1 = random.Next(minRndCount, maxRndCount);
          for (int loop1 = 0; loop1 <= intCountL1; loop1++)
          {
              // Entering loop1
              if (loop1 == 0)
                  lm.EnterLoop(loop1, intCountL1);
              else
                  lm.EnterLoop(loop1);

              // Simulate loop1 delay
              //Console.WriteLine("\tloop1");
              Thread.Sleep(random.Next(minRndSleep, maxRndSleep));

              int intCountL2 = random.Next(minRndCount, maxRndCount);
              for (int loop2 = 0; loop2 <= intCountL2; loop2++)
              {
                  // Entering loop2
                  if (loop2 == 0)
                      lm.EnterLoop(loop2, intCountL2);
                  else
                      lm.EnterLoop(loop2);

                  // Simulate loop2 delay
                  //Console.WriteLine("\t\tloop2");
                  Thread.Sleep(random.Next(minRndSleep, maxRndSleep));

                  int intCountL3 = random.Next(minRndCount, maxRndCount);
                  for (int loop3 = 0; loop3 <= intCountL3; loop3++)
                  {
                      // Entering loop3
                      if (loop3 == 0)
                          lm.EnterLoop(loop3, intCountL3);
                      else
                          lm.EnterLoop(loop3);

                      // Simulate loop3 delay
                      //Console.WriteLine("\t\t\tloop3");
                      Thread.Sleep(random.Next(minRndSleep, maxRndSleep));

                      int intCountL4 = random.Next(minRndCount, maxRndCount);
                      for (int loop4 = 0; loop4 <= intCountL4; loop4++)
                      {
                          // Entering loop4
                          if (loop4 == 0)
                              lm.EnterLoop(loop4, intCountL4);
                          else
                              lm.EnterLoop(loop4);

                          // Simulate loop4 delay
                          //Console.WriteLine("\t\t\t\tloop4");
                          Thread.Sleep(random.Next(minRndSleep, maxRndSleep));

                          // Exiting loop4
                          lm.ExitLoop();
                      }
                      // Exiting loop3
                      lm.ExitLoop();
                  }
                  // Exiting loop2
                  lm.ExitLoop();
              }
              // Exiting loop1
              lm.ExitLoop();
          }
          ts.Cancel();
      }

      static private void DoUpdate(LoopTimer lm)
      {
          char[] animchar = { '|', '/', '-', '\\' };
          int index = 0;

          Thread.Sleep(100);

          while (true)
          {
              TimeSpan eta = lm.RemainingTime;

              Console.SetCursorPosition(0, 0);
              Console.Write(string.Format(" {4} ETA: {0} Days, {1} Hours, {2} Minutes, {3} Seconds", eta.Days.ToString().PadLeft(3, '0'), eta.Hours.ToString().PadLeft(2, '0'), eta.Minutes.ToString().PadLeft(2, '0'), eta.Seconds.ToString().PadLeft(2, '0'), animchar[index].ToString()));

              if (++index > 3)
                  index = 0;
              Thread.Sleep(1000);

              ts.Token.ThrowIfCancellationRequested();
          }
      }

      /*
        This method is provided as a sample on displaying the estimated total time.
       */
      static private void DoUpdateTotal(LoopTimer lm)
      {
          char[] animchar = { '|', '/', '-', '\\' };
          int index = 0;

          Thread.Sleep(100);

          while (true)
          {
              TimeSpan eta = lm.RemainingTime;
              TimeSpan total = lm.TotalTime;

              Console.SetCursorPosition(0, 0);
              Console.Write(string.Format(" {4} ETA: {0} Days, {1} Hours, {2} Minutes, {3} Seconds", eta.Days.ToString().PadLeft(3, '0'), eta.Hours.ToString().PadLeft(2, '0'), eta.Minutes.ToString().PadLeft(2, '0'), eta.Seconds.ToString().PadLeft(2, '0'), animchar[index].ToString()));
              Console.Write(string.Format("\n Total: {0} Days, {1} Hours, {2} Minutes, {3} Seconds", total.Days.ToString().PadLeft(3, '0'), total.Hours.ToString().PadLeft(2, '0'), total.Minutes.ToString().PadLeft(2, '0'), total.Seconds.ToString().PadLeft(2, '0')));

              if (++index > 3)
                  index = 0;
              Thread.Sleep(1000);

              ts.Token.ThrowIfCancellationRequested();
          }
      }

      /*
       This method is provided as a sample on displaying the estimated total time, and
       the internal values of every loop.
      */
      static private void DoUpdateStatus(LoopTimer lm)
      {
          char[] animchar = { '|', '/', '-', '\\' };
          int index = 0;

          Thread.Sleep(100);

          while (true)
          {
              TimeSpan eta = lm.RemainingTime;
              TimeSpan total = lm.TotalTime;
              TotalEta[] status = lm.GetStatus();

              Console.SetCursorPosition(0, 0);
              Console.Write(string.Format(" {4} ETA: {0} Days, {1} Hours, {2} Minutes, {3} Seconds", eta.Days.ToString().PadLeft(3, '0'), eta.Hours.ToString().PadLeft(2, '0'), eta.Minutes.ToString().PadLeft(2, '0'), eta.Seconds.ToString().PadLeft(2, '0'), animchar[index].ToString()));
              Console.Write(string.Format("\n Total: {0} Days, {1} Hours, {2} Minutes, {3} Seconds", total.Days.ToString().PadLeft(3, '0'), total.Hours.ToString().PadLeft(2, '0'), total.Minutes.ToString().PadLeft(2, '0'), total.Seconds.ToString().PadLeft(2, '0')));
              Console.WriteLine();

              int loop = 0;

              foreach (var item in status)
              {
                  Console.Write(string.Format("\n Loop: {0} ETA: {1}    \tTotal: {2}", loop, item.Eta.ToString(@"hh\:mm\:ss\.FFFF"), item.Total.ToString(@"hh\:mm\:ss\.FFFF")));
                  loop++;
              }

              if (++index > 3)
                  index = 0;

              Thread.Sleep(1000);

              ts.Token.ThrowIfCancellationRequested();
          }
      }

      /*
        This method is provided as a sample for variations on 
        the ExitLoopRet method. Uses in-place calls.
      */
      static internal void Bonus()
      {
          TotalEta remVal;
          TimeSpan remTime;

          LoopTimer lm = new LoopTimer();

          Console.Clear();

          // easy change test parameters
          var random = new Random();
          int minRndCount = 1;
          int maxRndCount = 5;

          int maxRndSleep = 1000;

         
          // First, outer loop
          int intCountL1 = random.Next(minRndCount, maxRndCount);

          for (int i = 0; i < intCountL1; i++)
          {
              if (i == 0)
                  lm.EnterLoop(i, intCountL1);
              else
                  lm.EnterLoop(i);

              Console.WriteLine(string.Format("\nLoop1 begin iteration: {0} of {1}. Will work(sleep) for {2} ms.", i, intCountL1 - 1, maxRndSleep));
              Thread.Sleep(maxRndSleep);

              // Second, middle loop
              int intCountL2 = random.Next(minRndCount, maxRndCount);

              for (int j = 0; j < intCountL2; j++)
              {
                  if (j == 0)
                      lm.EnterLoop(j, intCountL2);
                  else
                      lm.EnterLoop(j);

                  Console.WriteLine(string.Format("\n\tLoop2 begin iteration: {0} of {1}. Will work(sleep) for {2} ms.", j, intCountL2 - 1, maxRndSleep));
                  Thread.Sleep(maxRndSleep);

                  // Third, inner loop
                  int intCountL3 = random.Next(minRndCount, maxRndCount);
                  for (int k = 0; k < intCountL3; k++)
                  {
                      if (k == 0)
                          lm.EnterLoop(k, intCountL3);
                      else
                          lm.EnterLoop(k);

                      Console.WriteLine(string.Format("\n\t\tLoop3 begin iteration: {0} of {1}. Will work(sleep) for {2} ms.", k, intCountL3 - 1, maxRndSleep));
                      Thread.Sleep(maxRndSleep);

                      lm.ExitLoop();
                   
                      Console.WriteLine(string.Format("\n\t\tLoop3 end iteration: {0} of {1}", k, intCountL3 - 1));
                      lm.ShowStatus();
                  }

                  remTime = lm.ExitLoopRetETA();
                
                  Console.WriteLine(string.Format("\n\tLoop2 end iteration: {0} of {1}", j, intCountL2 - 1));
                  Console.WriteLine("\t\tRem: " + remTime.ToString());
              }

              remVal = lm.ExitLoopRetTotalEta();
              
              Console.WriteLine(string.Format("\nLoop1 end iteration: {0} of {1}", i, intCountL1 - 1));
              Console.WriteLine("\t\tTot: " + remVal.Total.ToString());
              Console.WriteLine("\t\tRem: " + remVal.Eta.ToString());
          }
      }

  }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM