亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb

首頁 > 編程 > C# > 正文

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

2020-01-24 01:23:18
字體:
來源:轉載
供稿:網友

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

匿名方法

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

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

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

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委托調用了同在TestClass類中的私有方法Print,并對Test方法中的局部變量index進行了讀寫。在加上C# 3.0中Lambda表達式的新特性,匿名方法的使用得到了極大的推廣。不過,如果使用不當,匿名方法也容易造成難以發現的問題。

問題案例

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

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

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

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

您看出問題原因來了嗎?

分析原因

要發現問題所在,我們必須了解匿名方法在.NET環境中的實現方式。

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

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

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

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

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

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

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

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

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實例的公有方法。因此線程池每次調用的便是該實例的WaitCallback方法。

現在問題應該一目了然了吧?每次把委托交給線程池之后,線程池并不會立即執行,而會保留到合適的時間再進行。而WaitCallback方法在執行時,它會讀取m_batchItems這個Field字段“當前”所引用的對象。而與此同時,Process方法已經“拋棄”了原本我們要提交的數據,因此會引起提交到數據庫中數據的丟失。同時,在準備每批次數據的過程中,很有可能會發起兩次數據提交,兩個線程提交同樣一批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>();    }  }}

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

總結

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

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

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

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

發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb
久久偷看各类女兵18女厕嘘嘘| 97精品国产97久久久久久| 亚洲成人网久久久| 久久精品视频在线播放| 亚洲女成人图区| 亚洲国产美女精品久久久久∴| 91亚洲精品久久久久久久久久久久| 欧美在线视频网| 成人免费看吃奶视频网站| 国产成人鲁鲁免费视频a| 2019最新中文字幕| 亚洲精品欧美日韩专区| 亚洲国产精品久久久久| 久久偷看各类女兵18女厕嘘嘘| 97香蕉久久夜色精品国产| 日韩视频―中文字幕| 亚洲最大福利网| 国产精品视频不卡| xxxxx成人.com| 毛片精品免费在线观看| 亚洲毛片在线免费观看| 久久中文字幕视频| 精品在线观看国产| 欧美日韩成人在线视频| 久久久精品美女| 日韩国产高清污视频在线观看| 草民午夜欧美限制a级福利片| 热久久这里只有精品| 日韩电影大全免费观看2023年上| 九九九热精品免费视频观看网站| 91高清视频免费观看| 精品国产乱码久久久久久婷婷| 91久久国产综合久久91精品网站| 91精品久久久久| 国产精品96久久久久久| 久久国产精品久久精品| 77777亚洲午夜久久多人| 日韩欧美国产骚| 国产成人精品在线观看| 夜夜嗨av色一区二区不卡| 91精品国产91久久久久久| 另类视频在线观看| 欧美视频免费在线观看| 91九色国产视频| 亚洲第一福利在线观看| 成人激情综合网| 亚洲天堂av在线播放| 在线观看国产精品91| 国产精品r级在线| 亚洲天堂第一页| 欧美最顶级丰满的aⅴ艳星| 亚洲裸体xxxx| 色播久久人人爽人人爽人人片视av| 中文字幕日韩精品在线| 欧美日韩ab片| 欧美高清在线观看| 国产999在线| 亚洲精品日韩在线| 中文字幕亚洲一区二区三区五十路| 亚洲国产精品悠悠久久琪琪| 亚洲日本中文字幕免费在线不卡| 亚洲一级黄色片| 日韩激情视频在线播放| 久久99青青精品免费观看| 欧美一级电影免费在线观看| 欧美性xxxxxxx| 一道本无吗dⅴd在线播放一区| 欧美在线性爱视频| 国产精品亚洲精品| 国产精品第10页| 国产97人人超碰caoprom| 日韩av综合中文字幕| 亚洲欧洲国产伦综合| 欧美电影免费观看网站| 黑人极品videos精品欧美裸| 日本sm极度另类视频| 日韩欧美亚洲国产一区| 国产日本欧美视频| 国产精品视频不卡| 亚洲精品国产精品久久清纯直播| 久久国产精品首页| 国产91精品久久久久久| 日韩中文字幕免费| 亚洲欧美制服中文字幕| 欧美大人香蕉在线| 欧美中文在线字幕| 91精品久久久久久久久中文字幕| 精品国产乱码久久久久久虫虫漫画| 国产91免费看片| 久久久亚洲福利精品午夜| 精品福利一区二区| 成人做爰www免费看视频网站| 日本韩国欧美精品大片卡二| 亚洲肉体裸体xxxx137| 777777777亚洲妇女| 91亚洲精品久久久久久久久久久久| 中文国产成人精品久久一| 国产丝袜精品视频| 26uuu另类亚洲欧美日本老年| 日韩精品视频在线观看网址| 亚洲无限av看| 最新日韩中文字幕| 狠狠爱在线视频一区| 久久成人一区二区| 国产精品专区一| 国产精品老牛影院在线观看| 精品网站999www| 国产一区二区三区日韩欧美| 日韩精品免费综合视频在线播放| 91天堂在线视频| 久久亚洲国产精品成人av秋霞| 欧美大尺度激情区在线播放| 欧美激情综合色综合啪啪五月| 91久久中文字幕| 国模精品系列视频| 国产欧美一区二区三区视频| 一本久久综合亚洲鲁鲁| 98精品国产自产在线观看| 久久夜色精品国产| 欧美日韩国产在线看| 色婷婷av一区二区三区久久| 国产欧美一区二区白浆黑人| 国产黑人绿帽在线第一区| 欧美亚洲一区在线| 日韩久久免费电影| 日韩大胆人体377p| 成人免费淫片视频软件| 51色欧美片视频在线观看| www.亚洲男人天堂| 97人洗澡人人免费公开视频碰碰碰| 在线观看视频99| 大量国产精品视频| 日韩成人中文字幕在线观看| 亚洲欧美在线磁力| 日韩美女免费观看| 国模极品一区二区三区| 川上优av一区二区线观看| 国产69精品久久久久9| 成人h猎奇视频网站| 国产免费一区二区三区香蕉精| 综合欧美国产视频二区| 国产精品视频一区二区三区四| 国产日韩av在线播放| 51ⅴ精品国产91久久久久久| 成人写真福利网| 国产精品第一视频| 久久频这里精品99香蕉| 日韩av在线网| 国产噜噜噜噜久久久久久久久| 97免费视频在线| 成人两性免费视频| 色无极影院亚洲| 亚洲欧美另类自拍| 欧美福利小视频| 懂色av中文一区二区三区天美| 中文欧美在线视频| 精品久久久久久中文字幕一区奶水| 亚洲精品在线视频| 欧美性20hd另类| 亚洲一区二区中文| 欧美黑人巨大xxx极品| 性日韩欧美在线视频| 国产精品久久久久久久av大片| 国产一区二区三区三区在线观看|