คุณจะเพิ่มตัวจับเวลาลงในแอปพลิเคชันคอนโซล C # ได้อย่างไร


132

แค่นี้ - คุณจะเพิ่มตัวจับเวลาลงในแอปพลิเคชันคอนโซล C # ได้อย่างไร? จะดีมากถ้าคุณสามารถจัดหาตัวอย่างการเข้ารหัสได้


16
ข้อควรระวัง: คำตอบที่นี่มีจุดบกพร่องวัตถุตัวจับเวลากำลังจะเก็บขยะ การอ้างอิงถึงตัวจับเวลาจะต้องถูกเก็บไว้ในตัวแปรคงที่เพื่อให้แน่ใจว่ายังคงติ๊กอยู่
Hans Passant

@HansPassant ดูเหมือนคุณจะพลาดคำชี้แจงที่ชัดเจนในคำตอบของฉัน: "ขอแนะนำให้ใช้ระบบ Static (ที่ใช้ร่วมกันใน VB.NET) ตลอดเวลาหากคุณกำลังพัฒนาบริการ Windows และต้องใช้ตัวจับเวลาเพื่อทำงานเป็นระยะ วิธีนี้จะหลีกเลี่ยงการเก็บขยะวัตถุจับเวลาของคุณก่อนเวลาอันควร” หากผู้คนต้องการคัดลอกตัวอย่างสุ่มและใช้สุ่มสี่สุ่มห้านั่นคือปัญหาของพวกเขา
เถ้า

คำตอบ:


120

เป็นเรื่องที่ดีมากอย่างไรก็ตามในการจำลองเวลาที่ผ่านไปเราจำเป็นต้องเรียกใช้คำสั่งที่ใช้เวลาพอสมควรและชัดเจนมากในตัวอย่างที่สอง

อย่างไรก็ตามรูปแบบของการใช้ for loop เพื่อทำงานบางอย่างตลอดไปนั้นใช้ทรัพยากรอุปกรณ์จำนวนมากและเราสามารถใช้ Garbage Collector เพื่อทำบางสิ่งเช่นนั้นแทนได้

เราสามารถดูการแก้ไขนี้ได้ในโค้ดจากหนังสือเล่มเดียวกัน CLR ผ่าน C # Third Ed

using System;
using System.Threading;

public static class Program {

   public static void Main() {
      // Create a Timer object that knows to call our TimerCallback
      // method once every 2000 milliseconds.
      Timer t = new Timer(TimerCallback, null, 0, 2000);
      // Wait for the user to hit <Enter>
      Console.ReadLine();
   }

   private static void TimerCallback(Object o) {
      // Display the date/time when this method got called.
      Console.WriteLine("In TimerCallback: " + DateTime.Now);
      // Force a garbage collection to occur for this demo.
      GC.Collect();
   }
}

3
Khalid สิ่งนี้มีประโยชน์มาก ขอบคุณ console.readline () และ GC.Collect เป็นเพียงสิ่งที่ฉันต้องการ
Seth Spearman

9
@Ralph Willgoss ทำไมต้อง GC.Collect (); ต้องระบุ?
Puchacz

2
@Puchacz GC.Collect()ฉันไม่เห็นจุดของการโทร ไม่มีอะไรให้เก็บสะสม มันจะสมเหตุสมผลถ้าGC.KeepAlive(t)ถูกเรียกหลังConsole.ReadLine();
พิมพ์ใหม่

1
สิ้นสุดหลังจากการโทรกลับครั้งแรก
BadPiggie

1
@Khalid Al Hajami "อย่างไรก็ตามรูปแบบของการใช้ for loop เพื่อทำงานบางอย่างตลอดไปนั้นใช้ทรัพยากรอุปกรณ์จำนวนมากและเราสามารถใช้ Garbage Collector เพื่อทำสิ่งนั้นแทนได้" นี่คือขยะไร้สาระอย่างแท้จริง คนเก็บขยะไม่เกี่ยวข้องอย่างเต็มที่ คุณคัดลอกจากหนังสือและไม่เข้าใจสิ่งที่คุณกำลังคัดลอกใช่หรือไม่?
เถ้า

