Within a C# instance method, can 'this' ever be null?
I have a situation where very rarely a Queue of Objects is dequeuing a null. The only call to Enqueue is within the class itself:
m_DeltaQueue.Enqueue(this);
Very rarely, a null is dequeued from this queue in the following code (a static method):
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);
}
This is the error report that was generated:
[Jan 23 01:53:13]: m_DeltaQueue.Dequeue of a null occurred: m_DeltaQueue is not null. m_DeltaQueue.count:345
I'm very confused as to how a null value could be present in this queue.
As I'm writing this, I'm wondering if this could be a failure of thread synchronization; this is a multi threaded application and It's possible the enqueue or dequeue could be happening simultaneously in another thread.
This is currently under .Net 4.0, but it previously occurred in 3.5/2.0
Update:
This is my (hopefully correct) solution to the problem which was made clear though the great answers below as being a synchronization problem.
private static object _lock = new object();
private static Queue<Mobile> m_DeltaQueue = new Queue<Mobile>();
Enqueue:
lock (_lock)
m_DeltaQueue.Enqueue(this);
Dequeue:
i开发者_开发知识库nt 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();
I'm still trying to get a handle on proper syncronization, so any comments on the correctness of this would be very appreciated. I initially chose to use the queue itself as a syncronization object because it's private, and introduces less clutter into what is already a very large class. Based on John's suggestion I changed this to lock on a new private static object, _lock.
this
can never be null, unless the method was called using a call
instruction in hand-written IL.
However, if you use the same Queue
instance on multiple threads simultaneously, the queue will become corrupted and lose data.
For example, if two items are added simultaneously to a near-capacity queue, the first item might be added to the array after the second thread resizes it, which will end up copying a null
to the resized array and adding the first item to the old array.
You should protect your queues with locks or use .Net 4's ConcurrentQueue<T>
.
this
can never be null (the CLR will raise an exception if you try to call a method on null
). It's almost certainly the case that you have a synchronization bug, where two threads are trying to add to the queue simultaneously. Perhaps both threads are incrementing an index into the array and then putting their value into the same location. This means that the first thread is getting its value overwritten.
Either synchronize your access (e.g. with lock
) or use a ConcurrentQueue
(in .Net 4).
Queues are not inherently thread safe. This is your issue. Use a mutex/lock/whatever or look for a thread safe-queue.
Indeed, if the Queue class you are using is not thread safe, you could be Dequeueing from two threads at the same time. The easiest way to avoid this is by locking your queue when you are dequeueing from it.
//declare this object in a globally accessible location
object locker = new object();
lock(locker)
{
m = mDeltaQueue.Dequeue();
}
(Slightly off-topic and a highly unlikely possibility; have made this community wiki. The real question has already been resolved; this is mainly related to the title of the question.)
In theory, if your code m_DeltaQueue.Enqueue(this)
resulted in the invocation of an implicit conversion operator on the argument, that could indeed result in a null-reference being passed to the method.
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();
}
}
It is possible to create a delegate that calls an instance method on a null
instance using the Delegate.CreateDelegate(Type, object, MethodInfo)
overload.
The MSDN says (emphasis mine)
If firstArgument is a null reference and method is an instance method, the result depends on the signatures of the delegate type type and of method:
- If the signature of type explicitly includes the hidden first parameter of method, the delegate is said to represent an open instance method. When the delegate is invoked, the first argument in the argument list is passed to the hidden instance parameter of method.
- If the signatures of method and type match (that is, all parameter types are compatible), then the delegate is said to be closed over a null reference. Invoking the delegate is like calling an instance method on a null instance, which is not a particularly useful thing to do.
精彩评论