Странный поток NullReferenceException при чтении значения, которое существует?

У меня невероятно странное исключение NullReferenceException, возникающее при чтении значения из публичного поля объекта, о существовании которого я знаю. Основной поток таков:

Edit: Я понял, что забыл упомянуть кое-что важное, это происходит не каждый раз, когда я пытаюсь прочитать значение Tag, а только иногда, достаточно, чтобы я мог воспроизвести это каждый раз, просто запустив код, но не мгновенно, когда код выполняется

  • Сервер получает сообщение (рабочий поток)
  • Соединение, отправившее сообщение, получает значение Tag поля объекта сообщения (рабочий поток)
  • Сообщение помещается в "ReceivedMessages" очередь (обычный объект Queue, который охраняется блокировками для сериализованного доступа) (Worker thread)
  • Сообщение читается (Main Thread)
  • Я пытаюсь прочитать поле Tag сообщения, чтобы получить соединение, это иногда возвращает null и выбрасывает исключение, но когда исключение выбрасывается и я проверяю объект Message, я вижу объект Connection (который является объектом, находящимся в поле Tag) там ясно как день (Main Thread)

Если вы посмотрите на эту картинку, вы увидите это ясно как день:

Weird thread behavior

Вы можете видеть, где я отметил зеленым квадратиком, я пытаюсь прочитать сообщение. Tagсвойство тремя различными способами, все они возвращают null, как вы можете видеть в части, отмеченной синей рамкой.

Однако, если вы посмотрите на две области, отмеченные красным цветом, вы увидите, что объект действительно существует. И, чтобы устранить путаницу, часть, где сообщение помещается в очередь полученных сообщений, выглядит так:

Как видите, я даже попробовал выполнить Thread.VolatileWrite, чтобы убедиться, что значение будет записано

message.Tag = buffer.Tag;

Thread.VolatileWrite(ref message.Tag, buffer.Tag);

if (message.Tag == null)
{
    isNullLog.Add(message.Id);
}

// Queue into received messages
lock (peer.ReceivedMessages)
{
    peer.ReceivedMessages.Enqueue(message);
}

Приведенный фрагмент происходит в рабочем потоке, и, как видите, я копирую buffer. Tag в message.Tag, я даже установил небольшую проверку для отладки, которая проверяет message.Tag на нулевое значение и добавляет его id в список под названием "isNullLog", если это так. Когда в главном потоке возникает NullReferenceException, этот список пуст.

Вы также видите, что я блокирую очередь peer.ReceivedMessages и отправляю сообщение в очередь после установки поля message.Tag.

Также, чтобы было еще более понятно, вот функция, которая используется для чтения сообщения из очереди peer.ReceivedMessages:

public bool TryGetMessage(out TIncomingMessage message)
{
    lock (ReceivedMessages)
    {
        if (ReceivedMessages.Count > 0)
        {
            message = ReceivedMessages.Dequeue();
            return true;
        }
    }

    ReceivedMessageEvent.Reset();

    message = null;
    return false;
}

Вы можете видеть, что я блокирую очередь еще до проверки счета, и если она не пуста, я устанавливаю свойство out и возвращаю true, иначе я возвращаю false.

Честно говоря, я в полном замешательстве, до этого я написал несколько многопоточных приложений и никогда не сталкивался с этим.

Небольшое обновление, я также попробовал пометить поле Tag как volatile, чтобы оно выглядело так public volatile object Tag; но это, похоже, не помогает.

7
задан thr 27 November 2011 в 10:42
поделиться