曂沅仴駦 发表于 2025-4-14 02:54:19

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

目次
1. System.Threading.Timer
2. System.Timers.Timer
3. System.Windows.Forms.Timer
总结对比
System.Timers.Timer 更新 UI的示例
方法1:利用 SynchronizationContext 更新 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 或特定线程交互的任务。
示例代码:
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 线程交互的任务。
示例代码:
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 的场景。
[*]对计时精度要求不高的任务。
[*]不必要后台线程支持的简单定时任务。
示例代码:
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

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

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,以避免潜在的内存泄漏或非常。
 

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
页: [1]
查看完整版本: 聊透多线程编程-线程池-7.C# 三个Timer类