Почему SynchronizationContext.Current равен нулю?

Ошибка: Object reference not set to an instance of an object.

Алгоритм ниже работает. Я попробовал это, затем я удалил Winform проецировать в другой каталог и SynchronizationContext.Current является null, Зачем?

SynchronizationContext uiCtx = SynchronizationContext.Current;  

private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
    int[] makeSelfMoves = new int[4];

    lock (replay)
    {
        // count should be more than 2
        foreach (KeyValuePair<int, int[]> item in replay)
        {              
            makeSelfMoves = replay[item.Key];
            codeFile.ExecuteAll(makeSelfMoves[0],
              makeSelfMoves[1], makeSelfMoves[2], makeSelfMoves[3]);

            // i get the error here. uictx is null
            uiCtx.Post(o =>
            {
                PrintPieces(codeFile.PieceState());
            }, null);                               

            System.Threading.Thread.Sleep(1000);
        }
    }
}

2 ответа

Решение

Ваш код критически зависит от того, когда и где работает конструктор вашего класса. SynchronizationContext.Current будет нулевым, когда:

  • Ваш объект класса создается слишком рано, прежде чем ваш код создаст экземпляр класса Form или вызовет Application.Run() в Main(). Это когда текущий элемент установлен на экземпляр WindowsFormsSynchronizationContext, класса, который знает, как маршализовать вызовы с помощью цикла сообщений. Исправьте это, переместив код экземпляра вашего объекта в основной конструктор формы.

  • Ваш объект класса создается в любом потоке, кроме основного потока пользовательского интерфейса. Только поток пользовательского интерфейса в приложении Winforms может маршалировать вызовы. Диагностируйте это, добавив конструктор в ваш класс с помощью этого оператора:

            Console.WriteLine(System.Threading.Thread.CurrentThread.ManagedThreadId);
    

Также добавьте эту строку в метод Main () в Program.cs. Это не будет работать, если отображаемое значение в окне вывода отличается. Исправьте это, переместив код экземпляра объекта в конструктор основной формы, чтобы вы могли быть уверены, что он выполняется в потоке пользовательского интерфейса.

Я столкнулся с этой проблемой при создании WinForms с помощью внедрения зависимостей в моей тестовой среде. Первоначально я бы зафиксировал SynchronizationContext.Current в своем конструкторе:

      private readonly SynchronizationContext UISyncCtxt;

public MyWinFormViewModel ()
{
    UISyncCtxt = SynchronizationContext.Current;
    ...
}

Это сработало нормально, если эта MyWinFormViewModel была создана, когда приложение уже работало, но это не обязательно ситуация при создании графа зависимостей в тестовой системе. При создании тестовой оснасткой SynchronizationContext.Current будет иметь значение NULL, и позже возникнет исключение с нулевой ссылкой.

Мое решение было «лениво» оценить это следующим образом:

          private SynchronizationContext _uisyncctxt;

    private SynchronizationContext UISyncCtxt => 
        _uisyncctxt ??= SynchronizationContext.Current;

К тому времени, когда мне действительно понадобится контекст (для обновления элементов управления в форме), он обязательно будет присутствовать (поскольку форма была создана).

EDIT: Питер Дунихо поднял вопрос о произвольном захвате контекста синхронизации. Мой первоначальный ответ также делает этот класс нечестным в отношении своих зависимостей, поскольку он полагается на этот контекст, но не запрашивает его через конструктор или другой вводимый метод. Поскольку этот класс использует DI, я добавил зависимость под названием IUISyncContext, которая имеет следующую сигнатуру:

      public interface IUISyncContext
{
    SynchronizationContext UISyncContext { get; }
}

... и конструктор для моей модели представления:

      private readonly SynchronizationContext UISyncCtxt;

public MyWinFormViewModel (IUISyncContext syncContext)
{        
    UISyncCtxt = syncContext.UISyncContext;
    ...
}

Спасибо за отзыв, Питер.

Другие вопросы по тегам