欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

C#警惕匿名方法造成的變量共享實例分析

 更新時間:2015年11月13日 15:50:45   作者:老趙  
這篇文章主要介紹了C#警惕匿名方法造成的變量共享,以實例形式分析了C#的匿名方法造成變量共享的原因及對應的解決方法,具有一定參考借鑒價值,需要的朋友可以參考下

本文實例講述了C#警惕匿名方法造成的變量共享。分享給大家供大家參考,具體如下:

匿名方法

匿名方法是.NET 2.0中引入的高級特性,“匿名”二字說明它可以把實現(xiàn)內(nèi)聯(lián)地寫在一個方法中,從而形成一個委托對象,而不用有明確地方法名,例如:

static void Test()
{
  Action<string> action = delegate(string value)
  {
    Console.WriteLine(value);
  };
  action("Hello World");
}

但是匿名方法的關鍵并不僅于“匿名”二字。其最強大的特性就在于匿名方法形成了一個閉包,它可以作為參數(shù)傳遞到另一個方法中去,但同時也能訪問方法的局部變量和當前類中的其它成員。例如:

class TestClass
{
  private void Print(string message)
  {
    Console.WriteLine(message);
  }
  public void Test()
  {
    string[] messages = new string[] { "Hello", "World" };
    int index = 0;
    Action<string> action = (m) =>
    {
      this.Print((index++) + ". " + m);
    };
    Array.ForEach(messages, action);
    Console.WriteLine("index = " + index);
  }
}

如上所示,在TestClass的Test方法中,action委托調(diào)用了同在TestClass類中的私有方法Print,并對Test方法中的局部變量index進行了讀寫。在加上C# 3.0中Lambda表達式的新特性,匿名方法的使用得到了極大的推廣。不過,如果使用不當,匿名方法也容易造成難以發(fā)現(xiàn)的問題。

問題案例

某位兄弟最近在一個簡單的數(shù)據(jù)導入程序,主要工作是從文本文件中讀取數(shù)據(jù),進行分析和重組,然后寫入數(shù)據(jù)庫。其邏輯大致如下:

static void Process()
{
  List<Item> batchItems = new List<Item>();
  foreach (var item in ...)
  {
    batchItems.Add(item);
    if (batchItems.Count > 1000)
    {
      DataContext db = new DataContext();
      db.Items.InsertAllOnSubmit(batchItems);
      db.SubmitChanges();
      batchItems = new List<Item>();
    }
  }
}

每次從數(shù)據(jù)源中讀取數(shù)據(jù)后,添加到batchItems列表中,當batchItems滿1000條時便進行一次提交。這段代碼功能運行正常,可惜時間卡在了數(shù)據(jù)庫提交上。數(shù)據(jù)的獲取和處理很快,但是提交一次就要花較長時間。于是想想,數(shù)據(jù)提交和數(shù)據(jù)處理不會有資源上的沖突,那么就把數(shù)據(jù)提交放在另外一個線程上進行處理吧!于是,使用ThreadPool來改寫代碼:

static void Process()
{ 
  List<Item> batchItems = new List<Item>();
  foreach (var item in ...)
  {
    batchItems.Add(item);
    if (batchItems.Count > 1000)
    {
      ThreadPool.QueueUserWorkItem((o) =>
      {
        DataContext db = new DataContext();
        db.Items.InsertAllOnSubmit(batchItems);
        db.SubmitChanges();
      });
      batchItems = new List<Item>();
    }
  }
}

現(xiàn)在,我們將數(shù)據(jù)提交操作交給ThreadPoll執(zhí)行,當線程池中有額外線程時,就會發(fā)起數(shù)據(jù)提交操作。而數(shù)據(jù)提交操作不會阻塞數(shù)據(jù)處理,因此按照那位兄弟的意圖,數(shù)據(jù)會不斷進行處理,最后只要等待所有數(shù)據(jù)庫提交完成就可以了。思路很好,可惜運行時發(fā)現(xiàn),原本(不利用多線程時)運行正常的代碼,如今會“莫名其妙”地拋出異常。更為奇怪的是,數(shù)據(jù)庫中的數(shù)據(jù)出現(xiàn)了丟失的情況:處理了并“提交”了一百萬條數(shù)據(jù),但是數(shù)據(jù)庫里卻少了一部分。于是對著代碼左看右看,百思不得其解。

您看出問題原因來了嗎?

分析原因

要發(fā)現(xiàn)問題所在,我們必須了解匿名方法在.NET環(huán)境中的實現(xiàn)方式。

