В методе экземпляра С# может ли '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.
Ответы
Ответ 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 является пустой ссылкой и метод является методом экземпляра, результат зависит от подписей типа типа делегата и метода:
- Если подпись типа явно включает скрытые параметр метода, делегат, как говорят, представляет собой открытый метод экземпляра. При вызове делегата первый аргумент в список аргументов передается параметру скрытого экземпляра Метод.
- Если подписи метода и типа совпадают (то есть все типы параметров совместимы), то делегат называется закрыто над нулевой ссылкой. Вызов делегата - это вызов вызова экземпляр метода на нулевом экземпляре, который не является особенно полезным предмет, чтобы сделать.