c# - 收藏已修改;即使在锁定语句中专门修改了集合,枚举操作也可能不会执行
问题描述
我有以下基本代码。ActionMonitor
无论是单线程还是多线程,任何人都可以在任何环境下使用。
using System;
public class ActionMonitor
{
public ActionMonitor()
{
}
private object _lockObj = new object();
public void OnActionEnded()
{
lock (_lockObj)
{
IsInAction = false;
foreach (var trigger in _triggers)
trigger();
_triggers.Clear();
}
}
public void OnActionStarted()
{
IsInAction = true;
}
private ISet<Action> _triggers = new HashSet<Action>();
public void ExecuteAfterAction(Action action)
{
lock (_lockObj)
{
if (IsInAction)
_triggers.Add(action);
else
action();
}
}
public bool IsInAction
{
get;private set;
}
}
恰好有一次,当我检查客户端机器上的崩溃时,在以下位置引发了异常:
System.Core:System.InvalidOperationException 集合已修改;枚举操作可能无法执行。在
System.Collections.Generic.HashSet`1.Enumerator.MoveNext() 在
WPFApplication.ActionMonitor.OnActionEnded()
看到这个堆栈跟踪时我的反应:这太不可思议了!这一定是一个 .Net 错误!
因为虽然ActionMonitor
可以在多线程设置中使用,但上面的崩溃不应该发生——所有的_triggers
(集合)修改都发生在一个lock
语句中。这保证了一个人不能同时迭代和修改集合。
而且,如果_triggers
碰巧包含一个Action
that ActionMonitor
,那么我们可能会陷入僵局,但它永远不会崩溃。
我只见过一次这种崩溃,所以我根本无法重现这个问题。但是根据我对多线程和lock
语句的理解,这个异常永远不会发生。
我在这里想念什么吗?或者是否知道.Net 可以以一种非常古怪的方式表现它,当它涉及时System.Action
?
解决方案
您没有针对以下调用屏蔽您的代码:
private static ActionMonitor _actionMonitor;
static void Main(string[] args)
{
_actionMonitor = new ActionMonitor();
_actionMonitor.OnActionStarted();
_actionMonitor.ExecuteAfterAction(Foo1);
_actionMonitor.ExecuteAfterAction(Foo2);
_actionMonitor.OnActionEnded();
Console.ReadLine();
}
private static void Foo1()
{
_actionMonitor.OnActionStarted();
//Notice that if you would call _actionMonitor.OnActionEnded(); here instead of _actionMonitor.OnActionStarted(); - you would get a StackOverflow Exception
_actionMonitor.ExecuteAfterAction(Foo3);
}
private static void Foo2()
{
}
private static void Foo3()
{
}
Damien_The_Unbeliever
仅供参考 - 这就是评论中正在谈论的场景。
要解决这个问题,唯一想到的两件事是
不要这样称呼它,这是您的课程,您的代码正在调用它,因此请确保遵守自己的规则
获取列表的副本并
_trigger
列举
关于第 1 点,您可以跟踪是否正在运行,如果在运行时被调用则OnActionEnded
抛出异常:OnActionStarted
private bool _isRunning = false;
public void OnActionEnded()
{
lock (_lockObj)
{
try
{
_isRunning = true;
IsInAction = false;
foreach (var trigger in _triggers)
trigger();
_triggers.Clear();
}
finally
{
_isRunning = false;
}
}
}
public void OnActionStarted()
{
lock (_lockObj)
{
if (_isRunning)
throw new NotSupportedException();
IsInAction = true;
}
}
关于第2点,这个怎么样
public class ActionMonitor
{
public ActionMonitor()
{
}
private object _lockObj = new object();
public void OnActionEnded()
{
lock (_lockObj)
{
IsInAction = false;
var tmpTriggers = _triggers;
_triggers = new HashSet<Action>();
foreach (var trigger in tmpTriggers)
trigger();
//have to decide what to do if _triggers isn't empty here, we could use a while loop till its empty
//so for example
while (true)
{
var tmpTriggers = _triggers;
_triggers = new HashSet<Action>();
if (tmpTriggers.Count == 0)
break;
foreach (var trigger in tmpTriggers)
trigger();
}
}
}
public void OnActionStarted()
{
lock (_lockObj) //fix the error @EricLippert talked about in comments
IsInAction = true;
}
private ISet<Action> _triggers = new HashSet<Action>();
public void ExecuteAfterAction(Action action)
{
lock (_lockObj)
{
if (IsInAction)
_triggers.Add(action);
else
action();
}
}
public bool IsInAction
{
get;private set;
}
}
推荐阅读
- azure-sql-database - 创建新索引时防止 DTU 耗尽
- makefile - 单个项目的多个 Makefile
- c# - 上课做什么?(带问号的类)在 C# 泛型类型约束中是什么意思?
- mysql - 使用 If 结构的约束
- python - 使用 python/numpy 创建一个复杂的矩阵
- python - 如何从 Google Places API 返回唯一值?
- vue.js - Vue 实例中基于身份验证检查的动态渲染函数
- python - 可以使用回归解决情感分类问题吗?
- javascript - react 应用程序中的 dangerouslySetInnerHTML 不适用于包含 Segment.io 标签
- reactjs - 启动 react-native 项目的不同方式之间的区别?