首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> .NET 網頁程式設計 >> 關於.NET

警惕匿名方法捕捉變數造成的共用陷阱與解決方案

     編輯:關於.NET

匿名方法與閉包機制

匿名方法 (C# 2.0) 與 Lambda (C# 3.0) 形成閉包,可存取區域變數與類別成員。編譯器實際生成一個私有輔助類別,將需捕捉的區域變數「提升」為該類別的公開欄位,委托指向該實例的方法。

問題案例:批次資料匯入搭配 ThreadPool

List<Item> batchItems = new List<Item>();
foreach (var item in ...) {
    batchItems.Add(item);
    if (batchItems.Count > 1000) {
        ThreadPool.QueueUserWorkItem(o => {
            var db = new DataContext();
            db.Items.InsertAllOnSubmit(batchItems); // 捕捉 batchItems
            db.SubmitChanges();
        });
        batchItems = new List<Item>(); // 立即重新指派!
    }
}

錯誤現象:資料遺失、例外亂噴。

根因分析:變數捕捉與共用

編譯器生成輔助類別 AutoGeneratedClass,欄位 m_batchItems。Lambda 委托與主迴圈共用同一實例。主迴圈在排入執行緒池後 立即重新指派 batchItems = new List...,導致 m_batchItems 指向新列表。執行緒池稍後執行時讀取的是「當前」指向的列表,造成:

  • 資料遺失:舊列表被遺棄,未送入資料庫。
  • 重複送入/例外:若時機湊巧,同一批資料可能被多個執行緒送入。

解決方案:明確封裝每批次資料

class WrapperClass {
    private List<Item> m_items;
    public WrapperClass(List<Item> items) { m_items = items; }
    public void WaitCallback(object o) {
        var db = new DataContext();
        db.Items.InsertAllOnSubmit(m_items);
        db.SubmitChanges();
    }
}

// 使用
ThreadPool.QueueUserWorkItem(new WrapperClass(batchItems).WaitCallback);
batchItems = new List<Item>();

每次建立新 WrapperClass 實例,鎖定該批次資料,避免共用變更。

延伸:Lambda 運算式樹同樣受影響

若 Lambda 建立運算式樹 (Expression<Func...>) 並捕捉區域變數,執行/解析時取得的是「當前」值,非建立時值。

對比:Java 匿名類別強制 final

Java 要求捕捉的區域變數必須 final,編譯期防止後續修改,從語言層面杜絕此類問題。

總結檢查清單

  • 委托不會立即同步執行(如 ThreadPool、Timer、非同步回呼)。
  • 委托捕捉了方法區域變數。
  • 建立委托後,程式碼還會修改該變數。
  • → 必須封裝/複製變數值,打破共用。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved