在ASP.NET Core中排队任务

3
例如功能 有20个用户,他们几乎同时点击了发送按钮,所以方法堆叠在队列中,第一个用户的消息被发送并接收到响应,然后是第二个、第三个等等。用户不会与其他人聊天,而是与设备聊天,其响应非常快。 因此,我正在尝试排队发送消息的任务。 我找到了使用任务队列的代码示例,如示例1示例2所示。
public class SerialQueue
{
    readonly object _locker = new object();
    WeakReference<Task> _lastTask;

    public Task Enqueue(Action action)
    {
        return Enqueue<object>(() => {
            action();
            return null;
        });
    }

    public Task<T> Enqueue<T>(Func<T> function)
    {
        lock (_locker)
        {
            Task lastTask = null;
            Task<T> resultTask = null;

            if (_lastTask != null && _lastTask.TryGetTarget(out lastTask))
            {
                resultTask = lastTask.ContinueWith(_ => function());
            }
            else
            {
                resultTask = Task.Run(function);
            }

            _lastTask = new WeakReference<Task>(resultTask);
            return resultTask;
        }
    }
}


例子2

 public class TaskQueue
{
    private readonly SemaphoreSlim _semaphoreSlim;

    public TaskQueue()
    {
        _semaphoreSlim = new SemaphoreSlim(1);
    }

    public async Task<T> Enqueue<T>(Func<Task<T>> taskGenerator)
    {
        await _semaphoreSlim.WaitAsync();

        try
        {
            return await taskGenerator();
        }
        finally
        {
            _semaphoreSlim.Release();
        }
    }

    public async Task Enqueue(Func<Task> taskGenerator)
    {
        await _semaphoreSlim.WaitAsync();
        try
        {
            await taskGenerator();
        }
        finally
        {
            _semaphoreSlim.Release();
        }
    }
}

问题在于当我将想要排队的任务传递给队列时(示例3),每次按下按钮时,任务仍然同时执行并相互中断。
 [HttpPost(Name = "add-message")]
        public async Task<IActionResult> PostMessage([FromBody] MessengerViewModel messengerViewModel)
        {
            TaskQueue taskQueue = new TaskQueue();
            SerialQueue serialQueue = new SerialQueue();

            await taskQueue.Enqueue(() => SendMessage(messengerViewModel.PhoneNr, messengerViewModel.MessageBody,
                messengerViewModel.ContactId, messengerViewModel.State));
//I'm not running tasks at same time, using one or other at time
            await serialQueue.Enqueue(() => SendMessage(messengerViewModel.PhoneNr, messengerViewModel.MessageBody,
                messengerViewModel.ContactId, messengerViewModel.State));

            return Ok();
        }

我该如何通过每次点击来解决问题并将任务堆叠到队列中?

1个回答

4
您的问题在于每次都创建一个新的TaskQueue和SerialQueue。因此,每次用户单击/调用PostMessage时,都会创建一个新队列,并且任务是队列中的第一个任务并直接执行。
您应该使用静态/单例队列,以便每次单击/调用都在同一个队列对象上工作。
但是,当您将Web应用程序扩展到多个服务器时,这可能会导致问题。为此,您应该使用像Azure Queue Storage与Azure Functions等组合使用的东西。

Startup.cs

public void ConfigureServices(IServiceCollection services)
{
    services.AddSingleton<TaskQueue>();
    services.AddSingleton<SerialQueue>();
    // the rest
}

SomeController.cs

[HttpPost(Name = "add-message")]
public async Task<IActionResult> PostMessage(
    [FromBody] MessengerViewModel messengerViewModel,
    [FromServices] TaskQueue taskQueue,
    [FromServices] SerialQueue serialQueue)
{
    await taskQueue.Enqueue(
        () => SendMessage(
                  messengerViewModel.PhoneNr, 
                  messengerViewModel.MessageBody,
                  messengerViewModel.ContactId, 
                  messengerViewModel.State));
    //I'm not running tasks at same time, using one or other at time
    await serialQueue.Enqueue(
        () => SendMessage(
                  messengerViewModel.PhoneNr, 
                  messengerViewModel.MessageBody,
                  messengerViewModel.ContactId, 
                  messengerViewModel.State));

    return Ok();
}

感谢您的回复,此程序将托管在本地服务器上,因此我不知道是否会出现与多个服务器连接的问题。 如果我理解正确,我需要将我的队列作为单例注入到 Startup.cs 中,对吗? - Daniel Žeimo
正确的做法是使用 DI 来获取队列。这样你就总是使用同一个对象。 - SynerCoder
services.AddSingleton<SerialQueue, SerialQueue>(); 我用这种方式注入了队列,但情况仍然相同,我是不是漏掉了什么重要的东西? - Daniel Žeimo
在控制器中添加了如何使用它的示例。 - SynerCoder

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接