66

ใช้คลาส System.Threading.Timer

System.Windows.Forms.Timer ได้รับการออกแบบมาเพื่อใช้ในเธรดเดียวโดยปกติจะเป็นเธรด UI ของ Windows Forms

นอกจากนี้ยังมีคลาส System.Timers ที่เพิ่มเข้ามาในช่วงต้นของการพัฒนา. NET framework อย่างไรก็ตามโดยทั่วไปแนะนำให้ใช้คลาส System.Threading.Timer แทนเนื่องจากนี่เป็นเพียง wrapper รอบ ๆ System.Threading.Timer เท่านั้น

ขอแนะนำให้ใช้ระบบแบบคงที่ (แชร์ใน VB.NET) เสมอตัวตั้งเวลาหากคุณกำลังพัฒนาบริการ Windows และต้องใช้ตัวจับเวลาเพื่อให้ทำงานเป็นระยะ วิธีนี้จะหลีกเลี่ยงการเก็บขยะวัตถุจับเวลาของคุณก่อนเวลาอันควร

นี่คือตัวอย่างของตัวจับเวลาในแอปพลิเคชันคอนโซล:

using System; 
using System.Threading; 
public static class Program 
{ 
    public static void Main() 
    { 
       Console.WriteLine("Main thread: starting a timer"); 
       Timer t = new Timer(ComputeBoundOp, 5, 0, 2000); 
       Console.WriteLine("Main thread: Doing other work here...");
       Thread.Sleep(10000); // Simulating other work (10 seconds)
       t.Dispose(); // Cancel the timer now
    }
    // This method's signature must match the TimerCallback delegate
    private static void ComputeBoundOp(Object state) 
    { 
       // This method is executed by a thread pool thread 
       Console.WriteLine("In ComputeBoundOp: state={0}", state); 
       Thread.Sleep(1000); // Simulates other work (1 second)
       // When this method returns, the thread goes back 
       // to the pool and waits for another task 
    }
}

จากหนังสือCLR Via C #โดย Jeff Richter หนังสือเล่มนี้อธิบายถึงเหตุผลเบื้องหลังตัวจับเวลา 3 ประเภทในบทที่ 23 ขอแนะนำ


คุณสามารถให้ข้อมูลเพิ่มเติมเล็กน้อยเกี่ยวกับการเข้ารหัสจริงได้หรือไม่?
Johan Bresler

ตัวอย่างจาก msdn เหมาะกับคุณหรือไม่? msdn.microsoft.com/en-us/library/system.threading.timer.aspx
Eric Tuttleman

เอริคฉันยังไม่ได้ลอง แต่จะไม่แปลกถ้ามีปัญหากับมัน ฉันสังเกตเห็นว่ามันกำลังพยายามทำการซิงโครไนซ์ระหว่างเธรดบางอย่างซึ่งเป็นพื้นที่ที่อาจเป็นเรื่องยากที่จะทำให้ถูกต้อง หากคุณสามารถหลีกเลี่ยงได้ในการออกแบบของคุณคุณควรทำเช่นนั้นเสมอ
Ash

1
Ash - ฉันเห็นด้วยกับตัวอย่าง msdn ฉันจะไม่ลดรหัสการซิงโครไนซ์ในทันทีหากตัวจับเวลาทำงานในเธรดของตัวเองแสดงว่าคุณกำลังเขียนแอพแบบมัลติเธรดและจำเป็นต้องระวังปัญหาเกี่ยวกับการซิงโครไนซ์
Eric Tuttleman

1
จะเกิดอะไรขึ้นหากมีหลายวิธีที่ตรงกับลายเซ็นผู้รับมอบสิทธิ์ TimerCallback
Ozkan

22

