Как этот окружающий контекст может стать нулевым? - PullRequest
12 голосов
/ 15 мая 2010

Может ли кто-нибудь помочь мне объяснить, как 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 () в своем процессе разрушения Fixture.
  • В нет многопоточного кода.
  • Время от времени один из модульных тестов не выполняется, поскольку 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);

в получателе, и он последовательно сообщает один и тот же идентификатор для всех тестовых случаев в тестовом прогоне, поэтому проблема, похоже, не связана с многопоточностью.

Ответы [ 2 ]

7 голосов
/ 15 мая 2010

Исходя только из этого кода, Current может быть null, если установлено значение null. Это, очевидно, не полезно для вас.

Не могли бы вы предоставить код для тестов? Если есть тестовая взаимозависимость, для читателей будет полезно предоставить какой-либо отзыв.

В то же время, возможно, статья Джона Скита о синглетонах может быть полезной, поскольку DefaultTimeProvider эффективно действует как синглтон: http://csharpindepth.com/Articles/General/Singleton.aspx

6 голосов
/ 16 мая 2010

У меня может быть частичный ответ на это, благодаря ссылкам, предоставленным Питером Ричи, хотя я не могу полностью объяснить, что происходит. Казалось бы, между статической инициализацией 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), и, поскольку он является явным статическим конструктором, класс не помечен перед fieldinit.

Кажется, это сработало ...

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...