这个环境上下文怎么会变成空呢?

发布于 2024-09-01 07:45:38 字数 1846 浏览 2 评论 0原文

谁能帮我解释一下 TimeProvider.Current 如何在下面的类中变为 null ?

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 的单元测试也会在其 Fixture Teardown 中调用 ResetToDefault()。
  • 涉及多线程代码。
  • 有时,其中一个单元测试会失败,因为 TimeProvider.Current 为 null(抛出 NullReferenceException)。
  • 这种情况仅在我运行整个套件时发生,但在我仅运行单个单元测试时不会发生,这表明存在一些微妙的测试相互依赖性。
  • 大约每五到六次测试运行就会发生一次。
  • 当发生故障时,它似乎发生在涉及 TimeProvider.Current 的第一个执行的测试中。
  • 可能会有多个测试失败,但在给定的测试运行中只有一个测试失败。

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; }
    }
}

我怀疑静态初始化之间存在一些微妙的相互作用,其中运行时实际上允许在所有静态初始化完成之前访问 TimeProvider.Current ,但我可以'我不太明白。

任何帮助表示赞赏。


FWIW,我只是放入

Console.WriteLine(Thread.CurrentThread.ManagedThreadId);

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

Can anyone help me explain how TimeProvider.Current can become null in the following class?

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;
    }
}

Observations

  • All unit tests that directly reference TimeProvider also invokes ResetToDefault() in their Fixture Teardown.
  • There is no multithreaded code involved.
  • Once in a while, one of the unit tests fail because TimeProvider.Current is null (NullReferenceException is thrown).
  • This only happens when I run the entire suite, but not when I just run a single unit test, suggesting to me that there is some subtle test interdependence going on.
  • It happens approximately once every five or six test runs.
  • When a failure occurs, it seems to be occuring in the first executed tests that involves TimeProvider.Current.
  • More than one test can fail, but only one fails in a given test run.

FWIW, here's the DefaultTimeProvider class as well:

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; }
    }
}

I suspect that there's some subtle interplay going on with static initialization where the runtime is actually allowed to access TimeProvider.Current before all static initialization has finished, but I can't quite put my finger on it.

Any help is appreciated.


FWIW I just threw

Console.WriteLine(Thread.CurrentThread.ManagedThreadId);

in the getter, and it consistently reports the same ID for all test cases in a test run, so the issue seems not related to threading.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

疯了 2024-09-08 07:45:38

仅基于此代码,Current 可能为 null,因为它被设置为 null。这显然对你没有帮助。

您能提供测试代码吗?如果存在测试相互依赖性,那么读者提供反馈将会很有帮助。

同时,Jon Skeet 关于单例的文章可能会有所帮助,因为 DefaultTimeProvider 实际上充当单例:http://csharpindepth.com/Articles/General/Singleton.aspx

Based solely on this code, Current could be null based on it being set to null. This obviously isn't helpful to you.

Could you provide the code for the tests? If there's a test interdependence, it would be helpful for readers in order to provide any feedback.

In the mean time, possibly Jon Skeet's article on singletons might be helpful, since DefaultTimeProvider is effectively acting as a singleton: http://csharpindepth.com/Articles/General/Singleton.aspx

Oo萌小芽oO 2024-09-08 07:45:38

感谢 Peter Ritchie 提供的链接,我可能对此有部分答案,尽管我无法完全解释发生了什么。 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; }
    }
}

现在只有一个静态初始化程序在运行 (TimeProvider),并且由于它是一个显式静态构造函数,因此该类未标记为 beforefieldinit。

这似乎已经达到了目的......

I may have a partial answer to this, thanks to the links provided by Peter Ritchie, although I can't fully explain what's going on. It would seem that there was some kind of race going on between static initilization of TimeProvider and DefaultTimeProvider. It may have to do with beforefieldinit.

Changing the implementation seems to have resolved the issue. If not, it has certainly made the race condition much rarer, to a point where I have yet to see it.

I changed initialization of TimeProvider to this:

public abstract class TimeProvider
{
    private static TimeProvider current;

    static TimeProvider()
    {
        TimeProvider.current = new DefaultTimeProvider();
    }

    //...
}

And DefaultTimeProvider simply to this:

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

There is now only one static initializer in play (TimeProvider) and since it's an explicit static constructor the class is not marked beforefieldinit.

This seems to have done the trick...

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文