.NET中本沒有什么“匿名方法”,也沒有類似的新特性?!澳涿椒ā蓖耆怯删幾g器施展的魔法,它會將匿名方法中需要訪問的所有成員一起包含在閉包中,確保所有的成員調(diào)用都符合.NET標準。例如在文章第一節(jié)中的第2個示例,實際上由編譯器處理之后就變成了如下的樣子(自然字段名經(jīng)過“友好化”處理):

class TestClass
{
  ...
  private sealed class AutoGeneratedHelperClass
  {
    public TestClass m_testClassInstance;
    public int m_index;
    public void Action(string m)
    {
      this.m_index++;
      this.m_testClassInstance.Print(m);
    }
  }
  public void TestAfterCompiled()
  {
    AutoGeneratedHelperClass helper = new AutoGeneratedHelperClass();
    helper.m_testClassInstance = this;
    helper.m_index = 0;
    string[] messages = new string[] { "Hello", "World" };
    Action<string> action = new Action<string>(helper.Action);
    Array.ForEach(messages, action);
    Console.WriteLine(helper.m_index);
  }
}

由此就可以看出編譯器是如何實現(xiàn)一個閉包的:

編譯器自動生成一個私有的內(nèi)部輔助類,并將其設為sealed,這個類的實例將成為一個閉包對象。

如果匿名方法需要訪問方法的參數(shù)或局部變量,那么該參數(shù)或局部變量將“升級”成為輔助類中的公有Field字段。

如果匿名方法需要訪問類中的其它方法,那么輔助類中將保存類的當前實例。

值得一提的是,在實際情況下以上三點理論都皆可能不滿足。在某些特別簡單的情況下(例如匿名方法中完全不涉及局部變量和其他方法),編譯器只會簡單生成一個靜態(tài)的方法來構造一個委托實例,因為這樣可以獲得更好的性能。

對于之前的案例,我們現(xiàn)在也將它進行一番改寫,這樣便可“避免”使用匿名對象,也可以清楚地展現(xiàn)出問題原因:

private class AutoGeneratedClass
{
  public List<Item> m_batchItems;
  public void WaitCallback(object o)
  {
    DataContext db = new DataContext();
    db.Items.InsertAllOnSubmit(this.m_batchItems);
    db.SubmitChanges();
  }
}
static void Process()
{ 
  var helper = new AutoGeneratedClass();
  helper.m_batchItems = new List<Item>();
  foreach (var item in ...)
  {
    helper.m_batchItems.Add(item);
    if (helper.m_batchItems.Count > 1000)
    {
      ThreadPool.QueueUserWorkItem(helper.WaitCallback);
      helper.m_batchItems = new List<Item>();
    }
  }
}

編譯器會自動生成一個AutoGeneratedClass類,并且在Process方法中使用這個類的實例來代替原來的batchItems局部變量。同樣,交給ThreadPool的委托對象也從匿名方法變成了AutoGeneratedClass實例的公有方法。因此線程池每次調(diào)用的便是該實例的WaitCallback方法。

現(xiàn)在問題應該一目了然了吧?每次把委托交給線程池之后,線程池并不會立即執(zhí)行,而會保留到合適的時間再進行。而WaitCallback方法在執(zhí)行時,它會讀取m_batchItems這個Field字段“當前”所引用的對象。而與此同時,Process方法已經(jīng)“拋棄”了原本我們要提交的數(shù)據(jù),因此會引起提交到數(shù)據(jù)庫中數(shù)據(jù)的丟失。同時,在準備每批次數(shù)據(jù)的過程中,很有可能會發(fā)起兩次數(shù)據(jù)提交,兩個線程提交同樣一批Item時,就拋出了所謂“莫名其妙”的異常。

解決問題

找到了問題所在,解決起來自然輕而易舉:

private class WrapperClass
{
  private List<Item> m_items;
  public WrapperClass(List<Item> items)
  {
    this.m_items = items;
  }
  public void WaitCallback(object o)
  {
    DataContext db = new DataContext();
    db.Items.InsertAllOnSubmit(this.m_items);
    db.SubmitChanges();
  }
}
static void Process()
{
  List<Item> batchItems = new List<Item>();
  foreach (var item in ...)
  {
    batchItems.Add(item);
    if (batchItems.Count > 1000)
    {
      ThreadPool.QueueUserWorkItem(
        new WrapperClass(batchItems).WaitCallback);
      batchItems = new List<Item>();
    }
  }
}

