В методе экземпляра C # может ли это когда-либо быть нулевым? - PullRequest
26 голосов
/ 20 февраля 2011

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

m_DeltaQueue.Enqueue(this);

Очень редко нулевое значение удаляется из этой очереди в следующем коде (статический метод):

while (m_DeltaQueue.Count > 0 && index++ < count)
    if ((m = m_DeltaQueue.Dequeue()) != null)
        m.ProcessDelta();
    else if (nullcount++ < 10)
    {
        Core.InvokeBroadcastEvent(AccessLevel.GameMaster, "A Rougue null exception was caught, m_DeltaQueue.Dequeue of a null occurred. Please inform an developer.");
        Console.WriteLine("m_DeltaQueue.Dequeue of a null occurred: m_DeltaQueue is not null. m_DeltaQueue.count:{0}", m_DeltaQueue.Count);
    }

Это отчет об ошибке, который был сгенерирован:

[23 января 01:53:13]: m_DeltaQueue.Dequeue of null произошло: m_DeltaQueue не является нулевым. m_DeltaQueue.count: 345

Я очень озадачен тем, как нулевое значение может присутствовать в этой очереди.

Когда я пишу это, мне интересно, может ли это быть сбой синхронизации потоков; Это многопоточное приложение, и вполне возможно, что очередь или очередь могут происходить одновременно в другом потоке.

Это в настоящее время под .Net 4.0, но ранее это происходило в 3.5 / 2.0

Обновление:

Это моё (надеюсь правильное) решение проблемы, которое было прояснено, хотя приведенные ниже отличные ответы как проблема синхронизации.

private static object _lock = new object();
private static Queue<Mobile> m_DeltaQueue = new Queue<Mobile>();

Ставить:

    lock (_lock)
        m_DeltaQueue.Enqueue(this);

Dequeue:

       int count = m_DeltaQueue.Count;
       int index = 0;
       if (m_DeltaQueue.Count > 0 && index < count)
           lock (_lock)
               while (m_DeltaQueue.Count > 0 && index++ < count)
                   m_DeltaQueue.Dequeue().ProcessDelta();

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

Ответы [ 6 ]

29 голосов
/ 20 февраля 2011

this никогда не может быть нулевым, если метод не был вызван с использованием инструкции call в рукописном IL.

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

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

Вы должны защищать свои очереди с помощью блокировок или использовать .Net 4's ConcurrentQueue<T>.

4 голосов
/ 20 февраля 2011

this никогда не может быть нулевым (CLR вызовет исключение, если вы попытаетесь вызвать метод на null). Это почти наверняка тот случай, когда у вас есть ошибка синхронизации, когда два потока пытаются добавить в очередь одновременно. Возможно, оба потока увеличивают индекс в массиве, а затем помещают свои значения в одно и то же место. Это означает, что первый поток перезаписывает свое значение.

Либо синхронизируйте свой доступ (например, с lock), либо используйте ConcurrentQueue (в .Net 4).

3 голосов
/ 20 февраля 2011

Очереди не являются поточно-ориентированными. Это твоя проблема. Используйте мьютекс / блокировку / что угодно или ищите потокобезопасную очередь.

3 голосов
/ 20 февраля 2011

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


//declare this object in a globally accessible location
object locker = new object();

lock(locker)
{
    m = mDeltaQueue.Dequeue();
}
1 голос
/ 21 февраля 2011

(Немного не по теме и крайне маловероятно; сделали это сообщество вики. Реальный вопрос уже решен; в основном это связано с названием вопроса.)

Теоретически, если ваш код m_DeltaQueue.Enqueue(this) привел к вызову оператора неявного преобразования аргумента, это действительно может привести к передаче нулевой ссылки в метод.

class Foo
{
    public static implicit operator string(Foo foo)
    {
        return null;
    }

    void InstanceMethod()
    {
        string @this = this;

        if (@this == null)
            Console.WriteLine("Appears like 'this' is null.");
    }

    static void Main()
    {
        new Foo().InstanceMethod();
    }
}
0 голосов
/ 22 февраля 2017

Можно создать делегат, который вызывает метод экземпляра для экземпляра null с использованием перегрузки Delegate.CreateDelegate(Type, object, MethodInfo).

MSDN говорит (выделено мое)

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

  • Если подпись типа явно включает в себя сначала скрытое параметр метод , делегат считается открытым метод экземпляра. Когда делегат вызывается, первый аргумент в список аргументов передается скрытому параметру экземпляра способ .
  • Если сигнатуры метода и типа совпадают (то есть все типы параметров совместимы), тогда делегат называется закрыто по нулевой ссылке . Вызов делегата подобен вызову метод экземпляра для нулевого экземпляра , который не особенно полезен что нужно сделать.
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...