你知道吗?多个类多线程环境下静态构造函数的执行顺序

时间:2022-04-21
本文章向大家介绍你知道吗?多个类多线程环境下静态构造函数的执行顺序,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

调用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%吧。