你知道嗎?多個類多線程環境下靜態構造函數的執行順序
調用A a=new A()
請問輸出是什么?為什么?
class A { static A() { Stopwatch sw = new Stopwatch(); sw.Start(); XTrace.WriteLine("A1"); Thread.Sleep(3000); //B b = new B(); XTrace.WriteLine("AA"); //ThreadPool.QueueUserWorkItem(delegate { XTrace.WriteLine("BB"); B b = new B(); }); Thread thread = new Thread(new ParameterizedThreadStart(delegate { XTrace.WriteLine("BB"); B b = new B(); })); thread.Start(); Thread.Sleep(3000); sw.Stop(); XTrace.WriteLine("A2 " + sw.Elapsed); } public A() { XTrace.WriteLine("new A"); } } class B { static B() { Stopwatch sw = new Stopwatch(); sw.Start(); XTrace.WriteLine("B1"); Thread.Sleep(3000); A a = new A(); Thread.Sleep(3000); sw.Stop(); XTrace.WriteLine("B2 " + sw.Elapsed); } public B() { XTrace.WriteLine("new B"); } }
關于靜態構造函數函數的基本常識就不多說,園子里隨處可見!
這個問題讓群里的高手糾結了一整天,那個線程為什么不動?(線程等到A靜態構造函數執行完畢后才執行)
傍晚時分,有人忍不住發信問微軟:
Z_(164734xxx) 19:19:25
A static constructor is never called more than once, and it must be finished before any other thread can create an instance of the class or use a static member of the class. Therefore, the thread you try starting cannot start before A's static constructor ends.
網上很多資料說到靜態構造函數,但是很少提到與線程相關的,這個例子實際上是想測試一下靜態構造函數的多線程沖突。
其實,這個問題源自于XCode v7.3中一個隱秘的BUG。
實體類A的靜態構造函數中可能會開一個線程去執行方法B,然后靜態構造函數接著執行后續方法C,問題就在于B和C都會爭奪同一個鎖,如果B拿到這個鎖,它會創建一個A的實例,但是因為A的靜態構造函數正常執行C,C又等待B釋放這個鎖,從而形成了死鎖,所有用到類型A的線程都會掛起。
因為B和C的執行速度不一樣,要是C先拿到資源,就不會出現死鎖,所以這個問題解決起來特別的麻煩!
XCode v7.3的這個BUG表明,那個線程應該是可以同步執行的,但是為什么測試項目里面線程就是不動呢?(先看看大家討論,后面再公布答案)
附上XCode中出錯的部分
/// <summary> /// 數據實體類基類。所有數據實體類都必須繼承該類。 /// </summary> [Serializable] public partial class Entity<TEntity> : EntityBase where TEntity : Entity<TEntity>, new() { #region 構造函數 /// <summary> /// 靜態構造 /// </summary> static Entity() { // 1,可以初始化該實體類型的操作工廠 // 2,CreateOperate將會實例化一個TEntity對象,從而引發TEntity的靜態構造函數, // 避免實際應用中,直接調用Entity的靜態方法時,沒有引發TEntity的靜態構造函數。 TEntity entity = new TEntity(); EntityFactory.CreateOperate(Meta.ThisType, entity); }
TEntity就是實體類,它本身也有靜態構造函數,并且它的靜態構造函數里面會開一個線程去調用EntityFactory.CreateOperate(Type type),該方法會取得一個字典的鎖,然后通過Activator.CreateInstance(type)創建類型type的實例,加入字典,也就是實體類本身的實例。
EntityFactory.CreateOperate(Type type, IEntityOperate entity)跟上面的EntityFactory.CreateOperate(Type type)共同點再也它也要那這個字典的鎖,不同的在于它只是把entity加入字典。
結果就是:如果兩個參數這個先執行,就沒有問題,如果一個參數那個先執行,大家一起死!
答案:
上面微軟的答復郵件說得很清楚,靜態構造函數只會被調用一次,并且在它執行完成之前,任何其它線程都不能創建這個類的實例或使用這個類的靜態成員!
這里面包含幾層一次:
1,靜態構造函數只會被調用一次,并且在所有對該類的訪問之前。這一點我確信99.99%的人都知道。
2,“其它線程”。也就是說,只是其它線程不能創建實例和調用靜態成員而已,當前線程仍然是可以的。
3,“創建實例或使用靜態成員”。那么實例成員呢?當然不可能了,因為實例都無法創建,如何使用實例成員?
4,也是最隱秘的地方。測試代碼中,在A的靜態構造函數里面使用了匿名函數,而編譯器會把它編譯成為A的一個靜態方法,因此,它就成了A的靜態成員了,所以……
實際上,我們沒注意到的地方是第四點,太粗心了!
不過,可能清楚第二點的人不到10%吧。

浙公網安備 33010602011771號