我有什么选择来使这个代码线程安全?

时间:2010-06-18 22:03:41

标签: c# collections thread-safety

我有这段代码,为了简洁省略了很多事情,但场景是这样的:

 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 个答案:

答案 0 :(得分:5)

嗯,首先,这一行:

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> - 它会提供与DateTime相比,基于List<PrecalculateValue>密钥的闪电般快速的O(1)查找,而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方法是过度的。如果您只是想获得第一个值并忽略可能的重复项,{{1}}更安全(例如,更少的异常机会)和更快(因为它不必遍历整个集合)。

答案 1 :(得分:1)

可以使用类似的东西

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

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