นี่คือรหัสสำหรับสร้างเครื่องหมายจับเวลาหนึ่งวินาทีอย่างง่าย:

  using System;
  using System.Threading;

  class TimerExample
  {
      static public void Tick(Object stateInfo)
      {
          Console.WriteLine("Tick: {0}", DateTime.Now.ToString("h:mm:ss"));
      }

      static void Main()
      {
          TimerCallback callback = new TimerCallback(Tick);

          Console.WriteLine("Creating timer: {0}\n", 
                             DateTime.Now.ToString("h:mm:ss"));

          // create a one second timer tick
          Timer stateTimer = new Timer(callback, null, 0, 1000);

          // loop here forever
          for (; ; )
          {
              // add a sleep for 100 mSec to reduce CPU usage
              Thread.Sleep(100);
          }
      }
  }

และนี่คือผลลัพธ์ที่ได้:

    c:\temp>timer.exe
    Creating timer: 5:22:40

    Tick: 5:22:40
    Tick: 5:22:41
    Tick: 5:22:42
    Tick: 5:22:43
    Tick: 5:22:44
    Tick: 5:22:45
    Tick: 5:22:46
    Tick: 5:22:47

แก้ไข:ไม่ควรเพิ่มฮาร์ดสปินลูปลงในโค้ดเนื่องจากใช้รอบ CPU โดยไม่ได้รับ ในกรณีนี้มีการเพิ่มลูปเพียงเพื่อหยุดแอปพลิเคชันจากการปิดทำให้สามารถสังเกตการทำงานของเธรดได้ แต่เพื่อความถูกต้องและเพื่อลดการใช้งาน CPU จึงมีการเพิ่ม Sleep call แบบธรรมดาลงในลูปนั้น


7
สำหรับ (;;) {} ทำให้เกิดการใช้งาน cpu 100%
Seth Spearman

1
มันไม่ชัดเจนพอสมควรถ้าคุณมีลูปที่ไม่มีที่สิ้นสุดนั่นจะส่งผลให้ CPU เป็น 100% ในการแก้ไขสิ่งที่คุณต้องทำคือเพิ่มการโทรแบบสลีปลงในลูป
veight

3
เป็นที่น่าอัศจรรย์ที่มีคนจำนวนมากที่จับจ้องว่า for loop ควรเป็น while loop หรือไม่และทำไม CPU ถึง 100% พูดถึงคิดถึงไม้เพื่อต้นไม้! Azimuth ฉันอยากรู้เป็นการส่วนตัวว่า a while (1) จะแตกต่างกับ infinite for loop อย่างไร? แน่นอนว่าคนที่เขียนเครื่องมือเพิ่มประสิทธิภาพคอมไพเลอร์ CLR จะตรวจสอบให้แน่ใจว่าการสร้างโค้ดทั้งสองนี้สร้างโค้ด CLR เดียวกันหรือไม่?
Blake7

1
สาเหตุหนึ่งที่ทำให้ในขณะที่ (1) ไม่ทำงานคือ c #: test.cs (21,20) ไม่ถูกต้อง: ข้อผิดพลาด CS0031: ค่าคงที่ '1' ไม่สามารถแปลงเป็น 'บูล' ได้
Blake7

1
ไม่ได้อยู่ในเครื่องของฉัน (win8.1, i5) เพียงประมาณ 20-30% ตอนนั้นคุณมีคอมพิวเตอร์แบบไหน @SethSpearman
shinzou

17

มาสนุกกันเถอะ

using System;
using System.Timers;

namespace TimerExample
{
    class Program
    {
        static Timer timer = new Timer(1000);
        static int i = 10;

        static void Main(string[] args)
        {            
            timer.Elapsed+=timer_Elapsed;
            timer.Start(); Console.Read();
        }

        private static void timer_Elapsed(object sender, ElapsedEventArgs e)
        {
            i--;

            Console.Clear();
            Console.WriteLine("=================================================");
            Console.WriteLine("                  DEFUSE THE BOMB");
            Console.WriteLine(""); 
            Console.WriteLine("                Time Remaining:  " + i.ToString());
            Console.WriteLine("");        
            Console.WriteLine("=================================================");

            if (i == 0) 
            {
                Console.Clear();
                Console.WriteLine("");
                Console.WriteLine("==============================================");
                Console.WriteLine("         B O O O O O M M M M M ! ! ! !");
                Console.WriteLine("");
                Console.WriteLine("               G A M E  O V E R");
                Console.WriteLine("==============================================");

                timer.Close();
                timer.Dispose();
            }

            GC.Collect();
        }
    }
}

