.NET Core视图lock语句中使用asp-*标签助手报CS1996错误
我们采用.NET Core 3.1 MVC架构,视图Index.cshtml需渲染单例服务返回的内存内集合。为避免多线程修改集合时foreach枚举抛出“Collection was modified; enumeration operation may not execute”异常,我们使用lock语句,但在lock块内使用asp-*标签助手时,编译报错CS1996“Cannot await in the body of a lock statement”;移除asp-*标签助手则编译正常。已知示例中lockObj初始化方式不合理,仅用于复现问题,现咨询:
- 该错误原因是什么?
asp-*标签助手内部是否使用异步方法?- 能否在
lock语句内使用asp-*标签助手?
1. 错误原因
C#的lock语句基于Monitor.Enter和Monitor.Exit实现线程独占逻辑,它的设计要求锁块内不能包含异步等待(await)操作。因为异步等待会让当前线程释放锁,后续恢复执行时可能切换到其他线程,这会彻底破坏锁的线程独占性,引发不可预测的线程安全问题。因此编译器会直接拦截这种写法,抛出CS1996错误。
2. asp-*标签助手内部是否使用异步方法?
是的,不少asp-*标签助手内部会调用异步方法。比如路由类标签助手(asp-action、asp-controller)在生成URL时,可能异步访问路由服务获取路由数据;模型绑定类标签助手(asp-for)处理复杂模型验证、数据转换时也可能涉及异步逻辑。这些异步操作最终会触发await调用,这就是在lock块内使用它们会触发编译错误的核心原因。
3. 能否在lock语句内使用asp-*标签助手?
不能直接在lock块内使用。因为标签助手的异步逻辑必然会引入await,而C#语法明确禁止在lock块内执行异步等待操作。
可行替代方案
- 提前生成集合快照:在
lock块内仅做集合复制操作,生成独立的快照后立即释放锁,后续在锁块外遍历快照并使用标签助手渲染:
List<YourModel> collectionSnapshot; lock(lockObj) { collectionSnapshot = _singletonService.TargetCollection.ToList(); } @foreach(var item in collectionSnapshot) { <a asp-action="ViewDetail" asp-route-id="@item.Id">@item.Title</a> }
- 替换为线程安全集合:将原内存集合替换为
ConcurrentBag<T>、ConcurrentDictionary<TKey, TValue>这类.NET内置线程安全集合,它们支持多线程场景下的安全枚举,无需手动加lock。
内容的提问来源于stack exchange,提问作者Aleks Vujic

