HOWTO · Csharp

如何在 C# 中製作延遲計時器

本文介紹如何在 C# 中製作延遲計時器。它包括四種不同的方法:Thread.Sleep()、Task.Delay()、System.Timers.Timer 和 System.Threading.Timer。

本頁內容

若要在 C# 中新增延遲,請選擇 Thread.Sleep() 來封鎖目前執行緒、選擇 Task.Delay() 以非同步方式等待,或選擇計時器類別來排程回呼。以下範例使用兩秒的間隔,並展示每種選項的行為。它們尚未在所提供的執行環境中執行,因此不宣稱任何觀察到的輸出。

使用 Thread.Sleep() 方法在 C# 中建立延遲

Thread.Sleep() 會將目前執行緒暫停指定的毫秒數。由於該執行緒在等待期間無法執行其他工作,因此僅在適合封鎖時使用此方法,例如在簡短的同步主控台程式中。

這個完整範例會印出一則訊息、等待兩秒,然後再印出另一則訊息:

using System;
using System.Threading;

class DelayExample
{
    static void Main()
    {
        Console.WriteLine("The delay starts.");
        Thread.Sleep(2000);
        Console.WriteLine("The delay ends.");
    }
}

參數以毫秒為單位,因此 2000 表示請求等待兩秒。實際經過的時間可能會因系統排程而有所不同。

使用 Task.Delay() 方法在 C# 中建立延遲

Task.Delay() 會建立一個在請求的間隔後完成的工作。在非同步方法中使用 await 等待它,可讓該方法在等待期間讓出執行權,這與佔用目前執行緒的 Thread.Sleep() 不同。請從應用程式現有的非同步流程中呼叫該非同步方法。

這個完整範例將非同步等待保留在一個獨立的方法中,並使用同步進入點來呼叫它:

using System;
using System.Threading.Tasks;

class DelayExample
{
    static void Main()
    {
        RunAsync().GetAwaiter().GetResult();
    }

    static async Task RunAsync()
    {
        Console.WriteLine("Before the delay");
        await Task.Delay(2000);
        Console.WriteLine("After the delay");
    }
}

await 運算式會在延遲工作完成後繼續執行 RunAsync。在已經使用非同步方法的應用程式中,請直接使用 await 等待 Task.Delay(2000),而不是封鎖進入點來等待該工作。

使用 System.Timers.Timer 類別在 C# 中建立延遲

System.Timers.Timer 會在其以毫秒為單位的 Interval 之後引發 Elapsed 事件。若只需單一事件,請將 AutoReset 設為 false;若事件應按該間隔重複,請將其設為 true。回呼是由事件驅動的,因此啟動計時器不會暫停下一行程式碼。

這個完整範例會在主控台程式結束之前等待該一次性事件:

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

class DelayExample
{
    static void Main()
    {
        using (var completed = new ManualResetEvent(false))
        using (var timer = new System.Timers.Timer(2000))
        {
            timer.AutoReset = false;
            timer.Elapsed += (sender, args) =>
            {
                Console.WriteLine("Timer elapsed.");
                completed.Set();
            };

            Console.WriteLine("Timer started.");
            timer.Start();
            completed.WaitOne();
        }
    }
}

WaitOne() 會讓這個主控台範例保持運作,直到事件處理常式發出完成訊號為止。在執行時間較長的應用程式中,請依照其正常的事件迴圈或服務存續時間讓應用程式保持運作,而不是僅為了計時器而新增封鎖式等待。

使用 System.Threading.Timer 類別在 C# 中建立延遲

System.Threading.Timer 會以初始到期時間和週期來排程回呼。這兩個值都以毫秒為單位。當回呼只應執行一次時,請將週期設為 Timeout.Infinite;若要排程重複的回呼,請提供有限的週期。回呼會與啟動計時器的程式碼分開執行。

這個完整範例會在兩秒後排程一個回呼,並在結束之前等待它:

using System;
using System.Threading;

class DelayExample
{
    static void Main()
    {
        using (var completed = new ManualResetEvent(false))
        using (var timer = new System.Threading.Timer(
            state =>
            {
                Console.WriteLine("Timer callback.");
                completed.Set();
            },
            null,
            2000,
            Timeout.Infinite))
        {
            Console.WriteLine("Timer scheduled.");
            completed.WaitOne();
        }
    }
}

到期時間會將第一個回呼排程在建構後的兩秒。Timeout.Infinite 可防止排程另一個回呼。此等待會讓這個主控台範例保持運作夠長的時間以接收回呼;具有自身事件迴圈的應用程式可以改用該存續時間。

結論

當封鎖目前執行緒可以接受時,請使用 Thread.Sleep() 進行簡單的同步暫停。在非同步程式碼中,請優先使用搭配 await 的 Task.Delay(),以便方法在等待期間可以讓出執行權。當您需要以回呼為基礎的排程時,請選擇 System.Timers.Timer 或 System.Threading.Timer,並以 AutoReset 或計時器週期來控制重複。計時器回呼會排程工作;它們不會暫停循序執行,且其時間並非精確的實際時鐘保證。