2014-03-06 25 views
1

我讀過一些關於這裏這個其他職位,但我的問題似乎在(Is the order of static class initialization in C# deterministic?)貼在這裏,一般的智慧和幾個相關的臉飛帖子。靜態初始化VS靜態構造函數,執行命令,釋放模式奇怪的行爲

我有一個ASP.Net 4.0應用程序,和我的Application_Start()方法,通過觸摸的很多東西跑,讓他們成立。其中一件事是爲我們的DAL初始化我們的db連接字符串,其他的初始化其他變量。幾天前,我們的發佈版本開始爆炸了,並且在添加了大量的調試日誌記錄之後,原因竟然是在Application_Start()正式輸入之前調用的新靜態初始化程序並試圖訪問我們的DAL之前已初始化。這似乎與我在其他帖子中看到的有關靜態構造函數和初始化程序執行順序的看法相違背。

具體而言,靜態初始化程序是而不是正在調用它們的代碼中運行;發佈版本中的堆棧跟蹤指示發佈編譯將它們混洗成的一部分,輸入 Application_Startup()。換句話說,堆棧跟蹤在啓動時會在堆棧上顯示Application_Startup() - 但是沒有與其關聯的行號。 DAL初始化的堆棧跟蹤確實顯示行號。

要充實一些僞代碼,我們有

Application_Start() 
{ 
    DAL.Inst.GetSetting("ConnectionString"); // tickles the singleton to get the DAL initialized. 

    // numerous other lines of code  
    if (!AppUtil.IsAdminAccountSetup) 
    { // Do admin account setup 
    } 
    // More code 
} 

public class DAL 
{ 
    private static DAL _singleton; 

    static DAL() 
    { 
     _singleton = new DAL(); 
     _singleton.Initialize(); 
    } 

    public static DAL Inst 
    { 
     get { return _singleton; } 
    } 
    // all the other methods 
} 

public class AppUtil 
{ 
    private static bool _isAdminSetup = InitializeAdminSetup(); 
    public static bool IsAdminAccountSetup 
    { 
     get { return _isAdminSetup; } 
    } 

    private static bool InitializeAdminSetup() 
    { 
     // Call DAL for query 
    } 

    private static bool _isProductRegistered = InitializeProdReg(); 
    public static bool IsProductRegistered 
    { 
     get { return _isProductRegistered; } 
    } 

    private static bool InitializeProdReg() 
    { 
     // Call DAL for query 
    } 
} 

AppUtil最近有IsProductRegistered布爾補充說,這似乎是,當事情南下。

Application_Start()中未調用AppUtil.IsProductRegistered,但是當我們查看有關DAL查詢順序的堆棧跟蹤時,我們看到AppUtil.IsProductRegistered的初始化程序調用DAL(在DAL.Inst被觸摸之前)和from Application_Start()(沒有行號)。

然後,我們看到在連接字符串初始化得到的DAL靜態構造函數的一部分,這可以追溯到在的Application_Start(在DAL.Inst參考) - 在這裏它確實顯示的行數。

如果我們改變無論是在AppUtil靜態initialzers到一個單一的靜態構造函數,東西回去參考順序初始化和吹脹消失。

但是,整個情況在我看過的東西里面飛速發展.Net說它會做。

對不起,我希望有足夠的細節來解決這個難題。

+1

爲什麼「靜態initizalizer ...在Application_Start之前調用」令您驚訝?唯一的保證是在第一次使用該類之前調用​​靜態構造函數。不知道你在帖子中看到的是什麼類型的建議,除了「不要依賴靜態構造函數的任何特定順序/時間調用」。 –

+0

@AlexeiLevenkov靜態構造函數只會在第一次訪問之前立即運行。沒有靜態構造函數的類上的字段初始值設定項可以更早地運行。 – Loki

+0

我編輯了你的標題。請參閱:「[應該在其標題中包含」標籤「](http://meta.stackexchange.com/questions/19190/)」,其中的共識是「不,他們不應該」。 –

回答

3

類與靜態構造函數和不帶靜態構造函數的初始值之間有很大的區別。在沒有靜態構造函數的類上,它們可以在第一次訪問之前的任何時候運行,如果運行時間如此決定,則可以早得多。使用靜態構造函數,它們只能在第一次訪問之前立即運行。

DAL有一個靜態構造函數,因此它保證初始化第一次訪問和不早。

AppUtil沒有靜態構造函數,所以它可以儘早運行,特別是它可以在初始化之前運行DAL

如果您向AppUtil添加靜態構造函數(即使它爲空),則您的直接問題應該消失。

您應該閱讀Jon Skeet的C# and beforefieldinit,他在這裏詳細解釋了這種差異。

引述規格:

如果靜態構造(§10.12)在類存在,靜態字段初始化的執行發生之前立即執行該靜態構造。否則,靜態字段初始值設定項會在第一次使用該類的靜態字段之前的執行相關時間執行。

封閉類類型的靜態構造函數在給定的應用程序域中最多執行一次。靜態構造函數的執行由應用程序域中發生的以下第一個事件觸發:

  • 創建類類型的實例。
  • 引用任何類類型的靜態成員。

不過這種初始化模式是在許多層面上是一個壞主意。這些初始化程序很難調試,可以隨時改變它們的順序。

首先,您不應該爲這種狀態使用靜態字段。在您的初始化代碼中創建這些類的單個實例,而不是使用使用自己類之外的狀態進行初始化的經典單例。或者更好的是,使用適當的依賴注入。這些服務是DI和IoC閃耀的地方。

+0

+1:關於靜態構造函數的精確時序的詳細說明。從實際角度來看,它與「第一次使用前的隨機點」沒有多大區別。 –

+0

感謝Skeet ref;我會讀它。我認爲靜態初始化器a)在第一次使用之前也運行過,並且b)從.NET 4.0開始,它們從靜態構造函數中解除。當我們從.Net 2移動到4時,我們得到了相反的結果。我們有一個奇怪的依賴鏈,在那裏啓動DAL,因爲觸摸一個diff類的diff靜態成員也運行那個碰巧碰到DAL的靜態cctor;當我們到4.0時,觸摸其他靜態並沒有最終導致DAL進入。這就是爲什麼我們在Application_Start的頂部放置一個撓癢癢的原因。幸運的是,在調試 – user1664043

+0

repro'ed只是修改上述評論。去看看我們以前的.net 4轉換錯誤,這是另一個初始化程序,沒有靜態構造函數的情況下,2和4之間的差異似乎是在2,它是「碰一個,初始化所有」,其中4是更加懶惰的加載和不同的靜態分離。在2之下,我們逃脫了它,因爲觸摸Foo.bar1最終也初始化了Foo.bar2,它正好初始化我們的DAL。當我們到達4時,觸摸Foo.bar1不再初始化Foo.bar2,以便打開洞。 – user1664043

0

除了Loki的評論。 如果依賴於DAL中的靜態特性,則可能會非常嚴重地燒壞。例如實體框架。 不要在不瞭解.Net ASP管道以及如何使用線程的情況下嘗試此操作。 您必須考慮應用程序池中發生了什麼。應用程序池的生命週期。線程安全。應用程序池將來電分配給線程。所以你必須使靜態線程安全。 您可能需要一個自定義初始值設定項,它僅在APP啓動時纔會爲每個請求調用。
當您發現應用程序啓動可能已經在先前的請求上運行。

供大家參考:

//Global asax handlers 
    public override void Init() { 
     base.Init(); 
     // handlers managed by ASP.Net during Forms authentication 
     BeginRequest += new EventHandler(BeginRequestHandler); 
     // PostAuthorizeRequest += new EventHandler(PostAuthHandler); 
     EndRequest += new EventHandler(EndRequestHandler); 
    } 

考慮 ASP.NET控制器構造函數 「更新」,從最後一次通話遺留下來的環境。

請考慮如何保護您的靜電。 //線程安全嗎?

private static Object _bgalock = new Object(); 
    [ThreadStatic] // thread based static to avoid disasters.... 
    private static sometypeStaticUsedGlobally _ouch; 

    // Then Get and Set static with lock