异步环境中的惰性评估使用HashSet引发异常

时间:2019-11-18 18:36:30

标签: c# .net linq .net-core

下面我有一个将id映射到MyRecord的集合到Dictionary中的方法。

由于选择,对字典值的计算是延迟的。

此方法调用的结果在异步环境中的其他地方用作线程安全变量。

该类的调用者检索给定ID的记录列表并将其具体化。

_cache = await BuildCache();

var list = _cache[id].ToList();

如果两个线程同时实现一个值会怎样?

T1 _cache[123].ToList();

T2 _cache[123].ToList();

        private async Task<IDictionary<int, IEnumerable<MyRecord>> BuildCache()
        {
            var records = await _repo.GetAll();
            return BuildMap(records);
        }

        private IDictionary<int, IEnumerable<MyRecord>> BuildMap(IEnumerable<MyRecord> records)
        {
            var internStrings = new HashSet<string>();

            var map = records?
                .GroupBy(x => x.PersonId)
                .ToDictionary((k) => k.Key, (v) => v.Select(t =>
                {
                    if (internStrings.TryGetValue(t.Title, out string interned))
                        t.Title= interned;
                    else
                        internStrings.Add(t.Title);
                    return t;
                }));

            return map;

        }

我问的原因是因为从internStrings.Add(t.Title)调整大小时,hashSet抛出错误

当多个线程实现时,惰性评估是否会跳闸?

*这是例外:

System.ArgumentException: Destination array was not long enough. Check the destination index, length, and the array's lower bounds.
   at System.Array.Copy(Array sourceArray, Int32 sourceIndex, Array destinationArray, Int32 destinationIndex, Int32 length, Boolean reliable)
   at System.Array.Copy(Array sourceArray, Int32 sourceIndex, Array destinationArray, Int32 destinationIndex, Int32 length)
   at System.Collections.Generic.HashSet`1.SetCapacity(Int32 newSize)
   at System.Collections.Generic.HashSet`1.IncreaseCapacity()
   at System.Collections.Generic.HashSet`1.AddIfNotPresent(T value)

注意:底层IEnumerable来自精简查询而不是EF,我不是在问LINQ to SQL

connection.QueryAsync<MyRecord>(SqlQueries.MyQuery, commandTimeout: _commandTimeout))

1 个答案:

答案 0 :(得分:0)

很可能在一个线程调整大小时出现另一个线程并更改了数组大小,从而导致您看到异常。

如果您担心字符串实例,我建议您不要使用HashSet<string>,而直接使用string.Intern()

    private async Task<IDictionary<int, IEnumerable<MyRecord>> BuildCache()
    {
        var records = await _repo.GetAll();
        return BuildMap(records);
    }

    private IDictionary<int, IEnumerable<MyRecord>> BuildMap(IEnumerable<MyRecord> records)
    {
        var map = records?
            .GroupBy(x => x.PersonId)
            .ToDictionary((k) => k.Key, (v) => v.Select(t => { t.Title = string.Intern(t.Title); return t; }));

        return map;

    }