当 运行 与立即 Window 时,lock() 块中的 ++ 是否有不同的应用?

Does a ++ inside a lock() block get applied differently when run from the Immediate Window?

我今天试图调试我们应用程序中重复 ID 的问题,我注意到来自即时 Window 的 运行ning 代码没有按预期运行。这是我用来测试的示例程序:

class Program
{
    static void Main(string[] args)
    {
        ClassA a = new ClassA();
        ClassB b = new ClassB();

        Console.WriteLine(string.Format("A: {0}", a.Id));
        Console.WriteLine(string.Format("B: {0}", b.Id));

        Console.ReadLine();
    }
}

public class ClassA
{
    private static int _id = MySingleton.Instance.GetId(typeof(ClassA));

    public int Id { get { return _id; } }
}

public class ClassB
{
    private static int _id = MySingleton.Instance.GetId(typeof(ClassB));

    public int Id { get { return _id; } }
}

public class MySingleton
{
    private int _id = 0;
    private object lockObj = new object();
    Dictionary<string, int> _cache = new Dictionary<string, int>();

    private static readonly MySingleton _mySingleton = new MySingleton();

    public int GetId(Type t)
    {
        if (_cache.ContainsKey(t.FullName))
        {
            return _cache[t.FullName];
        }
        else
        {
            lock (lockObj)
            {
                Add(t.FullName, _id++);
                return _id;
            }
        }
    }

    public void Add(string key, int value)
    {
        if (!_cache.ContainsKey(key))
            _cache.Add(key, value);
    }

    public static MySingleton Instance
    {
        get { return _mySingleton; }
    }
}

如果我运行这段代码,输出是

A: 1
B: 2

但是如果我在 static int _id 下一个断点并在立即 window 中检查 MySingleton.Instance.GetId(typeof(ClassA)) 的值,那么它会显示第一个 1 的值时间,put 坚持 0,所以输出是:

A: 0
B: 2

如果我将断点放在 ClassB 中并将 运行 MySingleton.Instance.GetId(typeof(ClassA)) 放在立即 window 中,它第一次显示 2 的值,但仍然存在1 对于任何后续调用:

A: 1
B: 1

我实际期望的是,因为我使用的是 _id++ 而不是 ++_id,所以输出应该是

A: 0
B: 1

为什么会这样?

首先观察几点:

  1. 当您第一次调用 GetID 时,该类型的缓存不会被填充。该值被添加到缓存中,然后增加。但是返回的是增加的(我假设不正确)值(1 for ClassA),而不是缓存中的值,它较低。因此,如果您为同一类型第二次调用 GetID,您会得到一个低一个的值(0 对应 ClassA)。

  2. 当您使用调试器并观察结果时,那些 "watches" 可能会修改结果。在您的情况下:手表 window 显示不正确的值(1 代表 ClassA),之后程序使用正确的值(0 代表 ClassA).

尝试像这样修复您的代码:

lock (lockObj)
{
    Add(t.FullName, _id); // <-- remove the ++
    return _id++; // <-- add the ++
}

这样,_id 的原始值在递增之前返回。

另一个观察:

  1. 您正在使用 Dictionay<K,V>。这不是线程安全的。您不仅应该在写操作期间使用锁来保护它,而且在读操作期间也应该使用锁来保护它。使用您现在的代码,写操作期间读取可能会导致不良结果。考虑使用 ConcurrentDictionary<K,V>.