代码之家  ›  专栏  ›  技术社区  ›  Mark Seemann

环境上下文如何变为空?

  •  12
  • Mark Seemann  · 技术社区  · 15 年前

    有人能帮我解释一下 TimeProvider.Current 在下面的类中可以变为空吗?

    public abstract class TimeProvider
    {
        private static TimeProvider current =
            DefaultTimeProvider.Instance;
    
        public static TimeProvider Current
        {
            get { return TimeProvider.current; }
            set
            {
                if (value == null)
                {
                    throw new ArgumentNullException("value");
                }
                TimeProvider.current = value;
            }
        }
    
        public abstract DateTime UtcNow { get; }
    
        public static void ResetToDefault()
        {
            TimeProvider.current = DefaultTimeProvider.Instance;
        }
    }
    

    观察

    • 直接引用TimeProvider的所有单元测试也会在其夹具拆卸中调用ResettoDefault()。
    • 涉及多线程代码。
    • 偶尔,其中一个单元测试会失败,因为 时间提供程序。当前 为空(引发NullReferenceException)。
    • 这只在我运行整个套件时发生,而不是在我只运行一个单元测试时发生,这向我暗示正在进行一些微妙的测试相互依赖性。
    • 大约每五到六次测试运行一次。
    • 当发生故障时,似乎是在涉及 时间提供程序。当前 .
    • 多个测试可以失败,但在给定的测试运行中只有一个失败。

    fwiw,这里还有defaultTimeProvider类:

    public class DefaultTimeProvider : TimeProvider
    {
        private readonly static DefaultTimeProvider instance =
            new DefaultTimeProvider();
    
        private DefaultTimeProvider() { }
    
        public override DateTime UtcNow
        {
            get { return DateTime.UtcNow; }
        }
    
        public static DefaultTimeProvider Instance
        {
            get { return DefaultTimeProvider.instance; }
        }
    }
    

    我怀疑静态初始化中存在一些微妙的相互作用,运行时实际上可以访问 时间提供程序。当前 在所有的静态初始化完成之前,但我不能完全将手指放在上面。

    感谢您的帮助。


    我刚才扔了

    Console.WriteLine(Thread.CurrentThread.ManagedThreadId);
    

    在getter中,它始终为测试运行中的所有测试用例报告相同的ID,因此该问题似乎与线程无关。

    2 回复  |  直到 12 年前
        1
  •  7
  •   Peter Ritchie    12 年前

    仅基于此代码, Current 可以是 null 基于它被设置为 无效的 . 这显然对你没有帮助。

    你能提供测试的代码吗?如果有一个测试相互依赖性,它将有助于读者提供任何反馈。

    同时,乔恩·斯基特关于独生子的文章可能会有所帮助,因为 DefaultTimeProvider 有效地扮演了一个单身汉的角色: http://csharpindepth.com/Articles/General/Singleton.aspx

        2
  •  6
  •   Mark Seemann    15 年前

    多亏了彼得·里奇提供的链接,我可能对这个问题有部分的答案,尽管我不能完全解释到底发生了什么。似乎在TimeProvider和DefaultTimeProvider的静态初始化之间存在某种竞争。这可能与 beforefieldinit .

    改变实施方式似乎解决了这个问题。如果不是这样的话,那肯定会使比赛条件变得更为罕见,到了我还没有看到的地步。

    我将TimeProvider的初始化更改为:

    public abstract class TimeProvider
    {
        private static TimeProvider current;
    
        static TimeProvider()
        {
            TimeProvider.current = new DefaultTimeProvider();
        }
    
        //...
    }
    

    和DefaultTimeProvider简单来说:

    public class DefaultTimeProvider : TimeProvider
    {
        public override DateTime UtcNow
        {
            get { return DateTime.UtcNow; }
        }
    }
    

    现在在play(timeprovider)中只有一个静态初始值设定项,由于它是显式静态构造函数,因此类没有标记为beforefieldinit。

    这似乎已经成功了…