The following class is supposed to test, if an expected number of asynchronous Callbacks was received:
class Program
{
static List<int> callbackObjects = new List<int>();
static int expectedNumberOfCallbacks;
static void Main(string[] args)
{
expectedNumberOfCallbacks = 10;
Monitor.Enter(callbackObjects);
CallbackTest.CallbackTester.TriggerCallbacks(expectedNumberOfCallbacks,Callback);
Monitor.Wait(callbackObjects,10000);
Assert.AreEqual(callbackObjects.Count, expectedNumberOfCallbacks);
}
private static void Callback(int callbackObject)
{
callbackObjects.Add(callbackObject);
Console.WriteLine($"Callback number {callbackObjects.Count}");
if(callbackObjects.Count == expectedNumberOfCallbacks)
{
Monitor.Exit(callbackObjects);
}
}
}
The code outputs
Callback number 1
Callback number 2
Callback number 3
Callback number 4
Callback number 5
Callback number 6
Callback number 7
Callback number 8
Callback number 9
Callback number 10
but then it throws an
System.Threading.SynchronizationLockException: 'Object synchronization method was called from an unsynchronized block
on executing
Monitor.Exit(callbackObjects);
Is it because Monitor.Enter
and Monitor.Exit
are invoked in different scopes/threads?
How can I rewrite the code, to do the
Assert.AreEqual(callbackObjects.Count, expectedNumberOfCallbacks)
test in Main
, either after the expected number of callbacks was received or a predefined timeout?
CodePudding user response:
Is it because
Monitor.Enter
andMonitor.Exit
are invoked in different scopes/threads?
Yes, exactly that. Monitor
is thread-bound.
However, IMO you should also consider whether the multiple callbacks can themselves be overlapped; if they are, you need to synchronize inside the callback so that callbackObjects.Add
is well-defined. It could be that what you really need here is lock
and Pulse
:
static void Main(string[] args)
{
expectedNumberOfCallbacks = 10;
lock (callbackObjects)
{
CallbackTest.CallbackTester.TriggerCallbacks(
expectedNumberOfCallbacks,Callback);
Monitor.Wait(callbackObjects, 10000);
Assert.AreEqual(callbackObjects.Count, expectedNumberOfCallbacks);
}
}
private static void Callback(int callbackObject)
{
lock (callbackObjects)
{
callbackObjects.Add(callbackObject);
Console.WriteLine($"Callback number {callbackObjects.Count}");
if(callbackObjects.Count == expectedNumberOfCallbacks)
{
Monitor.Pulse(callbackObjects);
}
}
}
Wait
releases a monitor and waits for either a signal or a timeout to re-acquire it; Pulse
and PulseAll
are what sends that signal to a waiting thread (or threads).
You can also use the return value from Wait
to determine whether or not you got signaled (vs timeout).