C# 并发web api请求以及如何在ASP.NET核心中处理状态

C# 并发web api请求以及如何在ASP.NET核心中处理状态,c#,asp.net-web-api,asp.net-core,design-patterns,concurrency,C#,Asp.net Web Api,Asp.net Core,Design Patterns,Concurrency,ASP.NET核心2.1应用程序(实体框架),由多个web api端点组成。 其中之一是“加入”端点,用户可以在这里加入队列。 另一个是“离开”端点,用户可以在这里离开队列 该队列有10个可用位置 如果所有10个点都填好了,我们会抛出一条消息说:“队列已满。” 如果只有3个用户加入,我们将返回true 如果加入的用户数不是3,则返回false 200个喜欢触发的用户准备加入和离开不同的队列。它们都同时调用“加入”和“离开”端点 这意味着我们必须以一种顺序的方式处理传入的请求,以确保以一种友好和可

ASP.NET核心2.1应用程序(实体框架),由多个web api端点组成。 其中之一是“加入”端点,用户可以在这里加入队列。 另一个是“离开”端点,用户可以在这里离开队列

该队列有10个可用位置

如果所有10个点都填好了,我们会抛出一条消息说:“队列已满。”

如果只有3个用户加入,我们将返回true

如果加入的用户数不是3,则返回false

200个喜欢触发的用户准备加入和离开不同的队列。它们都同时调用“加入”和“离开”端点

这意味着我们必须以一种顺序的方式处理传入的请求,以确保以一种友好和可控的方式将用户添加和删除到正确的队列中。(对吗?)

一个选项是在
IServiceCollection
中添加
QueueService
类作为
AddSingleton
,然后进行
lock()
以确保一次只有一个用户可以进入。但是,由于它注册为
AddTransient
AddScoped
,因此如何处理
dbContext

连接零件的Psedudo代码:

public class QueueService
{
    private readonly object _myLock = new object();
    private readonly QueueContext _context;

    public QueueService(QueueContext context)
    {
        _context = context;
    }

    public bool Join(int queueId, int userId)
    {
        lock (_myLock)
        {
            var numberOfUsersInQueue = _context.GetNumberOfUsersInQueue(queueId); <- Problem. 
            if (numberOfUsersInQueue >= 10)
            {
                throw new Exception("Queue is full.");
            }
            else
            {
                _context.AddUserToQueue(queueId, userId); <- Problem.
            }

            numberOfUsersInQueue = _context.GetNumberOfUsersInQueue(queueId); <- Problem.

            if (numberOfUsersInQueue == 3)
            {
                return true;
            }
        }
        return false;
    }
}
公共类排队服务
{
私有只读对象_myLock=new object();
私有只读队列上下文_上下文;
公共队列服务(队列上下文)
{
_上下文=上下文;
}
公共bool连接(int-queueId,int-userId)
{
锁(_myLock)
{
var numberOfUsersInQueue=_context.GetNumberOfUsersInQueue(queueId);=10)
{
抛出新异常(“队列已满”);
}
其他的
{
_AddUserToQueue(queueId,userId);
这意味着我们必须以一种有序的方式处理传入的请求,以确保用户以一种良好且可控的方式添加和删除到正确的队列中。(对吗?)

基本上是的。请求可以并发执行,但它们必须以某种方式彼此同步(例如锁或数据库事务)

我应该处理内存中队列的状态吗

通常,最好将web应用程序设置为无状态且请求独立。在此模型下,状态存储在数据库中。最大的优点是无需进行同步,应用程序可以在多个服务器上运行。此外,如果应用程序重新启动(或崩溃),则不会丢失任何状态

这在这里似乎是完全可能和适当的

将状态放在关系数据库中,并使用并发控制使并发访问安全。例如,使用可序列化隔离级别,在死锁情况下重试。这为您提供了一个非常好的编程模型,在该模型中,您可以假装自己是数据库的唯一用户,但它是完全安全的。所有访问都必须放在事务处理中在出现死锁时,必须重试整个事务


如果您坚持内存状态,则将单例组件和瞬态组件拆分。将全局状态放入单例类,并将作用于该状态的操作放入瞬态类。这样,瞬态组件(如数据库访问)的依赖项注入非常简单和干净。全局类应该很小(可能只是数据字段)。

我最终得到了这个简单的解决方案

使用
ConcurrentDictionary
创建一个静态(或单例)类,该类接受queueId和锁

创建新队列时,将queueId和新锁定对象添加到字典中

使QueueService类
AddTransient
,然后:

public bool Join(int queueId, int userId)
    {
        var someLock = ConcurrentQueuePlaceHolder.Queues[queueId];
        lock (someLock)
        {
            var numberOfUsersInQueue = _context.GetNumberOfUsersInQueue(queueId); <- Working
            if (numberOfUsersInQueue >= 10)
            {
                throw new Exception("Queue is full.");
            }
            else
            {
                _context.AddUserToQueue(queueId, userId); <- Working
            }

            numberOfUsersInQueue = _context.GetNumberOfUsersInQueue(queueId); <- Working

            if (numberOfUsersInQueue == 3)
            {
                return true;
            }
        }
        return false;
    }
public bool连接(int-queueId,int-userId)
{
var someLock=ConcurrentQueuePlaceHolder.Queues[queueId];
锁(someLock)
{
var numberOfUsersInQueue=_context.GetNumberOfUsersInQueue(queueId);=10)
{
抛出新异常(“队列已满”);
}
其他的
{

_context.AddUserToQueue(队列ID,用户ID);谢谢。我同意将状态保留在db中,这是我在示例中尝试做的,但由于单例,我遇到了dbContext问题。您建议将QueueService更改为transient,移除锁,然后在db端使用并发控制,对吗?我希望两个或更多具有相同da的传入请求ta,这会在数据库上产生冲突。(?)您可以使用事务以原子方式查询和更新数据库。我的回答对此进行了详细说明。