【问题标题】:Does a ++ inside a lock() block get applied differently when run from the Immediate Window?从即时窗口运行时,lock() 块中的 ++ 是否会以不同的方式应用?
【发布时间】:2016-07-05 19:15:11
【问题描述】:

我今天尝试在我们的应用程序中调试一个带有重复 ID 的问题,我注意到从即时窗口运行的代码没有按预期运行。这是我用来测试的示例程序:

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; }
    }
}

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

答:1 乙:2

但是,如果我在static int _id 上设置断点并在“立即”窗口中检查MySingleton.Instance.GetId(typeof(ClassA)) 的值,那么它第一次显示1 的值,放置持续0,所以输出为:

答:0 乙:2

如果我将断点放在 ClassB 中并在“立即”窗口中运行 MySingleton.Instance.GetId(typeof(ClassA)),它会第一次向我显示 2 的值,但在任何后续调用中仍为 1

答:1 乙:1

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

答:0 乙:1

为什么会这样?

【问题讨论】:

    标签: c# .net-3.5


    【解决方案1】:

    首先观察几点:

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

    2. 当您使用调试器并观察结果时,那些“观察者”可能会修改结果。在您的情况下:监视窗口显示不正确的值(ClassA1),之后,de 程序使用正确的值(ClassA0)。

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

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

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

    另一个观察:

    1. 您正在使用Dictionay&lt;K,V&gt;。这不是线程安全的。您不仅应该在写操作期间用锁保护它,而且在读操作期间也应该使用锁来保护它。使用您现在的代码,read-during-a-write-operation 可能会导致不好的结果。考虑使用ConcurrentDictionary&lt;K,V&gt;

    【讨论】:

    • 你是对的!由于操作顺序,0被添加到缓存中,而1被返回! “将_id添加到缓存,增加_id,返回_id。好的,这至少解决了我的一个问题,谢谢!我想我一直在尝试调试这个问题太久了 :) 关于第 3 点,这里实际上没有使用 Dictionary,但我将它用于我的测试项目,因为它很简单。
    • 很高兴听到您更进一步。如果您的问题是完全答案,请使用勾号使其成为正确答案。如果您的问题仍有未解决的问题,我愿意为您提供进一步帮助。
    • 这确实回答了这个问题和我正在追踪的错误!在这种情况下,_id 的初始值是缓存文件中的最大值,因此每当向应用程序添加新对象时,都会添加一个副本。谢谢!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-02-16
    • 1970-01-01
    • 2017-10-17
    • 2019-11-25
    • 1970-01-01
    相关资源
    最近更新 更多