11

หรือใช้ Rx สั้นและหวาน:

static void Main()
{
Observable.Interval(TimeSpan.FromSeconds(10)).Subscribe(t => Console.WriteLine("I am called... {0}", t));

for (; ; ) { }
}

1
ทางออกที่ดีที่สุดจริงๆ!
Dmitry Ledentsov

8
อ่านไม่ออกมากและต่อต้านแนวปฏิบัติที่ดีที่สุด มันดูน่ากลัว แต่ไม่ควรใช้ในการผลิตเพราะ ppl บางตัวจะไป wtf และ poo เอง
Piotr Kula

2
Reactive Extensions (Rx) ไม่ได้รับการพัฒนาอย่างจริงจังเป็นเวลา 2 ปี นอกจากนี้ตัวอย่างยังไม่มีบริบทและสับสน ไม่ค่อยมีใครรู้แผนภาพหรือตัวอย่างการไหล
James Bailey

4

คุณยังสามารถใช้กลไกการจับเวลาของคุณเองได้หากคุณต้องการการควบคุมที่มากขึ้นเล็กน้อย แต่อาจมีความแม่นยำน้อยลงและมีรหัส / ความซับซ้อนมากขึ้น แต่ฉันยังคงแนะนำตัวจับเวลา ใช้สิ่งนี้แม้ว่าคุณต้องการควบคุมเธรดเวลาจริง:

private void ThreadLoop(object callback)
{
    while(true)
    {
        ((Delegate) callback).DynamicInvoke(null);
        Thread.Sleep(5000);
    }
}

จะเป็นเธรดการกำหนดเวลาของคุณ (แก้ไขสิ่งนี้เพื่อหยุดเมื่อมีการร้องขอและในช่วงเวลาใดก็ตามที่คุณต้องการ)

และในการใช้ / เริ่มคุณสามารถทำได้:

Thread t = new Thread(new ParameterizedThreadStart(ThreadLoop));

t.Start((Action)CallBack);

การโทรกลับเป็นวิธีที่ไม่มีพารามิเตอร์เป็นโมฆะของคุณที่คุณต้องการเรียกใช้ในแต่ละช่วงเวลา ตัวอย่างเช่น:

private void CallBack()
{
    //Do Something.
}

1
ถ้าฉันต้องการรันงานแบทช์จนกว่าจะหมดเวลาคำแนะนำของคุณตรงนี้จะดีที่สุดไหม
Johan Bresler

1

คุณยังสามารถสร้างของคุณเองได้ (หากไม่พอใจกับตัวเลือกที่มีให้)

การสร้างการTimerใช้งานของคุณเองเป็นเรื่องพื้นฐาน

นี่คือตัวอย่างสำหรับแอปพลิเคชันที่ต้องการการเข้าถึงวัตถุ COM บนเธรดเดียวกับส่วนที่เหลือของ codebase ของฉัน

/// <summary>
/// Internal timer for window.setTimeout() and window.setInterval().
/// This is to ensure that async calls always run on the same thread.
/// </summary>
public class Timer : IDisposable {

    public void Tick()
    {
        if (Enabled && Environment.TickCount >= nextTick)
        {
            Callback.Invoke(this, null);
            nextTick = Environment.TickCount + Interval;
        }
    }

    private int nextTick = 0;

    public void Start()
    {
        this.Enabled = true;
        Interval = interval;
    }

    public void Stop()
    {
        this.Enabled = false;
    }

    public event EventHandler Callback;

    public bool Enabled = false;

    private int interval = 1000;

    public int Interval
    {
        get { return interval; }
        set { interval = value; nextTick = Environment.TickCount + interval; }
    }

    public void Dispose()
    {
        this.Callback = null;
        this.Stop();
    }

}

