C# 锁定Web API控制器方法

C# 锁定Web API控制器方法,c#,multithreading,web-services,asp.net-web-api,C#,Multithreading,Web Services,Asp.net Web Api,我正在用C#和.NET Framework 4.7开发一个ASP.NET Web Api应用程序 我在控制器中有一个方法,我希望一次只由一个线程执行。换句话说,如果有人调用此方法,则另一个调用必须等待该方法完成 我发现这个可以做这项工作。但这里它使用了一个队列,我不知道如何使用这个队列。在该回答中,我解释了我可以创建一个windows服务来使用队列,但我不想向我的解决方案中添加另一个应用程序 我想在Web Api方法中添加一个锁,如下所示: [HttpPut] [Route("api/Publi

我正在用C#和.NET Framework 4.7开发一个ASP.NET Web Api应用程序

我在控制器中有一个方法,我希望一次只由一个线程执行。换句话说,如果有人调用此方法,则另一个调用必须等待该方法完成

我发现这个可以做这项工作。但这里它使用了一个队列,我不知道如何使用这个队列。在该回答中,我解释了我可以创建一个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]和[5]
。但是最后一个在第二个之前结束:
[12]、[5]和[13]

我需要一个机制来阻止这一切


如何确保调用的处理顺序与我的调用顺序相同?

您的锁定解决方案应该可以正常工作。如果请求失败,锁将被释放,其他挂起的请求可以进入锁。僵局不会发生

此解决方案的唯一问题是web请求可能会挂起很长一段时间(这可能会导致客户端超时)

要解决挂起请求的问题,您应该使用队列,并轮询后端(如果您喜欢,请尝试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
        }
    }
}
//这是一个包含请求/结果类的示例(只需根据需要实现)
公共静态类MyBackgroundWorker
{
私有静态ConcurrentQueue _queue=新ConcurrentQueue()
公共静态ConcurrentDictionary结果=新建ConcurrentDictionary();
静态MyBackgroundWorker()
{
var thread=新线程(ProcessQueue);
thread.Start();
}
私有静态void ProcessQueue()
{
键值对请求;
while(_queue.TryDequeue(out-req))
{
//在此处执行处理(确保在try/catch块中执行)
Results.TryAdd(请求键,结果);
}
}
公共静态Guid附加项(请求请求)
{
var guid=new guid();
_排队(新的KeyValuePair(guid,req));
返回guid;
}
}
公共类MyApi:ApiController
{
[HttpPut]
[路由(“api/Public/SendCommissioning/{serial}/{withChildren}”)]
公共HttpResponseMessage发送调试(字符串串行,带子项的bool)
{
var guid=MyBackgroundWorker.AddItem(新请求(串行,带子项));
返回guid;
}
[HttpGet]
[路由(“api/Public/getcommission/{guid}”)]
公共HttpResponseMessage GetCommission(字符串guid)
{
if(MyBackgroundWorker.Results.TryRemove(新Guid,out Result res))
{
返回res;
}
其他的
{
//返回结果未完成
}
}
}

我想你可以锁定不同的级别,你的方法就是其中之一

我遇到过一个系统(或web应用程序),它利用redis作为外部服务。在redis中,我们为请求保存了一个键,在您的情况下,我猜它可能是方法的名称。使用这种方法,我们首先有一个动作过滤器,检查锁是否存在(与redis对话),然后阻止请求


redis的优点是速度非常快,让我们指定一个键消失的超时时间。这可以防止永远被锁卡住。

你是什么意思
我认为这不是一个好的解决方案,因为它可能会阻止许多挂起的调用。
?这不正是你想要的吗?他们应该按照要求的顺序一次处理一个,并且只有当它们完成时才返回?@FrankerZ我用这样的解释更新了这个问题:“但我认为这不是一个好的解决方案,因为如果运行该方法时出现问题,它可能会阻止许多挂起的调用,我将丢失所有挂起的调用,或者可能是我错了,调用(线程)将等待其他调用结束。换句话说,我认为如果我使用this,可能会出现死锁。'注意:这对异步代码不起作用。@Simon_Weaver您可以简单地使用SemaphoreSlim进行异步
//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
        }
    }
}