ASP.NET控制器中ConcurrentDictionary异常:状态查询时字典为空
首先,咱们来揪出最核心的问题:ASP.NET控制器的生命周期导致字典实例不共享。
你把taskDictionary声明为控制器的私有字段:
TaskDictionary taskDictionary = new TaskDictionary();
而ASP.NET(不管是Framework还是Core)的控制器默认是**瞬态(Transient)**生命周期——也就是说,每收到一个HTTP请求,框架就会创建一个全新的控制器实例。这就导致了:
- 调用
/aaa接口时,会生成一个控制器实例,它的taskDictionary里添加了任务条目; - 调用
/bbb接口查询状态时,会生成另一个完全独立的控制器实例,它的taskDictionary是全新的空字典,自然查不到任何内容,而且Count显示为0。
这就是日志里出现矛盾现象的根本原因:两个请求用的根本不是同一个字典实例。
解决方案:让字典成为全局共享的实例
有两种靠谱的实现方式:
1. 快速修复:使用静态字段
最简单的方式是把taskDictionary改成静态字段,这样所有控制器实例都会共享同一个字典:
private static TaskDictionary taskDictionary = new TaskDictionary();
放心,ConcurrentDictionary本身就是线程安全的,静态字段的方式在并发场景下不会有线程冲突问题。
2. 推荐方案:依赖注入注册为单例
如果是ASP.NET Core项目,更符合框架设计的方式是通过依赖注入把TaskDictionary注册为单例:
- 首先在
Program.cs(或旧版的Startup.cs)里注册:
builder.Services.AddSingleton<TaskDictionary>();
- 然后在控制器里通过构造函数注入:
private readonly TaskDictionary _taskDictionary; public MyController(TaskDictionary taskDictionary) { _taskDictionary = taskDictionary; }
这种方式更易于测试和维护,也符合依赖注入的设计原则。
额外需要检查的潜在问题
除了生命周期问题,还有一个点需要确认:TaskId这个自定义类型是否正确重写了Equals和GetHashCode方法?
因为ConcurrentDictionary是基于哈希表实现的,它依赖键的哈希值和相等性判断来查找条目。如果TaskId没有重写这两个方法,那么即使两个TaskId实例的内容完全相同,框架也会认为它们是不同的键,导致TryGetValue失败。
比如如果TaskId是这样的:
public class TaskId { public string Id { get; set; } }
就必须重写Equals和GetHashCode:
public class TaskId : IEquatable<TaskId> { public string Id { get; set; } public bool Equals(TaskId other) { if (other is null) return false; return string.Equals(Id, other.Id, StringComparison.OrdinalIgnoreCase); } public override bool Equals(object obj) => Equals(obj as TaskId); public override int GetHashCode() => Id?.GetHashCode(StringComparison.OrdinalIgnoreCase) ?? 0; }
不过从你的日志来看,这个问题不是当前的主要原因(因为POSTSTATE里的字典Count是0,而不是Count为1但找不到条目),但还是建议补上,避免后续出现其他问题。
代码里的小逻辑瑕疵优化
最后看一下POST方法里的冗余逻辑:
bool rc = taskDictionary.TryGetValue(inbound.taskId, out s); if (rc == false) taskDictionary.GetOrAdd(inbound.taskId, taskState); else return null; if (s != null) return null;
当TryGetValue返回false时,你调用了GetOrAdd但没把返回值赋值给s,后面的if (s != null)判断其实是多余的(因为刚进来s就是null)。可以优化成更简洁的写法:
var existingState = taskDictionary.GetOrAdd(inbound.taskId, taskState); if (existingState != taskState) { // 说明这个taskId已经存在,直接返回null return null; }
内容的提问来源于stack exchange,提问作者H2ONaCl

