最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

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

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

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

匿名方法

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

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

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

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,并對(duì)Test方法中的局部變量index進(jìn)行了讀寫(xiě)。在加上C# 3.0中Lambda表達(dá)式的新特性,匿名方法的使用得到了極大的推廣。不過(guò),如果使用不當(dāng),匿名方法也容易造成難以發(fā)現(xiàn)的問(wèn)題。

問(wèn)題案例

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

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列表中,當(dāng)batchItems滿1000條時(shí)便進(jìn)行一次提交。這段代碼功能運(yùn)行正常,可惜時(shí)間卡在了數(shù)據(jù)庫(kù)提交上。數(shù)據(jù)的獲取和處理很快,但是提交一次就要花較長(zhǎng)時(shí)間。于是想想,數(shù)據(jù)提交和數(shù)據(jù)處理不會(huì)有資源上的沖突,那么就把數(shù)據(jù)提交放在另外一個(gè)線程上進(jìn)行處理吧!于是,使用ThreadPool來(lái)改寫(xiě)代碼:

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

您看出問(wèn)題原因來(lái)了嗎?

分析原因

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

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

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);
  }
}

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

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

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

如果匿名方法需要訪問(wèn)類中的其它方法,那么輔助類中將保存類的當(dāng)前實(shí)例。

值得一提的是,在實(shí)際情況下以上三點(diǎn)理論都皆可能不滿足。在某些特別簡(jiǎn)單的情況下(例如匿名方法中完全不涉及局部變量和其他方法),編譯器只會(huì)簡(jiǎn)單生成一個(gè)靜態(tài)的方法來(lái)構(gòu)造一個(gè)委托實(shí)例,因?yàn)檫@樣可以獲得更好的性能。

對(duì)于之前的案例,我們現(xiàn)在也將它進(jìn)行一番改寫(xiě),這樣便可“避免”使用匿名對(duì)象,也可以清楚地展現(xiàn)出問(wè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>();
    }
  }
}

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

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

解決問(wèn)題

找到了問(wèn)題所在,解決起來(lái)自然輕而易舉:

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>();
    }
  }
}

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

總結(jié)

匿名方法是強(qiáng)大的,但是也會(huì)造成一些令人難以察覺(jué)的陷阱。對(duì)于使用匿名方法創(chuàng)建的委托,如果不會(huì)立即同步執(zhí)行,并且其中使用了方法的局部變量,那么您就需要對(duì)其留個(gè)心眼了。因?yàn)榇藭r(shí)“局部變量”事實(shí)上已經(jīng)由編譯器轉(zhuǎn)變成一個(gè)自動(dòng)類的實(shí)例上的Field字段,而這個(gè)字段將被當(dāng)前方法和委托對(duì)象共享。如果您在創(chuàng)建了委托對(duì)象之后還會(huì)修改共享的“局部變量”,那么請(qǐng)?jiān)偃_認(rèn)這樣做符合您的意圖,而不會(huì)造成問(wèn)題。

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

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

希望本文所述對(duì)大家C#程序設(shè)計(jì)有所幫助。

相關(guān)文章

  • C#檢查字符串是否是合法URL地址的方法

    C#檢查字符串是否是合法URL地址的方法

    這篇文章主要介紹了C#檢查字符串是否是合法URL地址的方法,涉及C#字符串判斷的相關(guān)技巧,需要的朋友可以參考下
    2015-05-05
  • 詳解c# 接口IDisposable的用法

    詳解c# 接口IDisposable的用法

    這篇文章主要介紹了詳解c# 接口IDisposable的用法,幫助大家更好的理解和學(xué)習(xí)c#,感興趣的朋友可以了解下
    2020-10-10
  • 輸出的文本實(shí)現(xiàn)對(duì)齊的方法(超簡(jiǎn)單)

    輸出的文本實(shí)現(xiàn)對(duì)齊的方法(超簡(jiǎn)單)

    下面小編就為大家分享一篇c#輸出的文本實(shí)現(xiàn)對(duì)齊的方法,特別簡(jiǎn)單!希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧
    2017-12-12
  • C# 無(wú)邊框窗體之窗體移動(dòng)實(shí)現(xiàn)代碼

    C# 無(wú)邊框窗體之窗體移動(dòng)實(shí)現(xiàn)代碼

    這篇文章介紹了C# 無(wú)邊框窗體之窗體移動(dòng)實(shí)現(xiàn)代碼,有需要的朋友可以參考一下
    2013-10-10
  • c#調(diào)用jar包的方法步驟(非常詳細(xì))

    c#調(diào)用jar包的方法步驟(非常詳細(xì))

    這篇文章主要給大家介紹了關(guān)于c#調(diào)用jar包的方法步驟,文中通過(guò)圖文介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2021-01-01
  • 最新評(píng)論

    阜新市| 隆林| 宁安市| 汉寿县| 柳江县| 海丰县| 鸡东县| 扶风县| 河南省| 宜川县| 永兴县| 乌恰县| 侯马市| 虹口区| 潼关县| 象山县| 兴仁县| 泰顺县| 诸城市| 济南市| 华池县| 泾川县| 遵化市| 波密县| 拜城县| 永仁县| 稻城县| 宜昌市| 阳泉市| 乌鲁木齐县| 遂昌县| 望江县| 长白| 仁寿县| 炉霍县| 竹山县| 辽阳市| 土默特左旗| 丰顺县| 抚州市| 禄丰县|