8

So, I have a method, which contains async call to the server. That code is called from 3rd party tool, which somehow sometimes calls same method several times in a row from different threads, so I can't affect that.

What I want to be sure is that my method is called once, and then, another calls should be ignored.

At first, I tried to lock(locker) with bool isBusy, but that is not satisfied me, as async request was still called several times from second thread, which was fast enough to see isBusy=true;

Then, I tried Monitor

                object obj = new object();
                Monitor.TryEnter(obj);
                try
                {

                    var res = await _dataService.RequestServerAsync(SelectedIndex, e.StartIndex, e.Count);

                    ****
                }
                finally 
                {
                    Monitor.Exit(obj);
                }

However, on Exit(), I'm getting exception:

A first chance exception of type 'System.Threading.SynchronizationLockException'

Is there any other way to guarantee only 1 time execution of the code?

1
  • What 3rd party tool are you using? Commented Aug 21, 2013 at 20:00

2 Answers 2

14

Put in the class:

private int entered = 0;

and in the method:

if (Interlocked.Increment(ref entered) != 1)
{
    return;
}

Only the first call to the method will be able to change entered from 0 to 1. The others will make it 2, 3, 4, 5...

Clearly you'll need something to reset the entered if you want your method to be refireable...

Interlocked.Exchange(ref entered, 0);

at the end of a successful call to the method.

Ah... and it isn't possible to use lock/Monitor.* around an await, because the current thread of the method can change, while nearly all the synchronization libraries expect that the thread you use to enter a lock is the same you use to exit the lock.

You can even use the Interlocked.CompareExchange()...

if (Interlocked.CompareExchange(ref entered, 1, 0) != 0)
{
    return;
}

The first thread to enter will be able to exchange the value of entered from 0 to 1, and it will receive the old value, 0 (so failing the if and continuing in the remaining code). The other threads will fail the CompareExchange and see the "current" value of 1, so entering the if and exiting the method

4
  • Great answer, thanks. Can i also use Exchange() instead of Increment()? I would be needed to store some info about current page, so its looking like a nice place to store that info. Commented Aug 20, 2013 at 11:25
  • 3
    @VitaliiVasylenko No, you would need to use if (Interlocked.CompareExchange(ref entered, 1, 0) != 0) return;. The first one to enter is able to change from 0 to 1 and receives the old value (0). The others fail the change and receive the current value (1).
    – xanatos
    Commented Aug 20, 2013 at 11:29
  • Hm.. anyway, thanks for answer - now i know, where to jump into for additional info. Commented Aug 20, 2013 at 11:31
  • 1
    @VitaliiVasylenko Technically the CompareExchange is safer, because after 4 billion calls the Interlocked.Increment will return to 0 and have a success.
    – xanatos
    Commented Aug 20, 2013 at 11:32
4

If you do want to restrict multiple threads using the same method concurrently then I would use the Semaphore class to facilitate the required thread limit; here's how...

A semaphore is like a mean night club bouncer, it has been provide a club capacity and is not allowed to exceed this limit. Once the club is full, no one else can enter... A queue builds up outside. Then as one person leaves another can enter (analogy thanks to J. Albahari).

A Semaphore with a value of one is equivalent to a Mutex or Lock except that the Semaphore has no owner so that it is thread ignorant. Any thread can call Release on a Semaphore whereas with a Mutex/Lock only the thread that obtained the Mutex/Lock can release it.

Now, for your case we are able to use Semaphores to limit concurrency and prevent too many threads from executing a particular piece of code at once. In the following example five threads try to enter a night club that only allows entry to three...

class BadAssClub
{
    static SemaphoreSlim sem = new SemaphoreSlim(3);
    static void Main()
    {
        for (int i = 1; i <= 5; i++) 
            new Thread(Enter).Start(i);
    }

    // Enfore only three threads running this method at once.
    static void Enter(int i)
    {
        try
        {
            Console.WriteLine(i + " wants to enter.");
            sem.Wait();
            Console.WriteLine(i + " is in!");
            Thread.Sleep(1000 * (int)i);
            Console.WriteLine(i + " is leaving...");
        }
        finally
        {
            sem.Release();
        }
    }
}

Note, that SemaphoreSlim is a lighter weight version of the Semaphore class and incurs about a quarter of the overhead. it is sufficient for what you require.

I hope this helps.

1
  • 1
    There is no reason why they cannot be used inside an async method.
    – MoonKnight
    Commented Aug 20, 2013 at 11:29

Not the answer you're looking for? Browse other questions tagged or ask your own question.