Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to ensure that part of code (which contains async) would be called only once?

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?

like image 933
Vitalii Vasylenko Avatar asked Aug 20 '13 11:08

Vitalii Vasylenko


Video Answer


2 Answers

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

like image 156
xanatos Avatar answered Oct 05 '22 22:10

xanatos


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.

like image 39
MoonKnight Avatar answered Oct 05 '22 21:10

MoonKnight