问题描述
我正在使用C#和.Net Framework 4.7开发ASP.NET Web Api应用程序.
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 other words, if someone calls this method, another call must wait until the method has finished.
我已经找到可以完成此任务的 SO答案.但是这里它使用一个队列,我不知道如何使用它来消耗该队列.在该答案中,我可以创建一个Windows服务来使用队列,但不想在解决方案中添加其他应用程序.
I have found this SO answer that could do the job. But here it uses a queue and I don't know how to do it to consume that queue. In that answer explains that I can create a windows service to consume the queue but I don't want to add another application to my solution.
我想像这样在Web Api方法中添加一个锁:
I thought to add a lock inside the Web Api method like this:
[HttpPut]
[Route("api/Public/SendCommissioning/{serial}/{withChildren}")]
public HttpResponseMessage SendCommissioning(string serial, bool withChildren)
{
lock
{
string errorMsg = "Cannot set commissioning.";
HttpResponseMessage response = null;
bool serverFound = true;
try
{
[ ... ]
}
catch (Exception ex)
{
_log.Error(ex.Message);
response = Request.CreateResponse(HttpStatusCode.InternalServerError);
response.ReasonPhrase = errorMsg;
}
return response;
}
}
但是我认为这不是一个好的解决方案,因为如果方法运行出现问题,它可能会阻塞许多挂起的调用,并且我将丢失所有挂起的调用,或者我错了,并且这些调用(线程)将一直等到其他人结束.换句话说,我认为如果使用this可能会陷入僵局.
But I don't think this is a good solution because it could block a lot of pending calls if there is a problem running the method and I will lost all the pending calls or maybe I'm wrong and the calls (threads) will wait until the others end. In other words, I think if I use the this I could reach a deadlock.
我正在尝试此操作,因为我需要按照接收到的顺序执行调用.查看此操作日志:
I'm trying this because I need to execute the calls in the same order I receive it. Look at this action log:
2017-06-20 09:17:43,306 DEBUG [12] WebsiteAction - ENTERING PublicController::SendCommissioning , serial : 38441110778119919475, withChildren : False
2017-06-20 09:17:43,494 DEBUG [13] WebsiteAction - ENTERING PublicController::SendCommissioning , serial : 38561140779115949572, withChildren : False
2017-06-20 09:17:43,683 DEBUG [5] WebsiteAction - ENTERING PublicController::SendCommissioning , serial : 38551180775118959070, withChildren : False
2017-06-20 09:17:43,700 DEBUG [12] WebsiteAction - EXITING PublicController::SendCommissioning
2017-06-20 09:17:43,722 DEBUG [5] WebsiteAction - EXITING PublicController::SendCommissioning
2017-06-20 09:17:43,741 DEBUG [13] WebsiteAction - EXITING PublicController::SendCommissioning
在它们结束之前,我收到了三个调用:线程 [12],[13]和[5]
.但是最后一个在第二个之前结束: [12],[5]和[13]
.
I receive three calls before any of them end: threads [12], [13] and [5]
. But the last one ends before the second one: [12], [5] and [13]
.
我需要一种不允许这样做的机制.
I need a mechanism to don't allow this.
我该怎么做才能确保以与拨打电话相同的顺序处理呼叫?
What can I do to ensure that the calls will be process in the same order that I made them?
推荐答案
您的锁定解决方案应该可以正常工作.如果请求失败,则将释放该锁,然后其他未决请求可以输入该锁.不会发生死锁.
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.
此解决方案的唯一问题是Web请求可能会长时间挂起(这可能导致客户端超时).
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
}
}
}
要解决挂起请求的问题,您应该利用队列,并轮询后端(或者,如果愿意,可以尝试SignalR),直到完成工作为止.例如:
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
}
}
}
这篇关于锁定Web API控制器方法的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!