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 或计时器周期来控制重复。计时器回调会调度工作;它们不会暂停顺序执行,且其时间并非精确的实际时钟保证。