聊透多线程编程-线程池-7.C# 三个Timer类

目录

[1. System.Threading.Timer](#1. System.Threading.Timer)

[2. System.Timers.Timer](#2. System.Timers.Timer)

[3. System.Windows.Forms.Timer](#3. System.Windows.Forms.Timer)

总结对比

[System.Timers.Timer 更新 UI的示例](#System.Timers.Timer 更新 UI的示例)

[方法1:使用 SynchronizationContext 更新 UI](#方法1:使用 SynchronizationContext 更新 UI)

[方法2:使用 使用 Control.Invoke 更新 UI](#方法2:使用 使用 Control.Invoke 更新 UI)


今天要说的这三个Timer类分别是System.Threading.Timer 、System.Timers.Timer和System.Windows.Forms.Timer,其中前两个都是在线程池线程上运行的,而System.Windows.Forms.Timer则是运行在UI线程的,之所以将第三个与前两个放在一起比较,是因为他们比较像,平时如果不注意的话,容易搞混淆。


1. System.Threading.Timer

System.Threading.Timer 是 .NET 中提供的一个轻量级、高性能的计时器,适用于需要精确控制定时任务的场景。它通过回调函数的方式工作,没有事件模型。

特点:

  • 底层实现:最轻量级的计时器,直接基于线程池运行。
  • 回调机制:通过回调函数执行任务,没有事件模型。
  • 灵活性:支持动态调整定时器的行为(如延迟启动、重复触发等)。
  • 线程模型 :在 线程池线程 上运行,适合后台任务。
  • 异常处理:需要手动捕获和处理异常,否则可能导致程序崩溃。

适用场景:

  • 高性能、低级别的计时需求。
  • 需要完全控制计时器行为的场景。
  • 不需要与 UI 或特定线程交互的任务。

示例代码:

cs 复制代码
using System;
using System.Threading;

class Program
{
    static void Main()
    {
        Timer timer = new Timer(TimerCallback, null, 0, 1000); // 第一次立即触发,之后每秒触发一次

        Console.WriteLine("按 Enter 键退出...");
        Console.ReadLine();
    }

    private static void TimerCallback(object state)
    {
        Console.WriteLine("Callback: " + DateTime.Now);
    }
}

缺点:

  • 无事件模型:不支持事件驱动开发,使用起来不够直观。
  • 异常风险:如果回调函数中抛出未捕获的异常,可能会导致程序崩溃。
  • UI 线程限制:不能直接更新 UI,需额外处理线程同步问题。

注意事项:

  • 在回调函数中避免长时间阻塞操作,以免影响线程池性能。
  • 如果需要与 UI 线程交互,应使用 SynchronizationContext 或其他线程同步机制。
  • 使用完成后需要调用 Dispose() 方法释放资源。

2. System.Timers.Timer

System.Timers.Timer 是基于 System.Threading.Timer 构建的一个更高层次的计时器,提供了事件驱动模型,适合需要定期执行后台任务的应用程序。

特点:

  • 高层封装:基于 System.Threading.Timer 实现,提供了更高级别的抽象。
  • 事件驱动:使用 Elapsed 事件处理定时任务。
  • 易用性:支持属性(如 Interval、AutoReset),可以动态调整计时器行为。
  • 线程模型 :默认在 线程池线程 上运行,但可以通过 SynchronizingObject 属性绑定到特定线程(如 UI 线程)。
  • 异常处理:Elapsed 事件中的异常会被自动捕获并记录,不会导致程序崩溃。

适用场景:

  • 需要定期执行后台任务的应用程序。
  • 对线程安全性和易用性要求较高的场景。
  • 需要跨线程通信或与 UI 线程交互的任务。

示例代码:

cs 复制代码
using System;
using System.Timers;

class Program
{
    static void Main()
    {
        Timer timer = new Timer(1000); // 每秒触发一次
        timer.Elapsed += OnTimedEvent; // 绑定事件处理程序
        timer.AutoReset = true;       // 是否重复触发
        timer.Enabled = true;         // 启动计时器

        Console.WriteLine("按 Enter 键退出...");
        Console.ReadLine();
    }

    private static void OnTimedEvent(object sender, ElapsedEventArgs e)
    {
        Console.WriteLine($"Elapsed at {e.SignalTime}");
    }
}

缺点:

  • 精度略低:由于其高层封装,可能存在一定的延迟。
  • 依赖线程池:仍基于线程池运行,可能在高负载情况下受到影响。
  • 资源管理:如果不正确释放资源,可能会导致内存泄漏。

注意事项:

  • 如果需要与 UI 线程交互,记得设置 SynchronizingObject 属性。
  • 使用完成后需要调用 Dispose() 方法释放资源。
  • 在 Elapsed 事件处理程序中避免长时间阻塞操作。

3. System.Windows.Forms.Timer

System.Windows.Forms.Timer 是专门为 Windows 窗体应用程序设计的计时器,它在 UI 线程上运行,适合用于更新 UI 控件。

特点:

  • UI 线程运行:在 UI 线程上运行,可以直接更新 UI 控件。
  • 简单易用:提供 Tick 事件,易于使用。
  • 无多线程支持:不适合后台任务或多线程环境。
  • 低精度:由于运行在 UI 线程上,受 UI 响应速度的影响,精度较低。

适用场景:

  • Windows 窗体应用程序中需要定期更新 UI 的场景。
  • 对计时精度要求不高的任务。
  • 不需要后台线程支持的简单定时任务。

示例代码:

cs 复制代码
using System;
using System.Windows.Forms;
using static System.Net.Mime.MediaTypeNames;

class Program : Form
{
    static void Main()
    {
        Application.Run(new Program());
    }

    public Program()
    {
        Timer timer = new Timer();
        timer.Interval = 1000; // 设置间隔时间(毫秒)
        timer.Tick += Timer_Tick; // 绑定事件处理程序
        timer.Start();
    }

    private void Timer_Tick(object sender, EventArgs e)
    {
        Console.WriteLine("Tick: " + DateTime.Now);
    }
}

缺点:

  • 低精度:受 UI 线程响应速度的影响,不适合高精度计时。
  • 单线程限制:只能在 UI 线程上运行,无法用于后台任务。
  • 性能瓶颈:如果 UI 线程被阻塞,计时器也会停止工作。

注意事项:

  • 不要在 Tick 事件中执行耗时操作,以免阻塞 UI 线程。
  • 仅适用于 Windows 窗体应用程序,不适用于其他类型的应用程序(如控制台应用或 ASP.NET 应用)。
  • 如果需要更高的计时精度或后台任务支持,请选择其他计时器。

总结对比

|------------------------------------|--------------|-------------|------------|------------------|
| 计时器类型 | 运行线程 | 易用性 | 精度 | 适用场景 |
| System.Threading.Timer | 线程池线程 | 较低 | 高 | 高性能、低级别计时需求 |
| System.Timers.Timer | 线程池线程 | 较高 | 中 | 定期执行后台任务 |
| System.Windows.Forms.Timer | UI 线程 | 非常高 | 低 | 更新 Windows 窗体 UI |


System.Timers.Timer 更新 UI的示例

方法1:使用 SynchronizationContext 更新 UI

cs 复制代码
using System;
using System.Reflection.Emit;
using System.Timers;
using System.Windows.Forms;
using static System.Net.Mime.MediaTypeNames;

class Program : Form
{
    private Label label;
    private Timer timer;
    private SynchronizationContext uiContext;

    static void Main()
    {
        Application.Run(new Program());
    }

    public Program()
    {
        // 初始化 UI
        label = new Label
        {
            Text = "等待计时器更新...",
            AutoSize = true,
            Location = new System.Drawing.Point(10, 10)
        };
        this.Controls.Add(label);

        // 保存 UI 线程的上下文
        uiContext = SynchronizationContext.Current;

        // 初始化计时器
        timer = new Timer(1000); // 每秒触发一次
        timer.Elapsed += OnTimedEvent;
        timer.AutoReset = true;
        timer.Enabled = true;
    }

    private void OnTimedEvent(object sender, ElapsedEventArgs e)
    {
        // 使用 SynchronizationContext 切换到 UI 线程
        uiContext.Post(_ =>
        {
            label.Text = $"更新时间: {DateTime.Now}";
        }, null);
    }
}

方法2:使用 使用 Control.Invoke 更新 UI

cs 复制代码
using System;
using System.Reflection.Emit;
using System.Timers;
using System.Windows.Forms;
using static System.Net.Mime.MediaTypeNames;

class Program : Form
{
    private Label label;
    private Timer timer;

    static void Main()
    {
        Application.Run(new Program());
    }

    public Program()
    {
        // 初始化 UI
        label = new Label
        {
            Text = "等待计时器更新...",
            AutoSize = true,
            Location = new System.Drawing.Point(10, 10)
        };
        this.Controls.Add(label);

        // 初始化计时器
        timer = new Timer(1000); // 每秒触发一次
        timer.Elapsed += OnTimedEvent;
        timer.AutoReset = true;
        timer.Enabled = true;
    }

    private void OnTimedEvent(object sender, ElapsedEventArgs e)
    {
        // 使用 Control.Invoke 切换到 UI 线程
        if (label.InvokeRequired)
        {
            label.Invoke(new Action(() =>
            {
                label.Text = $"更新时间: {DateTime.Now}";
            }));
        }
        else
        {
            label.Text = $"更新时间: {DateTime.Now}";
        }
    }
}

关键点解析

线程问题:

System.Timers.Timer 的 Elapsed 事件运行在 线程池线程 上。

直接从非 UI 线程更新 UI 会导致异常,因此需要切换到 UI 线程。
解决方案:

SynchronizationContext:用于捕获和存储当前线程(通常是 UI 线程)的上下文,并通过 Post 或 Send 方法将操作切换回该线程。

Control.Invoke:检查控件是否需要调用 Invoke,如果是,则通过 Invoke 将操作委托给 UI 线程执行。
性能考虑:

如果更新频率较高,建议减少不必要的线程切换操作,以避免性能开销。
资源管理:

在窗体关闭时,记得停止并释放 System.Timers.Timer,以避免潜在的内存泄漏或异常。

相关推荐
云草桑6 分钟前
C# net CMS相关开源软件 技术选型 可行性分析
typescript·c#·cms
泽55318016 分钟前
4.13日总结
开发语言·python·pycharm
CloudWeGo19 分钟前
Kitex Release v0.13.0正式发布!
后端·架构·github
快乐源泉29 分钟前
【设计模式】状态模式,为何状态切换会如此丝滑?
后端·设计模式·go
衝衝29 分钟前
Spring Data JPA技术深度解析
后端
玛奇玛丶31 分钟前
java的Random居然是假随机
后端
码农小站32 分钟前
Elasticsearch 深度分页踩坑指南:从报错到终极解决方案
后端
InsightFuture32 分钟前
Java金额转换实战:从数字到中文大写金额的完整实现
后端
_x_w44 分钟前
【10】数据结构的矩阵与广义表篇章
开发语言·数据结构·笔记·python·线性代数·链表·矩阵
kovlistudio1 小时前
红宝书第三十六讲:持续集成(CI)配置入门指南
开发语言·前端·javascript·ci/cd·npm·node.js