คุณสามารถเพิ่มเหตุการณ์ได้ดังนี้:

Timer timer = new Timer();
timer.Callback += delegate
{
    if (once) { timer.Enabled = false; }
    Callback.execute(callbackId, args);
};
timer.Enabled = true;
timer.Interval = ms;
timer.Start();
Window.timers.Add(Environment.TickCount, timer);

เพื่อให้แน่ใจว่าตัวจับเวลาทำงานคุณต้องสร้างลูปที่ไม่มีที่สิ้นสุดดังนี้:

while (true) {
     // Create a new list in case a new timer
     // is added/removed during a callback.
     foreach (Timer timer in new List<Timer>(timers.Values))
     {
         timer.Tick();
     }
}

1

ใน C # 5.0+ และ. NET Framework 4.5+ คุณสามารถใช้ async / await:

async void RunMethodEvery(Action method, double seconds)
{
    while (true)
    {
        await Task.Delay(TimeSpan.FromSeconds(seconds));
        method();
    }
 }

0

คุณหมอ

คุณมีมัน :)

public static void Main()
   {
      SetTimer();

      Console.WriteLine("\nPress the Enter key to exit the application...\n");
      Console.WriteLine("The application started at {0:HH:mm:ss.fff}", DateTime.Now);
      Console.ReadLine();
      aTimer.Stop();
      aTimer.Dispose();

      Console.WriteLine("Terminating the application...");
   }

   private static void SetTimer()
   {
        // Create a timer with a two second interval.
        aTimer = new System.Timers.Timer(2000);
        // Hook up the Elapsed event for the timer. 
        aTimer.Elapsed += OnTimedEvent;
        aTimer.AutoReset = true;
        aTimer.Enabled = true;
    }

    private static void OnTimedEvent(Object source, ElapsedEventArgs e)
    {
        Console.WriteLine("The Elapsed event was raised at {0:HH:mm:ss.fff}",
                          e.SignalTime);
    }

0

ฉันขอแนะนำให้คุณปฏิบัติตามแนวทางของ Microsoft ( https://docs.microsoft.com/en-us/dotnet/api/system.timers.timer.interval?view=netcore-3.1 )

ฉันลองใช้ครั้งแรกSystem.Threading;กับ

var myTimer = new Timer((e) =>
{
   // Code
}, null, TimeSpan.Zero, TimeSpan.FromSeconds(5));

แต่มันหยุดลงอย่างต่อเนื่องหลังจากผ่านไปประมาณ 20 นาที

ด้วยเหตุนี้ฉันจึงลองตั้งค่าโซลูชัน

GC.KeepAlive(myTimer)

หรือ

for (; ; ) { }
}

แต่ไม่ได้ผลในกรณีของฉัน

ตามเอกสารของ Microsoft มันทำงานได้อย่างสมบูรณ์:

using System;
using System.Timers;

public class Example
{
    private static Timer aTimer;

    public static void Main()
    {
        // Create a timer and set a two second interval.
        aTimer = new System.Timers.Timer();
        aTimer.Interval = 2000;

        // Hook up the Elapsed event for the timer. 
        aTimer.Elapsed += OnTimedEvent;

        // Have the timer fire repeated events (true is the default)
        aTimer.AutoReset = true;

        // Start the timer
        aTimer.Enabled = true;

        Console.WriteLine("Press the Enter key to exit the program at any time... ");
        Console.ReadLine();
    }

    private static void OnTimedEvent(Object source, System.Timers.ElapsedEventArgs e)
    {
        Console.WriteLine("The Elapsed event was raised at {0}", e.SignalTime);
    }
}
// The example displays output like the following: 
//       Press the Enter key to exit the program at any time... 
//       The Elapsed event was raised at 5/20/2015 8:48:58 PM 
//       The Elapsed event was raised at 5/20/2015 8:49:00 PM 
//       The Elapsed event was raised at 5/20/2015 8:49:02 PM 
//       The Elapsed event was raised at 5/20/2015 8:49:04 PM 
//       The Elapsed event was raised at 5/20/2015 8:49:06 PM 
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.