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