代码之家  ›  专栏  ›  技术社区  ›  JOBG

我需要什么选项才能使这个代码线程安全?

  •  1
  • JOBG  · 技术社区  · 15 年前

    我有这段代码,为了简洁起见跳过了很多东西,但场景是这样的:

     public class Billing
        {
            private List<PrecalculateValue> Values = new List<PrecalculateValue>();
    
            public int GetValue(DateTime date)
            {
                var preCalculated = Values.SingleOrDefault(g => g.date == date).value;
                //if exist in Values, return it
                if(preCalculated != null)
                {
                   return preCalculated;
                }
    
                // if it does not exist calculate it and store it in Values
                int value = GetValueFor(date);
                Values.Add(new PrecalculateValue{date = date, value = value});
    
                return value;
            }
    
            private object GetValueFor(DateTime date)
            {
                //some logic here
            }
        }
    

    我有一个 List<PrecalculateValue> Values 在我存储我已经计算的所有值供以后使用的地方,我这样做主要是因为我不想为同一个客户机重新计算两次,每个计算涉及很多操作,需要500到1000毫秒,并且由于在洞计费类中涉及到一些递归,所以重用该值的可能性很大。

    所有这些都很好地工作,直到我做了一个测试,我为两个不同的客户同时进行了两次计算,并且 Values.Single(g => g.date == date).value 返回异常,因为它在集合中找到多个结果。 所以我检查了这个列表,它将两个客户机的值存储在同一个列表中。我能做些什么来避免这个小问题?

    2 回复  |  直到 15 年前
        1
  •  5
  •   Dan Tao    15 年前

    首先,这条线:

    return Values.Single(g => g.date == date).value;
    

    这样就不会调用后面的行。我猜你已经把你的代码解释了一点了?

    如果要同步写入 Values 列表,最简单的方法是 lock 在您要修改列表的代码中的所有公共对象上:

    int value = GetValueFor(date);
    
    lock (dedicatedLockObject) {
        Values.Add(new PrecalculateValue{date = date, value = value});
    }
    
    return value;
    

    但还有一件事值得注意:因为看起来你想要一个 PrecalculateValue DateTime ,更合适的数据结构可能是 Dictionary<DateTime, PrecalculateValue> --它将根据您的 日期时间 键,与 List<PrecalculateValue> 它必须迭代才能找到您要查找的内容。

    有了这些更改,您的代码可能会如下所示:

    public class Billing
    {
        private Dictionary<DateTime, PrecalculateValue> Values = 
            new Dictionary<DateTime, PrecalculateValue>();
    
        private readonly commonLockObject = new object();
    
        public int GetValue(DateTime date)
        {
            PrecalculateValue cachedCalculation;
    
            // Note: for true thread safety, you need to lock reads as well as
            // writes, to ensure that a write happening concurrently with a read
            // does not corrupt state.
            lock (commonLockObject) {
                if (Values.TryGetValue(date, out cachedCalculation))
                    return cachedCalculation.value;
            }
    
            int value = GetValueFor(date);
    
            // Here we need to check if the key exists again, just in case another
            // thread added an item since we last checked.
            // Also be sure to lock ANYWHERE ELSE you're manipulating
            // or reading from the collection.
            lock (commonLockObject) {
                if (!Values.ContainsKey(date))
                    Values[date] = new PrecalculateValue{date = date, value = value};
            }
    
            return value;
        }
    
        private object GetValueFor(DateTime date)
        {
            //some logic here
        }
    }
    

    最后一条建议是:除非在您的集合中不存在超过一个特定值这一点很重要,否则 Single 方法是多余的。如果你只想得到第一个值,忽略可能的重复值, First 既安全(如中所述,发生异常的可能性较小)又快速(因为它不必在整个集合中迭代)。

        2
  •  1
  •   Paul Creasey    15 年前

    可以用这样的东西

    public int GetValue(DateTime date)
    {
    
        var result = Values.Single(g => g.date == date) ?? GetValueFor(date);
    
        lock (Values)
        {
            if (!Values.Contains(result)) Values.Add(result);
        }
        return result.value;
    }
    
    private PrecalculateValue GetValueFor(DateTime date)
    {
        //logic
        return new PrecalculateValue() ;
    }
    

    建议使用字典作为键值对的列表。