【问题标题】:Azure functions - Parallel tasks seems not to run simultaneouslyAzure 函数 - 并行任务似乎不会同时运行
【发布时间】:2021-07-31 05:37:52
【问题描述】:

我有一个关于 Azure Functions 中并发运行任务的使用情况的问题,关于消费计划。

我们应用程序的一部分允许用户连接他们的邮件帐户,然后每 15 分钟下载一次邮件。我们有 azure 功能可以做到这一点,所有用户都可以使用。问题是,随着用户数量的增加,函数需要更多的时间来执行。

为了缓解超时情况,我更改了函数逻辑。您可以在下面找到一些代码。现在它为每个用户创建一个单独的任务,然后等待所有用户完成。还实现了一些异常处理,但这不是今天的主题。

问题是,当我检查一些日志时,我看到执行是因为函数不是同时执行的,而是一个接一个地执行。现在我想知道我的代码是否有错误,或者是天蓝色函数无法在这种情况下运行(我在 Microsoft 网站上没有找到任何建议,实际上恰恰相反)

PS - 我确实知道持久功能,但是出于某种原因,我想在没有它们的情况下解决这个问题。

我的代码:

List<Task<List<MailMessage>>> tasks = new List<Task<List<MailMessage>>>();
            foreach (var account in accounts)
            {
                using (var cancellationTokenSource = new CancellationTokenSource(TimeSpan.FromMinutes(6)))
                {
                    try
                    {
                        tasks.Add(GetMailsForUser(account, cancellationTokenSource.Token, log));
                    }
                    catch (TaskCanceledException)
                    {
                        log.LogInformation("Task was cancelled");
                   }
                }
            }
            
            try
            {
                await Task.WhenAll(tasks.ToArray());
            }
            catch(AggregateException aex)
            {
                aex.Handle(ex =>
                {
                    TaskCanceledException tcex = ex as TaskCanceledException;
                    if (tcex != null)
                    {
                        log.LogInformation("Handling cancellation of task {0}", tcex.Task.Id);
                        return true;
                    }
                    return false;
                });
            }

            log.LogInformation($"Zakończono pobieranie wiadomości.");
private async Task<List<MailMessage>> GetMailsForUser(MailAccount account, CancellationToken cancellationToken, ILogger log)
    {
        log.LogInformation($"[{account.UserID}] Rozpoczęto pobieranie danych dla konta {account.EmailAddress}");

        IEnumerable<MailMessage> mails;

        try
        {
            using (var client = _mailClientFactory.GetIncomingMailClient(account))
            {
                mails = client.GetNewest(false);
            }
            log.LogInformation($"[{account.UserID}] Pobrano {mails.Count()} wiadomości dla konta {account.EmailAddress}.");
            return mails.ToList();
        }
        catch (Exception ex)
        {
            log.LogWarning($"[{account.UserID}] Nie udało się pobrać wiadomości dla konta {account.EmailAddress}");
            log.LogError($"[{account.UserID}] {ex.Message} {ex.StackTrace}");
            return new List<MailMessage>();
        }
    }

输出:

【问题讨论】:

  • 我猜 _mailClientFactory.GetIncomingMailClient 正在返回同一个邮件客户端,它要么有一个锁,要么你的邮件服务器每个连接只允许 1 个请求(啊,只有一个连接)
  • 更有趣的是,我还有3个用这样的逻辑重做的函数,似乎都面临这个问题。
  • 所以问题可能不在于您发布的代码:-(
  • 实际上,虽然 GetMailsForUser 有async 修饰符,但我看不到任何async 函数或任何awaits。这只是一个 C+P 错误,还是正确的?
  • 但是你的代码都不是异步的!仅返回一个任务并不会使其异步。

标签: c# azure asp.net-core azure-functions


【解决方案1】:

消费计划中的 Azure 函数会自动横向扩展。问题是负载需要足够高才能触发横向扩展。

可能发生的情况是缩放没有被触发,因此一切都在同一个实例上运行,因此调用按顺序运行。

这里有一些代码来测试它的讨论:https://docs.microsoft.com/en-us/answers/questions/51368/http-triggered-azure-function-not-scaling-to-extra.html

【讨论】:

    【解决方案2】:

    编译器会给你一个GetMailsForUser的警告:

    CS1998:此异步方法缺少“等待”运算符,将同步运行。考虑使用 'await' 运算符来等待非阻塞 API 调用,或使用 'await Task.Run(...)' 在后台线程上执行 CPU 密集型工作。

    它告诉您它将同步运行,这就是您所看到的行为。在警告消息中有几个建议:

    1. 使用await。这将是最理想的解决方案,因为它会减少 Azure Function 使用的资源。但是,这意味着您的 _mailClientFactory 将需要支持异步 API,这可能是目前无法承担的工作量太大(许多 SMTP 库仍然不支持 async)。
    2. 使用线程池线程。 Task.Run 是一种选择,或者您可以使用 PLINQ 或 Parallel。此解决方案将占用每个帐户一个线程,您最终会遇到扩展问题。

    【讨论】:

      【解决方案3】:
      1. 如果您想确定哪个任务在哪个函数实例中运行等,请使用invocation idctx.InvocationId.ToString()。可以在你的所有日志前面加上这个 ID。

      2. 您的代码未编写为可以由运行时并行运行。看到这个:Executing tasks in parallel

      3. 您还可以使用trigger meta-data 获取有关触发器的更多信息。取决于触发器。这只是为了更深入地了解什么函数正在处理什么消息等。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2021-10-28
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多