我遇到的情况是,很少有对象队列出列空值。对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);
}
这是生成的错误报告:
[1月23日01:53:13]: m_DeltaQueue.Nqueue 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);
出列:
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();
我仍然试图处理正确的同步,因此任何关于此正确性的评论都将非常感激。我最初选择使用队列本身作为同步对象,因为它是私有的,并且对已经非常大的类引入了更少的混乱。基于John的建议,我将其更改为锁定一个新的私有静态对象_lock。
答案 0 :(得分:29)
this
永远不能为null,除非在手写IL中使用call
指令调用该方法。
但是,如果同时在多个线程上使用相同的Queue
实例,则队列将损坏并丢失数据。
例如,如果将两个项目同时添加到近容量队列,则第二个线程调整后可能会将第一个项目添加到数组中,最终会将null
复制到调整大小的数组并将第一项添加到旧数组中。
您应该使用锁保护您的队列或使用.Net 4的ConcurrentQueue<T>
。
答案 1 :(得分:4)
this
永远不能为空(如果您尝试在null
上调用方法,CLR将引发异常)。几乎可以肯定的是,您有一个同步错误,其中两个线程正在尝试同时添加到队列中。也许两个线程都将索引递增到数组中,然后将它们的值放入相同的位置。这意味着第一个线程正在覆盖它的值。
同步您的访问权限(例如使用lock
)或使用ConcurrentQueue
(在.Net 4中)。
答案 2 :(得分:3)
实际上,如果您使用的Queue类不是线程安全的,那么您可能同时从两个线程中出队。避免这种情况的最简单方法是在从队列中出队时锁定队列。
//declare this object in a globally accessible location
object locker = new object();
lock(locker)
{
m = mDeltaQueue.Dequeue();
}
答案 3 :(得分:3)
队列本身并不是线程安全的。这是你的问题。使用mutex / lock / whatever或查找线程安全队列。
答案 4 :(得分:1)
(稍微偏离主题,极不可能的可能性;已经建立了这个社区维基。真正的问题已经解决了;这主要与问题的标题有关。)
理论上,如果您的代码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();
}
}
答案 5 :(得分:0)
可以使用Delegate.CreateDelegate(Type, object, MethodInfo)
重载创建一个在null
实例上调用实例方法的委托。
MSDN说(强调我的)
如果 firstArgument 是空引用而方法是实例方法,则 结果取决于委托类型类型和方法的签名:
- 如果 type 的签名明确包含隐藏的第一个 方法的参数,代表代表开放 实例方法。调用委托时,第一个参数在 参数列表传递给隐藏的实例参数 方法
- 如果方法和类型的签名匹配(即全部 参数类型是兼容的),然后代表被称为 关闭空引用。 调用委托就像调用一个 null实例上的实例方法,这不是特别有用 要做的事。