Important: Don't assume WaitAsync throws an exception if it times out
If the semaphore has available threads remaining (where CacheSemaphore.CurrentCount > 0
) then await CacheSemaphore.WaitAsync(5000)
will decrement that count and return immediately.
However, if CacheSemaphore.CurrentCount == 0
is already true, then await CacheSemaphore.WaitAsync(5000)
will wait 5 seconds and that's it! Your code will continue anyway and I'm sure I'm not the only one who assumed it would throw an exception.
So what this means is if you have 100 tasks 'protected from running' with WaitAsync(5000)
and a semaphore with a maximum size of 1 then without due care they'll all get to continue after 5 seconds. That probably isn't what you want.
What are you supposed to do?
If using WaitAsync
with a timeout you need to check the value returned to see whether or not you actually acquired the lock.
if (await CacheSemaphore.WaitAsync(5000) == true){ // ok to run the task // release lock}else{ // not ok to run the task}
Obviously the == true
isn't actually needed, but I like the explicitness.
What's an even worse mistake you can make?
It's good practice to Release()
the semaphore inside a try/finally block. That ensures it is always released whether it fails or succeeds.
However if using a timeout, it's very important not to accidentally Release()
a lock you never obtained:
Don't do this:
try { if (await CacheSemaphore.WaitAsync(5000) == true) { // ok to run the task } else { // not ok to run the task }}finally{ // `Release` just blindly increments the counter. // So, we may release a 'slot' we never were able to claim CacheSemaphore.Release();}
Do this:
if (await CacheSemaphore.WaitAsync(5000) == true){ try { // ok to run the task } finally { CacheSemaphore.Release(); }}else{ // not ok to run the task}