這里我們明確地準備一個封裝類,用它來保留我們需要提交的數(shù)據(jù)。而每次提交時則使用保留好的數(shù)據(jù),自然不會發(fā)生不該有的“數(shù)據(jù)共享”,從而避免了錯誤的發(fā)生1。

總結

匿名方法是強大的,但是也會造成一些令人難以察覺的陷阱。對于使用匿名方法創(chuàng)建的委托,如果不會立即同步執(zhí)行,并且其中使用了方法的局部變量,那么您就需要對其留個心眼了。因為此時“局部變量”事實上已經(jīng)由編譯器轉(zhuǎn)變成一個自動類的實例上的Field字段,而這個字段將被當前方法和委托對象共享。如果您在創(chuàng)建了委托對象之后還會修改共享的“局部變量”,那么請再三確認這樣做符合您的意圖,而不會造成問題。

此類問題也不光會出現(xiàn)在匿名方法中。如果您使用Lambda表達式創(chuàng)建了一個表達式樹,其中也用到了一個“局部變量”,那么表達式樹在解析或執(zhí)行時同樣也會獲取“當前”的值,而不是創(chuàng)建表達式樹時的值。

這也是為什么Java中的內(nèi)聯(lián)寫法——匿名類——如果要共享方法內(nèi)的“局部變量”,則必須將變量使用final關鍵字來修飾:這樣這個變量只能在聲明時賦值,避免了后續(xù)的“修改”可能會造成的“古怪問題”。

希望本文所述對大家C#程序設計有所幫助。

相關文章

  • C#通過xpath查找xml指定元素的方法

    C#通過xpath查找xml指定元素的方法

    這篇文章主要介紹了C#通過xpath查找xml指定元素的方法,涉及C#操作XML文件的技巧,非常具有實用價值,需要的朋友可以參考下
    2015-04-04
  • C#圖片處理3種高級應用

    C#圖片處理3種高級應用

    本文介紹C#圖片處理高級應用,這些功能并無多大技術含量。全部基于.Net Framework類庫完成,代碼中包含了C#圖片處理的一些基礎知識,與大家分享,個人能力有限,不足之處還請及時指正。
    2015-10-10
  • C#中方括號[]的語法及作用介紹

    C#中方括號[]的語法及作用介紹

    C#中方括號[]可用于數(shù)組,索引、屬性,更重要的是用于外部DLL類庫的引用。
    2013-04-04
  • C#制作網(wǎng)站掛機程序的實現(xiàn)示例

    C#制作網(wǎng)站掛機程序的實現(xiàn)示例

    本文主要介紹了C#制作網(wǎng)站掛機程序,文中通過示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2021-10-10
  • C#生成word記錄實例解析

    C#生成word記錄實例解析

    這篇文章主要介紹了C#生成word記錄實例解析,很實用的功能,需要的朋友可以參考下
    2014-08-08
  • c#基礎系列之ref和out的深入理解

    c#基礎系列之ref和out的深入理解

    有過C#基礎知識的都應該清楚Ref和Out的使用方法,所以下面這篇文章主要給大家介紹了關于c#基礎系列之ref和out的相關資料,文中通過示例代碼介紹的非常詳細,需要的朋友可以參考借鑒,下面隨著小編來一起學習學習吧
    2018-09-09
  • C#中多態(tài)性的實現(xiàn)

    C#中多態(tài)性的實現(xiàn)

    這篇文章主要介紹了C#中多態(tài)性的實現(xiàn),文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2020-02-02
  • C#對DataTable里數(shù)據(jù)排序的方法

    C#對DataTable里數(shù)據(jù)排序的方法

    在日常開發(fā)過程中,有一個DataTable集合,里面有很多字段,現(xiàn)在要求針對某一列進行排序,如果該列為數(shù)字的話,進行ASC即可實現(xiàn),但是該字段類型為string,此時排序就有點不正確了
    2013-11-11
  • Unity多屏幕設置的具體方案

    Unity多屏幕設置的具體方案

    多屏幕指的是一個電腦主機,連接多個顯示器,本文主要介紹了Unity多屏幕設置的具體方案,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2022-05-05
  • C#實現(xiàn)Redis的分布式鎖

    C#實現(xiàn)Redis的分布式鎖

    我們在開發(fā)很多業(yè)務場景會使用到鎖,例如庫存控制,抽獎等。分布式與單機情況下最大的不同在于其不是多線程而是多進程。本文就來介紹一下,感興趣的可以了解一下
    2021-08-08

最新評論