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 或計時器週期來控制重複。計時器回呼會排程工作;它們不會暫停循序執行,且其時間並非精確的實際時鐘保證。