我基本上是在尝试将一些组织及其ID和名称缓存在字典中。我不断收到错误消息:Item with Same Key has already been added
。这对我来说很奇怪,因为第一个if块会检查字典是否包含键,如果存在则退出该方法。
在下面的(简化)代码下,为什么会出现错误?
public async Task<IList<Stat>> GetStats(DateTime start, DateTime end, string role, long tenantId)
{
...
var tasks = new List<Task>();
foreach (var org in query)
tasks.Add(GetOrgName(org));
await Task.WhenAll(tasks);
return query;
}
private Dictionary<string, string> Orgs = new Dictionary<string, string>();
private async Task GetOrgName(Organization org)
{
if (Orgs.ContainsKey(org.Id))
{
org.Name = Orgs[org.Id];
return;
}
var result = await _directoryService.GetOrganization(org.Id);
if (result != null)
org.Name = result.DisplayName;
else
org.Name = org.Id;
Orgs.Add(org.Id, org.Name);
}
答案 0 :(得分:2)
此代码看起来很危险
if (org != null)
org.Name = org.DisplayName;
else
org.Name = org.Id;
如果org == null
我认为您的else
子句将引发NullReference
异常。但是你不明白
此功能可能存在问题
var org = await _directoryService.GetOrganization(org.Id);
请注意,此命令意味着org
可以被修改,并且可能不包含其原始的Id
例如:如果它从不返回空值,则只是简单的空白对象,您可能会遇到Item with Same Key has already been added
异常
我认为可以通过仔细检查来解决
if (!Orgs.ContainsKey(org.Id))
{
Orgs.Add(org.Id, org.Name);
}
答案 1 :(得分:2)
所以问题在于字典可以同时由多个任务访问。因此,在一项任务正在检查名称是否存在的同时,另一项正在添加名称。另外,由于字典不是线程安全的,因此您在当前的实现中可能会遇到随机异常。
如果您更改此设置:
var tasks = new List<Task>();
foreach (var org in query)
tasks.Add(GetOrgName(org))
收件人:
foreach (var org in query)
await GetOrgName(org);
您不会遇到这个问题。
另一种选择是对缓存使用线程安全集合。
但是,如果查询包含1000条内容,则您的代码可能会同时对_directoryService.GetOrganization
进行1000次调用。可能是个问题?
答案 2 :(得分:1)
由于字典由多个线程访问,因此应使用ConcurrentDictionary而不是Dictionary。否则可能会发生,两个线程试图同时添加同一密钥,从而导致您提到的异常。此外,字典不是线程安全的,如果由多个线程访问,则字典可能会损坏。
答案 3 :(得分:1)
在您进行最新编辑时,这似乎不是多线程问题,而仅仅是多个异步调用的问题-您很可能正在等待并针对同一GetOrganization
对org.Id
进行多次查询当第一个尚未完成时(因此词典尚未更新)。
因此,您很可能不需要ConcurrentDictionary
。您可以改为执行以下操作:
private Dictionary<string, Task> Orgs = new Dictionary<string, Task>();
private Task GetOrgName(Organization org)
{
Task nameTask;
if (!Orgs.TryGetValue(org.Id, out nameTask)
{
nameTask = GetOrganization(.org);
Orgs.Add(org.Id, nameTask);
}
return nameTask;
}
private async Task GetOrganization(Organization org)
{
// Consider using .ConfigureAwait(false) here...
var result = await _directoryService.GetOrganization(org.Id);
if (result != null)
org.Name = result.DisplayName;
else
org.Name = org.Id;
}
注意Dictionary
的签名。
答案 4 :(得分:0)
使字典搜索不区分大小写。您可以参考以下内容:
private async Task GetOrgName(Organization org)
{
if (Orgs.ContainsKey(org.Id.ToUpper()))
{
org.Name = Orgs[org.Id.ToUpper()];
return;
}
var result = await _directoryService.GetOrganization(org.Id.ToUpper());
if (result != null)
org.Name = result.DisplayName;
else
org.Name = org.Id;
Orgs.Add(org.Id.ToUpper(), org.Name);
}