简体   繁体   English

Azure 功能 - 并行任务似乎不会同时运行

[英]Azure functions - Parallel tasks seems not to run simultaneously

I have a question referencing the usage of concurrently running tasks in Azure Functions, on the consumption plan.我有一个关于消费计划的问题,参考 Azure 函数中并发运行的任务的使用情况。

One part of our application allows users to connect their mail accounts, then downloads messages every 15 minutes.我们应用程序的一部分允许用户连接他们的邮件帐户,然后每 15 分钟下载一次邮件。 We have azure function to do so, one for all users.我们有 azure function 这样做,为所有用户提供一个。 The thing is, as users count increases, the function need's more time to execute.问题是,随着用户数量的增加,function 需要更多的时间来执行。

In order to mitigate a timeout case, I've changed our function logic.为了减轻超时情况,我改变了我们的 function 逻辑。 You can find some code below.您可以在下面找到一些代码。 Now it creates a separate task for each user and then waits for all of them to finish.现在它为每个用户创建一个单独的任务,然后等待所有用户完成。 There is also some exception handling implemented, but that's not the topic for today.还实现了一些异常处理,但这不是今天的主题。

The problem is, that when I check some logs, I see executions as the functions weren't executed simultaneously, but rather one after one.问题是,当我检查一些日志时,我看到了执行,因为这些函数不是同时执行的,而是一个接一个地执行。 Now I wonder if I made some mistake in my code, or is it a thing with azure functions that they cannot run in such a scenario (I haven't found anything suggesting it on the Microsoft sites, quite the opposite actually)现在我想知道我的代码是否犯了一些错误,还是 azure 函数在这种情况下无法运行(我在 Microsoft 网站上没有发现任何提示,实际上恰恰相反)

PS - I do know about durable functions, however, for some reason I'd like to resolve this issue without them. PS - 我确实知道持久功能,但是,出于某种原因,我想在没有它们的情况下解决这个问题。

My code:我的代码:

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>();
        }
    }

Output: Output:

日志:

Azure functions in a consumption plan scales out automatically. Azure 功能在消费计划中自动扩展。 Problem is that the load needs to be high enough to trigger the scale out.问题是负载需要足够高才能触发横向扩展。

What is probably happening is that the scaling is not being triggered, therefore everything runs on the same instance, therefore the calls run sequentially.可能发生的情况是缩放没有被触发,因此一切都在同一个实例上运行,因此调用按顺序运行。

There is a discussion on this with some code to test it here: https://docs.microsoft.com/en-us/answers/questions/51368/http-triggered-azure-function-not-scaling-to-extra.html对此进行了讨论,并在此处使用一些代码对其进行测试: https://docs.microsoft.com/en-us/answers/questions/51368/http-triggered-azure-function-not-scaling-to-extra。 html

The compiler will give you a warning for GetMailsForUser :编译器会给你一个GetMailsForUser警告:

CS1998: This async method lacks 'await' operators and will run synchronously. CS1998:此异步方法缺少“等待”运算符,将同步运行。 Consider using the 'await' operator to await non-blocking API calls, or 'await Task.Run(…)' to do CPU-bound work on a background thread.考虑使用“await”运算符来等待非阻塞 API 调用,或“await Task.Run(…)”在后台线程上执行 CPU 密集型工作。

It's telling you it will run synchronously, which is the behaviour you're seeing.它告诉您它将同步运行,这就是您所看到的行为。 In the warning message there's a couple of recommendations:在警告消息中有几个建议:

  1. Use await .使用await This would be the most ideal solution, since it will reduce the resources your Azure Function uses.这将是最理想的解决方案,因为它将减少您的 Azure Function 使用的资源。 However, this means your _mailClientFactory will need to support asynchronous APIs, which may be too much work to take on right now (many SMTP libraries still do not support async ).但是,这意味着您的_mailClientFactory将需要支持异步 API,这可能是目前需要承担的太多工作(许多 SMTP 库仍然不支持async )。
  2. Use thread pool threads.使用线程池线程。 Task.Run is one option, or you could use PLINQ or Parallel . Task.Run是一种选择,或者您可以使用 PLINQ 或Parallel This solution will consume one thread per account, and you'll eventually hit scaling issues there.此解决方案将消耗每个帐户一个线程,您最终会在那里遇到扩展问题。
  1. If you want to identify which Task is running in which Function instance etc. use invocation id ctx.InvocationId.ToString() .如果您想确定哪个任务正在哪个 Function 实例等中运行,请使用调用 id ctx.InvocationId.ToString() May be prefix all your logs with this id.可以使用此 ID 作为所有日志的前缀。

  2. Your code isn't written such that it can be run in parallel by the runtime.您的代码未编写为可以由运行时并行运行。 See this: Executing tasks in parallel看到这个: 并行执行任务

  3. You can also get more info about the trigger using trigger meta-data .您还可以使用触发器元数据获取有关触发器的更多信息。 Depends on trigger.取决于触发器。 This is just to get more insight into what function is handling what message etc.这只是为了更深入地了解 function 正在处理什么消息等。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM