【问题标题】:Fire and Forget multiple methods in C#C# 中的 Fire and Forget 多个方法
【发布时间】:2019-10-30 19:58:02
【问题描述】:

我有一个 Azure 函数,它能够调用多个 API 端点,而无需等待其中任何一个端点的结果。

Azure 函数在计时器触发器上运行,每 10 秒运行一次。

我所有的 API 调用和调用它们的参数都存储在一个 SQL 表中。我想确保在不等待特定调用完成的情况下进行 API 调用。

这只是我将要做的事情的蓝图。

[FunctionName("FunctionScheduler")]
public static async Task RunAsync([TimerTrigger("*/10 * * * * *")]TimerInfo myTimer, ILogger log)
{
    log.LogInformation("FUNCTION SCHEDULER STARTING ..");

    log.LogInformation($"C# Timer trigger function executed at: {DateTime.Now}");

    for(int i=0; i < 20; i++)
    {
        var task = Task.Run(() => ApiRef1(i, log));
        var taskref = await task;
    }

}

目前 ApiRef1() 只是打印出变量 i 的值。我得到了打印数字 0 到 19 的预期输出。我希望并行执行 ApiRef1() 方法,该方法最终将被类似这样的方法替换。

private static void CallApi(string apiName, string apiEndpoint, string methodType, string authenticationType, IDictionary<int, string> parameters, ILogger log)
{
    try
    {
        log.LogInformation($"Call to API {apiName} started.." );

        // Call API
    }
    catch (Exception ex)
    {
        log.LogInformation($"Exception {ex.Message} occurred.." );
    }
}

有没有更好的方法来做到这一点,或者这种方法会奏效吗?

【问题讨论】:

  • 如果您“解雇并忘记”任务,为什么要捕获每个任务的结果(使用await)?还是您不再需要这样做了?
  • 这对于C# Discards来说是一个很好的用例
  • @gunr2171。我不需要这样。我可以使用 await Task.Run(() => ApiRef1(i, log));这会产生相同的结果。但这是否保证这是一种真正的“一劳永逸”方法
  • 在这种情况下,您不能有真火而忘记,因为您需要确保在 Azure Function 终止之前完成任务。我会将为每次迭代创建的每个任务存储在一个列表中,并等待所有任务完成Task.WhenAll(IEnumerable&lt;Task&gt;).Wait();
  • 请记住,“一劳永逸”的意思是“我不在乎何时甚至如果这会成功”。你确定这就是你想要的吗?

标签: c# .net async-await nonblocking fire-and-forget


【解决方案1】:

当您使用 Azure 函数时,您不能着火而忘记,因为您冒着函数在所有任务完成之前终止的风险。

但是,我们不关心任务的结果,所以我们不需要单独等待每个任务。

System.Collections.Generic.List<System.Threading.Tasks.Task> tasks = new System.Collections.Generic.List<System.Threading.Tasks.Task>();
for (int i = 0; i < 20; i++)
{
  tasks.Add(System.Threading.Tasks.Task.Run(() => ApiRef1(i, log));
}
await System.Threading.Tasks.Task.WhenAll(tasks);

这允许所有任务并行触发,但暂停进一步执行直到它们全部完成,确保在进程终止之前完成任务。

【讨论】:

  • 谢谢。 Azure 函数终止完全让我忘记了。我必须将 i 的值分配给一个临时变量。否则,i 的最终值将被传递给所有函数调用。
  • 请参阅here,了解为什么您应该始终使用Task.Run 而不是Task.Factory.StartNew,除非您有理由不这样做。
  • 您可以使用Task.WaitAll(tasks) 代替Task.WhenAll(tasks).Wait()。但理想情况下,您根本不应该阻塞线程,而是使用await Task.WhenAll(tasks)
  • @GabrielLuci 有趣。随时更新答案。
【解决方案2】:

Task.Run() 返回一个Task。当您“开枪即忘”时,您就不会关心该任务。

要接受DetectivePikachu 的建议,请使用丢弃以确保您不关心结果。

public void Run()
{
    ...

    _ = Task.Run(() => ApiRef1(i, log));

    ...
}

包含Task.Run 调用的方法本身不是异步的。除非您有其他使用 await 的方法调用,否则您不再需要异步。

【讨论】:

  • 谢谢你。我做了以下事情: for (int i = 0; i ApiRef1(j, log)); } private static async void ApiRef1(int num, ILogger log) { await Task.Run(() => { try { log.LogInformation("Number is: " + num); } catch (Exception ex) { //请处理例外 } }); }
  • @garty 我同意。我喜欢你处理任务的方式。 (回应下面的评论)
  • 您的函数将在 20 个任务完成之前终止。 (无意中删除)
【解决方案3】:

您可以使用异步/任务功能。这是一个例子

    public static class AzureFunction
    {
        [FunctionName("SomeAzureFunction")]
        public static void Run([TimerTrigger("*/10 * * * * *")]TimerInfo myTimer, ILogger log)
        {
            Function1(log);
            Function2(log);
            Function3(log);
        }

        private static async void Function1(ILogger log)
        {
            await Task.Run(() =>
            {
                Thread.Sleep(6000);
                log.LogInformation("Function 1 now executed");
            });
        }

        private static async void Function2(ILogger log)
        {
            await Task.Run(() =>
            {
                Thread.Sleep(2000);
                log.LogInformation("Function 2 now executed");
            });
        }

        private static async void Function3(ILogger log)
        {
            await Task.Run(() =>
            {
                log.LogInformation("Function 3 now executed");
            });
        }
    }

(Threat.Sleep() 行仅用于向您展示函数如何/如何独立执行)

在输出窗口中,您将看到所有三个函数都已启动,但函数 3 将首先完成(因为我们没有使线程休眠),然后函数 2 将完成(因为我们有 2 秒的延迟)最后函数 1 将完成(延迟 6 秒)。

请注意,此示例中的所有函数都有一个返回类型“Void”。所以这个例子只有在你不关心函数的任何返回值时才有效。

但我认为这接近您的要求

【讨论】:

  • async void 是一个非常强大的“即发即弃”功能构造
  • Azure 函数将在 Function1Function2Function3 完成执行之前终止。由于这三个函数本身都没有被等待,因此后续的等待是多余的,并且完全没有必要考虑到我们不关心结果。
猜你喜欢
  • 2014-05-16
  • 1970-01-01
  • 2020-03-21
  • 2011-02-07
  • 2018-03-01
  • 2013-01-13
  • 2021-10-12
  • 2017-02-19
  • 2019-06-28
相关资源
最近更新 更多