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?
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
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 Semaphore
s 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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With