Подтвердить что ты не робот

В методе экземпляра С# может ли 'this' быть null?

У меня есть ситуация, когда очень редко Очередь объектов убирает нуль. Единственный вызов 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 null произошло: m_DeltaQueue не равно нулю. m_DeltaQueue.count: 345

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

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

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

Update:

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

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();

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

4b9b3361

Ответ 1

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

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

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

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

Ответ 2

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


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

lock(locker)
{
    m = mDeltaQueue.Dequeue();
}

Ответ 3

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

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

Ответ 4

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

Ответ 5

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

В теории, если ваш код 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();
    }
}

Ответ 6

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

MSDN говорит (акцент мой)

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

  • Если подпись типа явно включает скрытые параметр метода, делегат, как говорят, представляет собой открытый метод экземпляра. При вызове делегата первый аргумент в список аргументов передается параметру скрытого экземпляра Метод.
  • Если подписи метода и типа совпадают (то есть все типы параметров совместимы), то делегат называется закрыто над нулевой ссылкой. Вызов делегата - это вызов вызова экземпляр метода на нулевом экземпляре, который не является особенно полезным предмет, чтобы сделать.