Oblomov
Oblomov

Reputation: 9645

"Object synchronization method was called from an unsynchronized block of code" error in Callback test

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?

Upvotes: 1

Views: 599

Answers (1)

Marc Gravell
Marc Gravell

Reputation: 1063764

Is it because Monitor.Enter and Monitor.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).

Upvotes: 2

Related Questions