在C#实例方法中,’this’可以为null吗?
我有一种情况,很少有对象的队列出列空值。 对Enqueue的唯一调用是在类本身内:
m_DeltaQueue.Enqueue(this);
很少,在以下代码中,null会从此队列中出列(静态方法):
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); }
这是生成的错误报告:
[Jan 23 01:53:13]:m_DeltaQueue。发生null的取消:m_DeltaQueue不为null。 m_DeltaQueue.count:345
关于如何在此队列中出现空值,我感到非常困惑。
在我写这篇文章时,我想知道这是否可能是线程同步的失败; 这是一个multithreading应用程序,可能在另一个线程中同时发生入队或出队。
目前这是在.Net 4.0下,但它以前发生在3.5 / 2.0
更新:
这是我(希望是正确的)解决问题的方法,虽然下面的重要答案是同步问题,但这个问题已经明确了。
private static object _lock = new object(); private static Queue m_DeltaQueue = new Queue();
排队:
lock (_lock) m_DeltaQueue.Enqueue(this);
出列:
int count = m_DeltaQueue.Count; int index = 0; if (m_DeltaQueue.Count > 0 && index 0 && index++ < count) m_DeltaQueue.Dequeue().ProcessDelta();
我仍然试图找到适当的同步,所以任何关于正确性的评论都将非常感激。 我最初选择使用队列本身作为同步对象,因为它是私有的,并且在已经非常大的类中引入了更少的混乱。 基于John的建议,我将其更改为锁定一个新的私有静态对象_lock。
除非在手写IL中使用call
指令call
该方法,否则它永远不会为null。
但是,如果同时在多个线程上使用相同的Queue
实例,则队列将损坏并丢失数据。
例如,如果将两个项目同时添加到近容量队列,则第二个项目可能会在第二个线程resize后将其添加到数组中,这将最终将null
复制到已resize的数组并将第一个项目添加到旧arrays。
您应该使用锁保护您的队列或使用.Net 4的ConcurrentQueue
。
this
永远不会为null(如果尝试在null
上调用方法,CLR将引发exception)。 几乎可以肯定的是,您有一个同步错误,其中两个线程正在尝试同时添加到队列中。 也许两个线程都将索引递增到数组中,然后将它们的值放入相同的位置。 这意味着第一个线程正在覆盖其值。
要么同步您的访问权限(例如使用lock
),要么使用ConcurrentQueue
(在.Net 4中)。
队列本身并不是线程安全的。 这是你的问题。 使用互斥锁/ lock / whatever或查找线程安全队列。
实际上,如果您使用的Queue类不是线程安全的,那么您可能同时从两个线程中出队。 避免这种情况的最简单方法是在从队列中出队时锁定队列。
//declare this object in a globally accessible location object locker = new object(); lock(locker) { m = mDeltaQueue.Dequeue(); }
(稍微偏离主题,极不可能的可能性;已经建立了这个社区维基。真正的问题已经解决;这主要与问题的标题有关。)
理论上,如果您的代码m_DeltaQueue.Enqueue(this)
导致在参数上调用隐式转换运算符,那么确实可能导致将null引用传递给该方法。
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(); } }
可以使用Delegate.CreateDelegate(Type, object, MethodInfo)
重载创建一个在null
实例上调用实例方法的Delegate.CreateDelegate(Type, object, MethodInfo)
。
MSDN说(强调我的)
如果firstArgument是空引用而method是实例方法,则结果取决于委托类型类型和方法的签名:
- 如果类型的签名显式包含方法的隐藏的第一个参数,则表示委托表示开放实例方法。 调用委托时,参数列表中的第一个参数将传递给方法的隐藏实例参数。
- 如果方法和类型的签名匹配(即,所有参数类型都是兼容的),则表示委托在空引用上关闭 。 调用委托就像在null实例上调用实例方法一样 ,这不是特别有用的事情。