問題一:這里加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.Timer
的 Elapsed
事件默認由線程池線程觸發。如果定時任務(如代碼中的 TransferAllDirectories
)的執行時間超過了定時器的間隔時間,就會出現以下問題:
- 第一個任務還沒執行完,第二個定時器事件就被觸發,導致兩個線程同時執行
OnTimerElapsed
中的邏輯。 - 這可能引發資源競爭(如同時操作同一文件/目錄),導致數據錯亂或異常。
lock
的作用就是保證同一時間只有一個線程能進入被鎖定的代碼塊,避免這種并發沖突。
lock
的工作原理
lock
本質上是基于 .NET 的** Monitor 同步機制**實現的,其核心邏輯可以簡化為:
- 當線程進入
lock (_lock)
代碼塊時,會嘗試獲取_lock
對象的「獨占鎖」。 - 如果鎖未被占用,當前線程會獲取鎖并執行代碼塊。
- 如果鎖已被其他線程占用,當前線程會進入阻塞狀態,等待鎖被釋放。
- 當線程執行完代碼塊后,會自動釋放鎖,讓其他等待的線程可以獲取鎖并執行。
為什么用 _lock
這個對象?
lock
需要一個「同步對象」(這里是 _lock
),它的作用是:
- 作為鎖的「標識」,不同的同步對象對應不同的鎖。
- 必須是引用類型(如
object
),因為值類型會被裝箱,每次lock
都會創建新對象,導致鎖失效。 - 通常定義為
private readonly
,確保:- 外部代碼無法訪問該對象(避免外部誤操作導致鎖失控)。
- 對象引用不會被修改(保證始終用同一個對象作為鎖標識)。
這段代碼中 lock
的實際效果
假設定時器間隔為 5 分鐘,但 TransferAllDirectories
某次執行花了 7 分鐘:
- 第一個線程在第 0 分鐘進入代碼塊,獲取
_lock
鎖。 - 第 5 分鐘時,定時器再次觸發
Elapsed
事件,第二個線程嘗試進入代碼塊,但此時鎖被第一個線程占用,只能等待。 - 第 7 分鐘,第一個線程執行完并釋放鎖,第二個線程立即獲取鎖并開始執行。
這樣就避免了兩個線程同時執行文件轉移邏輯,保證了操作的安全性。
注意點
lock
會導致等待的線程阻塞,因此被鎖定的代碼塊應盡量簡短,避免長時間占用鎖影響性能。- 不要用
string
或this
作為同步對象:string
有字符串駐留機制,可能導致不同地方的鎖沖突;this
可能被外部代碼訪問,存在安全風險。 - 此代碼中
_timer.AutoReset = true
表示定時器會重復觸發,結合lock
可以完美解決「任務執行時間超過間隔」的并發問題。