【问题标题】:Time delay in For loop in c#c#中For循环的时间延迟
【发布时间】:2012-03-30 16:21:21
【问题描述】:

如何在特定旋转后在循环中使用时间延迟? 假设:

for(int i = 0 ; i<64;i++)
{
........
}

我希望每 8 次旋转后延迟 1 秒。

【问题讨论】:

    标签: c# timer


    【解决方案1】:

    有很多方法可以做到这一点:

    • 方法一:非常糟糕:忙碌等待:

      DateTime timeToStartUpAgain = whatever;

      while(DateTime.Now &lt; timeToStartUpAgain) {}

    这是一件可怕的事情;操作系统会假设你正在做有用的工作,并会分配一个 CPU 来做任何事情,除了在这上面旋转。永远不要这样做除非你知道旋转只会持续几微秒。基本上,当您这样做时,您已经雇用了某人为您看钟;这不经济。

    • 方法二:非常糟糕:让线程休眠。

    让线程休眠也是一件可怕的事情,但没有加热 CPU 可怕。休眠一个线程告诉操作系统“这个应用程序的这个线程应该停止响应事件一段时间并且什么都不做”。这比雇人为你看钟要好;现在你已经雇了一个人为你睡觉

    • 方法三:将工作分解为更小的任务。创建一个计时器。当你想要延迟时,不要做下一个任务,而是做一个计时器。当计时器触发其滴答事件时,从您离开的任务列表中获取。

    这可以有效利用资源;现在你正在雇人煮鸡蛋,在煮鸡蛋的时候,他们可以做吐司。

    但是,编写程序以便将工作分解为小任务是一件很痛苦的事情。

    • 方法四:使用C# 5对异步编程的支持; 等待延迟任务,让 C# 编译器负责重组您的程序以使其高效。

    当然,C# 5 目前仅处于测试阶段。

    注意:从 Visual Studio 2012 开始使用 C# 5。如果您使用的是 VS 2012 或更高版本,则可以使用异步编程。

    【讨论】:

      【解决方案2】:

      如果你想在每 8 次迭代后休眠,试试这个:

      for (int i = 0; i < 64; i++)
      {
          //...
          if (i % 8 == 7)
              Thread.Sleep(1000); //ms
      }
      

      【讨论】:

        【解决方案3】:

        使用Thread.Sleep(来自 System.Threading):

        for(int i = 0 ; i<64;i++)
        {
             if(i % 8 == 0)
                Thread.Sleep(1000);
        }
        

        【讨论】:

        • +1 两个答案,如果使用错误也会阻塞 UI 线程。
        • 第一次迭代后会休眠。
        【解决方案4】:

        这是我想出的实现。它是通用的,因此您可以轻松地将其重用于您的用例。 (它与@Servy 的建议一致)

        public static async Task ForEachWithDelay<T>(this ICollection<T> items, Func<T, Task> action, double interval)
        {
            using (var timer = new System.Timers.Timer(interval))
            {
                var task = new Task(() => { });
                int remaining = items.Count;
                var queue = new ConcurrentQueue<T>(items);
        
                timer.Elapsed += async (sender, args) =>
                {
                    T item;
                    if (queue.TryDequeue(out item))
                    {
                        try
                        {
                            await action(item);
                        }
                        finally
                        {
                            // Complete task.
                            remaining -= 1;
        
                            if (remaining == 0)
                            {
                                // No more items to process. Complete task.
                                task.Start();
                            }
                        }
                    }
                };
        
                timer.Start();
        
                await task;
            }
        }
        

        然后这样使用它:

        var numbers = Enumerable.Range(1, 10).ToList();
        var interval = 1000; // 1000 ms delay
        numbers.ForEachWithDelay(i => Task.Run(() => Console.WriteLine(i + " @ " + DateTime.Now)), interval);
        

        打印这个:

        1 @ 2/2/2016 9:45:37 AM
        2 @ 2/2/2016 9:45:38 AM
        3 @ 2/2/2016 9:45:39 AM
        4 @ 2/2/2016 9:45:40 AM
        5 @ 2/2/2016 9:45:41 AM
        6 @ 2/2/2016 9:45:42 AM
        7 @ 2/2/2016 9:45:43 AM
        8 @ 2/2/2016 9:45:44 AM
        9 @ 2/2/2016 9:45:45 AM
        10 @ 2/2/2016 9:45:46 AM
        

        【讨论】:

          【解决方案5】:

          您可能还想考虑使用Timer,而不是在循环中暂停当前线程。

          它将允许您在一段时间后执行某些代码块(重复或不重复)。如果没有更多上下文,很难确定这是否最适合您的情况,或者您将如何相应地更改您的解决方案。

          【讨论】:

            【解决方案6】:

            有一次我需要编写一个程序,女巫非常敏感,需要准确的时间(大约纳秒)。

            我使用了各种各样的东西,比如Thread.Sleep()、Timer 和......但它们都不是那么准确,可以满足我的需求,所以我用这个代码来延迟:

            var stopWatch = new Stopwatch();
            for(int i = 0 ; i<64;i++)
            {
            stopWatch.Restart();
            while (stopWatch.ElapsedMilliseconds <= 1000)
            {
            
            }
            }
            

            当然,这种方式对 CPU 不是很友好,但是当您需要如此精确的精度时,您就必须走老路了。

            【讨论】:

            • 系统时钟是否会将分辨率限制在~15ms?
            • 没有。秒表甚至可以精确到 1 毫秒。我们将它用于一个对时间非常敏感的项目。
            【解决方案7】:

            试试这个不依赖 async、await 或 TPL 的更简单的版本。

            1. 产生 50 个方法调用,
            2. 睡眠 20 秒,
            3. 检查 20 或 x 项 是完整的。
            4. 如果没有再次睡眠 20 秒
            5. 如果是则继续循环

            代码在这里

             foreach (var item in collection)
                        {
                            if (cnt < 50)
                            {
                                cnt++;
                                DoWork(item);
                            }
                            else
                            {
                                bool stayinLoop = true;
                                while (stayinLoop)
                                {
                                    Thread.Sleep(20000);
                                    if (cnt < 30)
                                    {
                                        stayinLoop = false;
                                        DoWork(item);
                                    }
                                }
                            }
                        }
            

            在单独的线程中工作,这样 sleep 就不会阻塞你,可以是后台 worker 、 Begin 、 End 方法的 webrequest 或可选的异步任务或 Task.Run()

            function DoWork()
            //Do work in a sep thread.
            cnt--;
            )
            

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2012-08-18
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2018-05-17
              • 1970-01-01
              相关资源
              最近更新 更多