成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

警惕.NET中的匿名方法造成變量共享

開發 后端
.NET中的匿名方法是一柄雙刃劍,稍有不慎就會變成陷阱,本文將幫助大家避開這一陷阱。

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

static void Test()
{
    Action 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 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 batchItems = new List();
    foreach (var item in ...)
    {
        batchItems.Add(item);

        if (batchItems.Count > 1000)
        {
            DataContext db = new DataContext();
            db.Items.InsertAllOnSubmit(batchItems);
            db.SubmitChanges();

            batchItems = new List();
        }
    }
}

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

static void Process()
{
    List batchItems = new List();
    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();
        }
    }
}

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

您看出問題原因來了嗎?

分析原因

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

.NET中本沒有什么“匿名方法”,也沒有類似的新特性。“匿名方法”完全是由編譯器施展的魔法,它會將匿名方法中需要訪問的所有成員一起包含在閉包中,確保所有的成員調用都符合.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 action = new Action(helper.Action);
        Array.ForEach(messages, action);

        Console.WriteLine(helper.m_index);
    }
}

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

編譯器自動生成一個私有的內部輔助類,并將其設為sealed,這個類的實例將成為一個閉包對象。
如果匿名方法需要訪問方法的參數或局部變量,那么該參數或局部變量將“升級”成為輔助類中的公有Field字段。

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

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

private class AutoGeneratedClass
{
    public List 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();

    foreach (var item in ...)
    {
        helper.m_batchItems.Add(item);

        if (helper.m_batchItems.Count > 1000)
        {
            ThreadPool.QueueUserWorkItem(helper.WaitCallback);
            helper.m_batchItems = new List();
        }
    }
}

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

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

解決問題

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

private class WrapperClass
{
    private List m_items;

    public WrapperClass(List 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 batchItems = new List();
    foreach (var item in ...)
    {
        batchItems.Add(item);

        if (batchItems.Count > 1000)
        {
            ThreadPool.QueueUserWorkItem(
                new WrapperClass(batchItems).WaitCallback);

            batchItems = new List();
        }
    }
}

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

總結

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

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

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

【編輯推薦】

  1. .NET動靜結合編程 接口和委托的約束強度
  2. 使用.NET Array類的Sort方法分類數值
  3. 詳論在.NET中定義結構設計標準
責任編輯:彭凡 來源: 博客園
相關推薦

2024-06-12 08:21:07

Deadlock死鎖版本

2010-01-05 15:43:13

.NET Framew

2010-01-12 18:28:28

VB.NET共享變量

2009-11-03 10:51:33

VB.NET共享

2011-05-20 16:34:35

VB.NET

2010-01-18 14:54:00

VB.NET共享成員變

2009-09-21 08:50:42

.NET中文變量

2009-11-03 11:40:37

VB.NET共享變量

2010-01-21 16:37:56

VB.NET變量聲明

2023-11-27 16:20:25

2009-08-05 17:11:38

匿名方法的作用

2010-12-16 13:56:57

匿名對象.NET

2024-11-12 07:28:39

2009-07-23 16:21:07

static變量ASP.NET

2020-11-03 06:57:10

MyBatis數據庫

2019-09-18 15:20:16

MyBatisSQL數據庫

2022-02-24 23:59:05

人工智能下棋隱私

2009-08-20 16:28:45

C#匿名方法

2009-08-20 16:15:19

C# 匿名方法

2011-06-08 11:05:38

getpost
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 成人网视频 | 国产www.| 日韩一区二区三区在线视频 | cao视频| 国产一区91在线 | 国产精品久久久久久久久久久久 | 麻豆精品国产91久久久久久 | 日韩在线视频一区 | 一区二区三区亚洲 | 精品久久精品 | www.成人免费视频 | 国产精品中文字幕一区二区三区 | 日韩伦理一区二区 | 日韩精品成人av | 国产精品99久久久久久宅男 | 在线观看视频91 | 综合国产 | 91精品国产91久久久久久不卞 | 国产精品福利一区二区三区 | 嫩呦国产一区二区三区av | 精品不卡 | 一区二区免费在线视频 | 国产一区二区三区四区三区四 | 免费观看国产视频在线 | 日韩一区二区三区在线观看 | 中国一级特黄毛片大片 | 欧美日韩综合 | 伊人春色在线 | 欧美激情久久久久久 | 精品久久久久久 | 女同久久另类99精品国产 | 日韩精品一区二区三区在线播放 | 亚洲在线一区 | 成年人免费在线视频 | 精品国产一区二区三区性色av | av一级久久| 欧美日韩精品在线免费观看 | 91视视频在线观看入口直接观看 | 伊人一区 | 色婷婷综合网 | 99精品国产一区二区青青牛奶 |