C#_定时器_解析

发布于:2025-07-26 ⋅ 阅读:(10) ⋅ 点赞:(0)

问题一:这里加lock是啥意思?它的原理是, 为什么可以锁住?

private readonly Timer _timer;
        /// <summary>
        /// 构造函数中初始化定时器
        /// </summary>
        public FtpTransferService()
        {
            // 初始化定时器
            _timer = new Timer(_intervalMinutes * 60 * 1000);
            _timer.Elapsed += OnTimerElapsed;//绑定定时触发的事件
            _timer.AutoReset = true;//定时器的属性,控制是否在每次间隔后重复触发事件
        }
        /// <summary>
        /// 定时器触发事件 - 增加锁防止并发
        /// </summary>
        private void OnTimerElapsed(object sender, ElapsedEventArgs e)
        {
            lock (_lock)
            {
                try
                {
                    WriteLog($"开始执行文件转移任务({DateTime.Now:yyyy-MM-dd HH:mm:ss})");
                    // 遍历所有目录(包括非最内层)并复制结构
                    TransferAllDirectories(_sourceRootPath);
                    WriteLog($"文件转移任务执行完成({DateTime.Now:yyyy-MM-dd HH:mm:ss})");
                }
                catch (Exception ex)
                {
                    WriteLog($"任务执行异常:{ex.Message}\r\n堆栈:{ex.StackTrace}", isError: true);
                }
            }
        }

在这段代码中,lock (_lock) 的作用是防止定时器事件被并发执行,确保 OnTimerElapsed 方法中的逻辑在同一时间只能被一个线程执行。

为什么需要加 lock

System.Timers.TimerElapsed 事件默认由线程池线程触发。如果定时任务(如代码中的 TransferAllDirectories)的执行时间超过了定时器的间隔时间,就会出现以下问题:

  • 第一个任务还没执行完,第二个定时器事件就被触发,导致两个线程同时执行 OnTimerElapsed 中的逻辑。
  • 这可能引发资源竞争(如同时操作同一文件/目录),导致数据错乱或异常。

lock 的作用就是保证同一时间只有一个线程能进入被锁定的代码块,避免这种并发冲突。

lock 的工作原理

lock 本质上是基于 .NET 的** Monitor 同步机制**实现的,其核心逻辑可以简化为:

  1. 当线程进入 lock (_lock) 代码块时,会尝试获取 _lock 对象的「独占锁」。
  2. 如果锁未被占用,当前线程会获取锁并执行代码块。
  3. 如果锁已被其他线程占用,当前线程会进入阻塞状态,等待锁被释放。
  4. 当线程执行完代码块后,会自动释放锁,让其他等待的线程可以获取锁并执行。

为什么用 _lock 这个对象?

lock 需要一个「同步对象」(这里是 _lock),它的作用是:

  • 作为锁的「标识」,不同的同步对象对应不同的锁。
  • 必须是引用类型(如 object),因为值类型会被装箱,每次 lock 都会创建新对象,导致锁失效。
  • 通常定义为 private readonly,确保:
    • 外部代码无法访问该对象(避免外部误操作导致锁失控)。
    • 对象引用不会被修改(保证始终用同一个对象作为锁标识)。

这段代码中 lock 的实际效果

假设定时器间隔为 5 分钟,但 TransferAllDirectories 某次执行花了 7 分钟:

  • 第一个线程在第 0 分钟进入代码块,获取 _lock 锁。
  • 第 5 分钟时,定时器再次触发 Elapsed 事件,第二个线程尝试进入代码块,但此时锁被第一个线程占用,只能等待。
  • 第 7 分钟,第一个线程执行完并释放锁,第二个线程立即获取锁并开始执行。

这样就避免了两个线程同时执行文件转移逻辑,保证了操作的安全性。

注意点

  • lock 会导致等待的线程阻塞,因此被锁定的代码块应尽量简短,避免长时间占用锁影响性能。
  • 不要用 stringthis 作为同步对象:string 有字符串驻留机制,可能导致不同地方的锁冲突;this 可能被外部代码访问,存在安全风险。
  • 此代码中 _timer.AutoReset = true 表示定时器会重复触发,结合 lock 可以完美解决「任务执行时间超过间隔」的并发问题。

网站公告

今日签到

点亮在社区的每一天
去签到