我正在使用C#和.Net Framework 4.7开发ASP.NET Web Api应用程序。
我在控制器中有一个方法,我想一次只能由一个线程执行。换句话说,如果有人调用此方法,则另一个调用必须等到方法完成。
我发现这个SO answer可以完成这项工作。但在这里它使用一个队列,我不知道如何使用它来消耗该队列。在那个答案解释说我可以创建一个Windows服务来使用队列,但我不想在我的解决方案中添加另一个应用程序。
我想在Web Api方法中添加一个锁,如下所示:
[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;
}
}
但我不认为这是一个很好的解决方案,因为如果运行该方法时出现问题,它可能会阻止大量待处理的呼叫,我将丢失所有待处理的呼叫,或者我可能错了并且调用(线程) )将等到其他人结束。换句话说,我认为如果我使用这个,我就会陷入僵局。
我正在尝试这个,因为我需要按照收到的顺序执行调用。看看这个行动日志:
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] and [5]
。但是最后一个在第二个结束之前结束:[12], [5] and [13]
。
我需要一种不允许这种情况的机制。
我该怎样做才能确保调用的处理方式与我制作的顺序相同?
答案 0 :(得分:6)
您的锁定解决方案应该可以正常工作。如果请求失败,则锁定将被释放,然后其他待处理请求可以进入锁定。不会发生僵局。
此解决方案的唯一问题是,Web请求将在很长一段时间内挂起(这可能会导致客户端超时)。
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),直到您的工作完成为止。例如:
//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
}
}
}
答案 1 :(得分:0)
我猜你可以锁定不同级别,你的方法是一个。
我遇到过将redis用作外部服务的系统(或网络应用)。在redis中,我们为请求保存了一个密钥,在您的情况下,我猜它可能是方法的名称。使用这种方法,我们首先有一个动作过滤器,用于检查锁是否存在(与redis交谈),然后阻止请求。
redis的好处在于它非常快,让你指定一个超时密钥消失的超时时间。这可以防止永远卡在锁上。