I have the following base code. The ActionMonitor
can be used by anyone, in whatever setting, regardless of single-thread or multi-thread.
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;
}
}
On exactly one occasion, when I examined a crash on client's machine, an exception was thrown at:
System.Core: System.InvalidOperationException Collection was modified;enumeration operation may not execute. at
System.Collections.Generic.HashSet`1.Enumerator.MoveNext() at
WPFApplication.ActionMonitor.OnActionEnded()
My reaction when seeing this stack trace: this is unbelievable! This must be a .Net bug!.
Because although ActionMonitor
can be used in multithreading setting, but the crash above shouldn't occur-- all the _triggers
( the collection) modification happens inside a lock
statement. This guarantees that one cannot iterate over the collection and modifying it at the same time.
And, if _triggers
happened to contain an Action
that involves ActionMonitor
, then the we might get a deadlock, but it would never crash.
I have seen this crash exactly once, so I can't reproduce the problem at all. But base on my understanding of multithreading and lock
statement, this exception can never have occurred.
Do I miss something here? Or is it known that .Net can behave it a very quirky way, when it involves System.Action
?
ActionMonitor
is the actual code. – GravitonExecuteAfterAction
and can it be reached by thetrigger()
call? Because the one thing locks definitely won't help you with is if the thread doing the enumeration is also the thread doing the modification. locks are reentrant. – Damien_The_Unbelieverlock (_lockObj) { lock (_lockObj) { stuff(); } }
would not deadlock but executestuff()
unhindered. – Cobra_Fast