你知道吗?多个类多线程环境下静态构造函数的执行顺序
调用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%吧。