I have a web api controller that I want to restrict it to run only once at a time. If a new request comes I want it to be rejected and not queued. I've implemented this behaviour using a Mutex, like in the following code:
public IHttpActionResult Get()
{
string token = Utils.GetCurrentToken(RequestContext);
//Some irrelevant code
Task.Factory.StartNew(() =>
{
var mutexName = Utils.GetDBFromToken(token).ProjectID.ToString();
var success = Mutex.TryOpenExisting(mutexName, out Mutex mutex);
if (!success || mutex == null)
{
mutex = new Mutex(true, mutexName);
Utils.Log(token, mutexName + " - Mutex created");
}
else
{
Utils.Log(token, mutexName + " - Mutex exists");
return;
}
try
{
mutex.WaitOne();
try
{
Utils.Log(token, mutexName + " - Job started");
Thread.Sleep(10000);
Utils.Log(token, mutexName + " - Job ended");
}
catch (Exception ex)
{
//Handle error
}
}
finally
{
Utils.Log(token, mutexName + " - About to release mutex");
if (mutex != null)
{
mutex.ReleaseMutex();
mutex.Close();
mutex.Dispose();
Utils.Log(token, mutexName + " - Mutex released");
}
}
});
return Ok();
}
Now if I call the controller 3 times in success, I get the following logs, which is exactly what I expected:
2018-10-26 11:45:11.650 d6e4dd2e-16f1-43aa-b34b-226187dd9185 - Mutex created
2018-10-26 11:45:11.870 d6e4dd2e-16f1-43aa-b34b-226187dd9185 - Mutex exists
2018-10-26 11:45:11.963 d6e4dd2e-16f1-43aa-b34b-226187dd9185 - Mutex exists
2018-10-26 11:45:12.323 d6e4dd2e-16f1-43aa-b34b-226187dd9185 - Job started
2018-10-26 11:45:22.633 d6e4dd2e-16f1-43aa-b34b-226187dd9185 - Job ended
2018-10-26 11:45:22.947 d6e4dd2e-16f1-43aa-b34b-226187dd9185 - About to release mutex
2018-10-26 11:45:23.290 d6e4dd2e-16f1-43aa-b34b-226187dd9185 - Mutex released
But if I call it once more after release and close has been called, I just get
2018-10-26 11:46:35.133 d6e4dd2e-16f1-43aa-b34b-226187dd9185 - Mutex exists
Shouldn't TryOpenExisting
fail after mutex has been released? Am I doing something wrong?
Rather than use a Mutex
, you should consider Monitor.TryEnter instead.
It functions like lock
, but returns immediately (as you want) if the lock is already taken.
To do this, you will need to declare a static
lock object:
private static object lockObject;
Or, if you need multiple locks (eg per database) then you would store those locks in a:
private static ConcurrentDictionary<string, object> lockObjects = new ConcurrentDictionary<string, object>();
getting the lock object using GetOrAdd .
Also, this will not work if you are using a web farm / web garden / load balancer either (since the lock will be process specific). If this is an issue for you, I'd consider using a queue - whereby every web server adds entries to the queue and a single queue consumer pulls off the queue and de-dupes the requests.
Do you really need logic around whether the Mutex exists or not?
Why not always use var mutex = new Mutex(true, mutexName);
? You don't HAVE to check whether it exists or not. And that check of Mutex.TryOpenExisting
isn't threadsafe either.
EDIT
One more question, do you need a Mutex here, will a LOCK not be sufficient? Are you planning on running multiple instances of this WebAPI on the same server? If not, then a Lock should be sufficient.
Mutex
- Work across Processes
Lock
- Work across Threads
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.