簡體   English   中英

C# 變量不應該是 null 拋出 null 引用異常

[英]C# variable that should never be null throws null reference exception

我發現了一個奇怪的情況,我無法解釋。 Basicaly 屬性“UserCalls”在開始時工作正常,但在應用程序啟動后很長時間(一個多月)后,不應取 null 值的屬性,在調用函數時拋出 null 異常(例如 Z83C8324F192E756CDE ))。

代碼示例:

public class UserCall
{
    public long UserID { get; set; }
    public long CallID { get; set; }
}

public static class Cache
{
    private static List<UserCall> userCalls = new List<UserCall>();

    public static List<UserCall> UserCalls
    {
        get
        {
            if (userCalls == null)
            {
                userCalls = new List<UserCall>();
            }

            return userCalls;
        }

        set
        {
            userCalls = value;
        }
    }

    private static void AddCall(long userID, long callID)
    {
        UserCalls.Add(
                    new UserCall
                    {
                        CallID = callID,
                        UserID = userID
                    });
    }


    public static UserCall GetCall(long userID)
    {
        var userCall = UserCalls.FirstOrDefault(x => x.UserID == userID);
        return userCall;
    }

    public static void RemoveCall(long userID)
    {
        UserCalls.RemoveAll(x => x.UserID == userID);
    }
}

調用“GetCall”時拋出以下異常:

    [ERROR] Object reference not set to an instance of an object. | at System.Linq.Enumerable.FirstOrDefault[TSource](IEnumerable`1 source, Func`2 predicate)

可以從多個線程調用方法“AddCall”、“GetCall”和“RemoveCall”。 此異常隨機發生(但僅在服務啟動后很長時間后)在更多負載的服務上發生。 如果發生異常,它會被鎖定在這個永久 null state 並且不會自行更正,只有服務重啟會有所幫助。 我知道這個實現並不完全是線程安全的,但它仍然不應該返回 null。 為什么會這樣? 有沒有人遇到過類似的情況?

在多線程上下文中,您的UserCalls屬性可以是 null。

鑒於:

public static List<UserCall> UserCalls
{
    get
    {
        if (userCalls == null)
        {
            userCalls = new List<UserCall>();
        }

        return userCalls;
    }

    set
    {
        userCalls = value;
    }
}

考慮以下具有兩個線程的序列,[A] 和 [B],其中userCalls當前為非空。

[A] Accesses `UserCalls` getter. 
[A] Checks if `userCalls` is null. It is not, so it will skip the assignment.    
[B] Accesses `UserCalls` setter, about to set it to `null`.
[B] Sets `userCalls` to null.
[A] Returns `userCalls`, which is now null => BANG!

您可以通過使用鎖來防止這種情況:

public static List<UserCall> UserCalls
{
    get
    {
        lock (_locker)
        {
            if (userCalls == null)
            {
                userCalls = new List<UserCall>();
            }

            return userCalls;
        }
    }

    set
    {
        lock (_locker)
        { 
            userCalls = value;
        }
    }
}

static object _locker = new object();

但是,請注意,沒有什么可以阻止將null UserCalls 如果這樣做,您還將UserCalls.FirstOrDefault(x => x.UserID == userID);等調用中獲得NullReferenceException ,因為x將是 null 並且使用x.UserID將為您提供 null 引用異常。

我建議:

  1. 刪除屬性 UserCalls 並將列表僅用作字段。 這可以防止添加 null 值或列表被您自己的其他進程修改的可能性。
  2. 項目清單

添加鎖以使列表線程的使用安全

編碼:

public static class Cache
{
    private static List<UserCall> userCalls = new List<UserCall>();    

    private static void AddCall(long userID, long callID)
    {
        lock(userCalls)
        {
            userCalls.Add(
                    new UserCall
                    {
                        CallID = callID,
                        UserID = userID
                    });
        }
    }


    public static UserCall GetCall(long userID)
    {
        UserCall userCall = null;
        lock(userCalls)
            userCall = UserCalls.FirstOrDefault(x => x.UserID == userID);
        return userCall;
    }

    public static void RemoveCall(long userID)
    {
        lock(userCalls)
            userCalls.RemoveAll(x => x.UserID == userID);
    }
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM