C# 并发请求预防

C# 并发请求预防,c#,asp.net-web-api2,C#,Asp.net Web Api2,我想让我的API的一些方法被锁定(HttpStatus.Conflict),直到另一个具有相同参数的方法没有完成(比如?id=1&key=sd6gd0f1g5ds16fh),比如如果坏用户试图一次发出2+相同的请求,那么只会执行一个。 我的想法是使用信号灯: public class Lock : IDisposable { private bool _disposed = false; private readonly Semaphore _semaphore; p

我想让我的API的一些方法被锁定(
HttpStatus.Conflict
),直到另一个具有相同参数的方法没有完成(比如?id=1&key=sd6gd0f1g5ds16fh),比如如果坏用户试图一次发出2+相同的请求,那么只会执行一个。 我的想法是使用
信号灯

public class Lock : IDisposable
{
    private bool _disposed = false;

    private readonly Semaphore _semaphore;

    public bool IsLocked
    {
        get;
        private set;
    }

    public Lock(string name)
    {
        this.IsLocked = false;
        try
        {
            this._semaphore = Semaphore.OpenExisting(name);
            this._semaphore.Close();
        }
        catch (Exception)
        {
            this._semaphore = new Semaphore(0, 1, name);
            this.IsLocked = true;
        }
    }

    ~Lock()
    {
        this.Dispose(false);
    }

    public void Dispose()
    {
        this.Dispose(true);
        GC.SuppressFinalize(this);
    }

    protected virtual void Dispose(bool disposing)
    {
        if (!this._disposed)
        {
            if (disposing)
            {
                this._semaphore.Release();
                this._semaphore.Dispose();
            }

            this._disposed = true;
        }
    }
}
我是这样使用它的:

[ActionName("Ping")]
[HttpGet]
public IHttpActionResult Ping([FromUri]int? id = null, [FromUri]string key = null)
{
    if (id == null)
    {
        //ProcessException is some wrap for api error answer
        throw new ProcessException(HttpStatusCode.BadRequest, "Service ID is required");
    }

    if (key == null)
    {
        throw new ProcessException(HttpStatusCode.BadRequest, "Service Key is required");
    }

    Lock serviceLock = new Lock("service." + id + "." + key);
    if (!serviceLock.IsLocked)
    {
        throw new ProcessException(HttpStatusCode.Conflict, "Other Service operation already in progress");
    }

    var service = Service.Get((int)id, key);
    if (service == null) // Right hereino
    {
        throw new ProcessException(HttpStatusCode.Forbidden, "Service ID and/or Key is invalid");
    }

    Service.Touch((int)id);

    serviceLock.Dispose();

    //JResponse is some wrap for Dictionary<string, object>
    return Ok(new JResponse(true));
}
[ActionName(“Ping”)]
[HttpGet]
公共IHttpActionResult Ping([FromUri]int?id=null,[FromUri]string key=null)
{
if(id==null)
{
//ProcessException是api错误答案的一些包装
抛出新的ProcessException(HttpStatusCode.BadRequest,“需要服务ID”);
}
if(key==null)
{
抛出新的ProcessException(HttpStatusCode.BadRequest,“需要服务密钥”);
}
锁服务锁=新锁(“服务“+id+”+钥匙);
如果(!serviceLock.IsLocked)
{
抛出新的ProcessException(HttpStatusCode.Conflict,“其他服务操作已在进行”);
}
var service=service.Get((int)id,key);
if(service==null)//就在这里ino
{
抛出新的ProcessException(HttpStatusCode.Forbidden,“服务ID和/或密钥无效”);
}
Service.Touch((int)id);
Dispose();
//JResponse是字典的一些包装
返回Ok(新JResponse(true));
}
但我对它很陌生,有一些问题:

  • 我走的是正确的方向吗
  • 当我调用
    Dispose
    时,
    信号量在下一个请求中仍然存在。怎么了
  • 我的类是否会在某些异常情况下被释放(并释放信号量)?(如上面所示,如果
    service==null

  • 这并不完美,还有改进的余地,但我认为这可能会让你开始另一个思考方向或方式

    使用信号量锁定静态字典

    //ToDo: You would have to make this ThreadSafe
    public static class Helper
    {
        public static Dictionary<string,ClientDto> ClientDtos 
        = new Dictionary<string, ClientDto>();
    }
    
    public class ClientDto
    {
        public int ClientKey { get; set; }
        public string Key { get; set; }
        public DateTime CreatedOn { get; set; }
    }
    
    in your Global.asax add.
    protected void Application_EndRequest()
    {
        Helper.ClientDtos.Remove(SeesionId);
    }
    
    //if this is called twice by the same client and the request is 
    //not finished processing the first request the second one will go into    
    //RequestBeingHandled and just return preventing the code from preforming 
    //the same action until the first/current is complete.
    
    public IHttpActionResult Ping([FromUri]int? id = null, [FromUri]string key = null)
    {
        if(RequestBeingHandled(id, key))
        {
            //
            Return .....
        }
        else 
        {
            //if not add 
            ClientDto client = new ClientDto();
            client.ClientKey = id;
            client.Key = key;
            client.CreatedOn = DateTime.Now;
            Helper.ClientDtos.Add(SeesionId, client);
        }
        //call some code to do stuff...
    }
    
    private bool RequestBeingHandled(int id, string key)
    {
        //ToDo: write this code.
        //check if its already in the dic
        return bool;
    }
    
    //ToDo:您必须使此线程安全
    公共静态类助手
    {
    公共静态字典ClientDtos
    =新字典();
    }
    公共类客户端
    {
    public int ClientKey{get;set;}
    公共字符串密钥{get;set;}
    public DateTime CreatedOn{get;set;}
    }
    在您的Global.asax中添加。
    受保护的无效应用程序\u EndRequest()
    {
    Helper.ClientDtos.Remove(请参见SIONID);
    }
    //如果同一客户机调用了两次,并且请求
    //未完成处理第一个请求,第二个请求将进入
    //RequestBeingHandled并返回阻止代码预执行
    //在第一次/当前操作完成之前,执行相同的操作。
    公共IHttpActionResult Ping([FromUri]int?id=null,[FromUri]string key=null)
    {
    if(正在处理的请求(id、密钥))
    {
    //
    返回。。。。。
    }
    其他的
    {
    //如果没有添加
    ClientDto client=新ClientDto();
    client.ClientKey=id;
    client.Key=Key;
    client.CreatedOn=DateTime.Now;
    Helper.ClientDtos.Add(请参见客户机ID);
    }
    //调用一些代码来做一些事情。。。
    }
    私有bool请求被处理(int-id,字符串键)
    {
    //ToDo:编写此代码。
    //检查其是否已在驾驶员信息中心中
    返回布尔;
    }
    
    您是否已登录不希望并发请求的用户?是否要限制来自同一会话的并发请求?还是想在任何地方限制所有请求?@NathanCooper没有登录。对每个请求按键进行身份验证(/service/ping?id=1&key=1337)。我想通过相同的id和密钥限制此ping的并发执行。好的,这里有一个问题,因为GET操作没有副作用。您为什么关心?请求默认为单线程。。。。您试图做的是限制同一客户机可以发出的请求量。但是为什么呢?无论如何,您可以简单地拥有一个静态字典,它记录客户端在最近一分钟或任何时间段内发出了多少请求。您需要建立一种机制来清除失败的响应,即在出现异常/错误时字典中留下的脏数据。@NathanCooper,这是错误的示例代码。还有其他方法应该只向数据库写入一次内容。但对于concurent请求,它可以被写入2次以上,因为首先我们从数据库中选择,检查所选信息,然后更新。使用命名的
    信号量
    不是更好吗,比如
    “service.id.key”
    ?@ShamilYakupov我不知道在请求之间如何全局访问锁类。。。。。请求=q.e.g。q1、q2、q3、q4所有不同的客户端都有不同的锁类。。。。现在,如果它们都是相同的客户端,那么同样的事情,它们都有不同的锁类实例。。。所以这对我来说没有意义。您当前的设计是1q=1thread=1lock类,您需要的是1q=1thread和一个在所有q之间存在的全局。希望这能澄清这一点。互斥不适用于整个应用程序吗?我正在检查它是否按名称存在,如果存在,请阻止此并发操作。@ShamilYakupov lol你让我用谷歌互斥,互斥是一种编程技术,可以存档你想要的东西。。。但你并没有以这样的方式实施它。。。这就是我想告诉你的。因此,当前您的代码将永远无法工作,因为没有全局类。阅读一下他们是如何归档互斥的,然后你就会看到。。。。你需要在字典中实现它,这是我已经说过的。奇怪的是,有人否决了评论“并发请求预防”,这是不正确的!!!很奇怪,好像他们没有阅读或不理解。