【问题标题】:Lazy evaluation in async environment throwing exceptions using HashSet异步环境中的延迟评估使用 HashSet 引发异常
【发布时间】:2020-03-14 04:41:46
【问题描述】:

下面有一个方法可以将 id 映射到 MyRecord 的集合到字典中。

Dictionary 值由于 Select.

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

该类的调用者检索给定 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;

        }

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

当多个线程具体化时,惰性求值是否会自行跳闸?

*这是例外:

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&lt;MyRecord&gt;(SqlQueries.MyQuery, commandTimeout: _commandTimeout))

【问题讨论】:

  • 你确定这可以编译吗?你的 BuildMap 方法不是异步的,
  • 抛出异常的那一行是什么?
  • @this 是伪代码,但我将 await 移至 repo 调用
  • @fabio 异常来自internStrings.Add(t.Title);
  • @Smolakian 这是个例外:System.ArgumentException: Destination array was not long enough. Check the destination index, length, and the array's lower bounds.

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


【解决方案1】:

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

如果您担心字符串实例,我建议您摆脱 HashSet&lt;string&gt; 并直接使用 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;

    }

【讨论】:

  • 我避免使用 build instring.Intern 因为我只想在方法调用的生命周期内(即重建缓存时)将其保存在内存中,而不是应用程序的生命周期或更长时间..来自文档:the memory allocated for interned String objects is not likely to be released until the common language runtime (CLR) terminates
  • 祝你好运! ConcurrentDictionary&lt;T,K&gt; 是你最好的选择,它具有更高的内存占用,因为它会被异步上下文中的 lambda 表达式捕获,这将是普通人无法理解的一生。
  • 在 Select 之后实现它怎么样? (v) =&gt; v.Select(t =&gt; { t.Title = string.Intern(t.Title); return t; }).ToList()
  • 这对你没有任何好处,string.Intern() 电话仍然存在。
  • 我的意思是:.ToDictionary((k) =&gt; k.Key, (v) =&gt; v.Select(t =&gt; { if (internStrings.TryGetValue(t.Title, out string interned)) t.Title = interned; else internStrings.Add(t.Title); return t; }).ToList()
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-02-02
相关资源
最近更新 更多