I\'m developing an ASP.NET Web Api application with C# and .Net Framework 4.7.
I have a method in a controller that I want to execute only by one thread at a time. In ot
Your lock solution should work fine. If the request fails, then the lock will be released, and other pending requests can then enter the lock. A deadlock wont occur.
The only issue with this solution, is web requests will go on hanging for possibly long periods of time (Which may result in time outs from the client end).
public class MyApi : ApiController
{
public static readonly object LockObject = new object();
[HttpPut]
[Route("api/Public/SendCommissioning/{serial}/{withChildren}")]
public HttpResponseMessage SendCommissioning(string serial, bool withChildren)
{
lock ( LockObject )
{
//Do stuff
}
}
}
To solve the issue with hanging requests, you should utilize a queue, and poll the back end (Or if you're fancy, try SignalR) until your job is complete. For example:
//This is a sample with Request/Result classes (Simply implement as you see fit)
public static class MyBackgroundWorker
{
private static ConcurrentQueue<KeyValuePair<Guid, Request>> _queue = new ConcurrentQueue<KeyValuePair<Guid, Result>>()
public static ConcurrentDictionary<Guid, Result> Results = new ConcurrentDictionary<Guid, Result>();
static MyBackgroundWorker()
{
var thread = new Thread(ProcessQueue);
thread.Start();
}
private static void ProcessQueue()
{
KeyValuePair<Guid, Request> req;
while(_queue.TryDequeue(out req))
{
//Do processing here (Make sure to do it in a try/catch block)
Results.TryAdd(req.Key, result);
}
}
public static Guid AddItem(Request req)
{
var guid = new Guid();
_queue.Enqueue(new KeyValuePair(guid, req));
return guid;
}
}
public class MyApi : ApiController
{
[HttpPut]
[Route("api/Public/SendCommissioning/{serial}/{withChildren}")]
public HttpResponseMessage SendCommissioning(string serial, bool withChildren)
{
var guid = MyBackgroundWorker.AddItem(new Request(serial, withChildren));
return guid;
}
[HttpGet]
[Route("api/Public/GetCommissioning/{guid}")]
public HttpResponseMessage GetCommissioning(string guid)
{
if ( MyBackgroundWorker.Results.TryRemove(new Guid(guid), out Result res) )
{
return res;
}
else
{
//Return result not done
}
}
}
I guess you can lock on different levels, with your approach being one.
I've come across a system (or a web app) that utilizes redis as an external service. In redis we save a key for the request, in your case I guess it could be the name of the method. With this approach we first have an action filter that checks if a lock exists (talking to redis) and then blocks the request.
The good thing with redis is that it's very fast, and let's your specify a timeout at which the key will dissapear. This prevents from being stuck with the lock forever.