在C#实例方法中,'this'可以为null吗?

时间:2011-02-20 03:30:19

标签: c# .net multithreading thread-safety

我遇到的情况是,很少有对象队列出列空值。对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。

6 个答案:

答案 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实例上的实例方法,这不是特别有用   要做的事。
  •