C# Aws Sqs消费者-仅当消息可以立即处理时进行轮询

C# Aws Sqs消费者-仅当消息可以立即处理时进行轮询,c#,concurrency,task-parallel-library,amazon-sqs,C#,Concurrency,Task Parallel Library,Amazon Sqs,我正在尝试创建一个AWS SQS windows服务使用者,它将以10个为一批轮询消息。每个消息将在其自己的任务中执行,以便并行执行。消息处理包括调用不同的api和发送电子邮件,因此可能需要一些时间 我的问题是,首先,我只想在可以立即处理10条消息时轮询队列。这是由于sqs可见性超时,接收到的消息“wait”可能超过可见性超时并“返回”队列。这将产生重复。我不认为调整可见性超时是好的,因为仍然有消息被复制的机会,这正是我试图避免的。其次,我希望对并行性有某种限制(例如,100个并发任务的最大限制

我正在尝试创建一个AWS SQS windows服务使用者,它将以10个为一批轮询消息。每个消息将在其自己的任务中执行,以便并行执行。消息处理包括调用不同的api和发送电子邮件,因此可能需要一些时间

我的问题是,首先,我只想在可以立即处理10条消息时轮询队列。这是由于sqs可见性超时,接收到的消息“wait”可能超过可见性超时并“返回”队列。这将产生重复。我不认为调整可见性超时是好的,因为仍然有消息被复制的机会,这正是我试图避免的。其次,我希望对并行性有某种限制(例如,100个并发任务的最大限制),这样服务器资源就可以被隔离,因为服务器中还有其他应用程序在运行


如何做到这一点?或者有其他方法来解决这些问题吗?

此答案做出以下假设:

  • 从AWS获取消息应序列化。只有消息处理应该并行化
  • 应处理从AWS获取的每条消息。在处理所有获取的消息之前,不应终止整个执行
  • 应等待每个消息处理操作。整个执行不应在所有已启动任务完成之前终止
  • 处理消息期间发生的任何错误都应忽略。整个执行不应因为处理单个消息失败而终止
  • 从AWS获取消息期间发生的任何错误都是致命的。整个执行应该终止,但不能在当前运行的所有消息处理操作完成之前终止
  • 执行机制应该能够处理这样的情况:从AWS操作获取返回的批处理的消息数量与请求的数量不同
  • 以下是(希望)满足这些要求的实现:

    /// <summary>
    /// Starts an execution loop that fetches batches of messages sequentially,
    /// and process them one by one in parallel.
    /// </summary>
    public static async Task ExecutionLoopAsync<TMessage>(
        Func<int, Task<TMessage[]>> fetchMessagesAsync,
        Func<TMessage, Task> processMessageAsync,
        int fetchCount,
        int maxDegreeOfParallelism,
        CancellationToken cancellationToken = default)
    {
        // Arguments validation omitted
        var semaphore = new SemaphoreSlim(maxDegreeOfParallelism, maxDegreeOfParallelism);
    
        // Count how many times we have acquired the semaphore, so that we know
        // how many more times we have to acquire it before we exit from this method.
        int acquiredCount = 0;
        try
        {
            while (true)
            {
                Debug.Assert(acquiredCount == 0);
                for (int i = 0; i < fetchCount; i++)
                {
                    await semaphore.WaitAsync(cancellationToken);
                    acquiredCount++;
                }
    
                TMessage[] messages = await fetchMessagesAsync(fetchCount)
                    ?? Array.Empty<TMessage>();
    
                for (int i = 0; i < messages.Length; i++)
                {
                    if (i >= fetchCount) // We got more messages than we asked for
                    {
                        await semaphore.WaitAsync();
                        acquiredCount++;
                    }
                    ProcessAndRelease(messages[i]);
                    acquiredCount--;
                }
    
                if (messages.Length < fetchCount)
                {
                    // We got less messages than we asked for
                    semaphore.Release(fetchCount - messages.Length);
                    acquiredCount -= fetchCount - messages.Length;
                }
    
                // This method is 'async void' because it is not expected to throw ever
                async void ProcessAndRelease(TMessage message)
                {
                    try { await processMessageAsync(message); }
                    catch { } // Swallow exceptions
                    finally { semaphore.Release(); }
                }
            }
        }
        catch (SemaphoreFullException)
        {
            // Guard against the (unlikely) scenario that the counting logic is flawed.
            // The counter is no longer reliable, so skip the awaiting in finally.
            acquiredCount = maxDegreeOfParallelism;
            throw;
        }
        finally
        {
            // Wait for all pending operations to complete. This could cause a deadlock
            // in case the counter has become out of sync.
            for (int i = acquiredCount; i < maxDegreeOfParallelism; i++)
                await semaphore.WaitAsync();
        }
    }
    
    //
    ///启动执行循环,按顺序获取成批消息,
    ///一个接一个地并行处理。
    /// 
    公共静态异步任务执行LoopAsync(
    Func fetchMessagesAsync,
    Func processMessageAsync,
    int fetchCount,
    int maxDegreeOfParallelism,
    CancellationToken CancellationToken=默认值)
    {
    //省略参数验证
    var信号量=新信号量lim(maxDegreeOfParallelism,maxDegreeOfParallelism);
    //计算我们获得信号量的次数,以便我们知道
    //在我们退出这个方法之前,我们还要获得多少次。
    int acquiredCount=0;
    尝试
    {
    while(true)
    {
    Assert(acquiredCount==0);
    for(int i=0;i=fetchCount)//我们收到的消息比我们要求的要多
    {
    wait semaphore.WaitAsync();
    acquiredCount++;
    }
    ProcessAndRelease(消息[i]);
    已获取帐户--;
    }
    if(messages.Length
    用法示例:

    var cts = new CancellationTokenSource();
    
    Task executionTask = ExecutionLoopAsync<Message>(async count =>
    {
        return await GetBatchFromAwsAsync(count);
    }, async message =>
    {
        await ProcessMessageAsync(message);
    }, fetchCount: 10, maxDegreeOfParallelism: 100, cts.Token);
    
    var cts=new CancellationTokenSource();
    任务executionTask=ExecutionLoopAsync(异步计数=>
    {
    返回wait wait GetBatchFromAwsAsync(计数);
    },异步消息=>
    {
    等待ProcessMessageAsync(消息);
    },fetchCount:10,maxDegreeOfParallelism:100,cts.Token);
    
    感谢@Theodor为理解和回答问题花了大量时间。你的假设是正确的。对于#5,当从aws获取时发生异常时。如果我捕捉到异常并只返回0消息,它会工作吗?@eyese7en是的,如果
    fetchMessagesAsync
    返回了一个空数组,那么循环中将不再需要执行任何操作,并且
    fetchMessagesAsync
    将立即再次调用(没有任何延迟)。您可以考虑通过添加一个额外的<代码> TimeStudioFixMeaseRealyTrime< /Cuff>参数来增强<代码> ActhRunyActhyCys机制,并在一个内部重试循环中包含<代码>等待FETCHMISGASEASYNC/<代码>行,其中包含一个具有指定持续时间的异步延迟。@特奥多尔天真地说,睡眠会像延迟一样工作吗?如果可以的话,我会在几天内完全实现后将其标记为答案。@eyese7en是的,
    Thread.Sleep
    将起作用