【问题标题】:Correct way to implement fire and forget async method call实现fire and forget异步方法调用的正确方法
【发布时间】:2021-02-13 23:28:10
【问题描述】:

在我的项目中,我有一个发送电子邮件的网络调用,我不想等待响应,因为最有可能电子邮件提供商成功发送电子邮件。以下哪种方法更好,有什么区别?

Method 1:等待SendAsync并使用Task.Run

public async Task<IActionResult> Index()
{
    await SendAsync();
    return View();
}

private Task SendAsync()
{
    _ = Task.Run(async () =>
    {
        _logger.LogInformation("Before");
        await Task.Delay(10000); // Email send
        _logger.LogInformation("After");
    });

    return Task.CompletedTask;
}

Method 2:不等待SendAsync

public IActionResult Index()
{
    SendAsync();
    return View();
}

private async Task SendAsync()
{
    _logger.LogInformation("Before");
    await Task.Delay(10000);  // Email send
    _logger.LogInformation("After");
}

这两种方法都可以在 Task.Delay(10000); 行中等待

【问题讨论】:

  • 无。对此类关键任务使用一些事务性手段,例如使用发送请求填充一些 SQL/Redis/RabbitMQ 队列并在单独的工作人员中处理它们,而不会忘记。即发即弃对于非关键或可重复任务很有用:日志记录、UI 更新、遥测、延长寿命策略、可重试任务等

标签: c# asp.net-core asynchronous async-await task


【解决方案1】:

在我的项目中,我有一个发送电子邮件的网络调用,我不想等待响应,因为最有可能电子邮件提供商成功发送电子邮件。

大多数电子邮件提供商的工作方式是让您发送到一个队列,然后在他们处理该队列之外的工作时,稍后发送实际的电子邮件。因此,发送到队列是快速且非常可靠的。

这意味着您的SendEmailAsync 或任何API 应该非常快,并且不需要提前返回。由于SendEmailAsync 实际上发送到一个队列,它唯一代表的是“请接受我的请求发送这封电子邮件”。

以下哪种方法更好,有什么区别?

都没有。

由于发送电子邮件只是队列写入,并且您不希望您的请求丢失,因此适当的方法是根本不使用即发即弃:

public async Task<IActionResult> Index()
{
    await SendAsync();
    return View();
}

private async Task SendAsync()
{
    _logger.LogInformation("Before");
    await Task.Delay(10000);  // Email send
    _logger.LogInformation("After");
}

如果出于某种原因,您使用的电子邮件提供商没有队列,那么您可以创建自己的队列(Azure 存储队列、Amazon SQS、RabbitMQ 等)并更改SendAsync 将消息写入该队列。然后让一个单独的后台进程(Azure Function、Amazon Lambda 等)从该队列中读取并将电子邮件发送给电子邮件提供商。

【讨论】:

    【解决方案2】:

    两者是等价的。即使有很多人建议不要使用这种模式——如果你知道你在做什么(比如日志记录、注意处理任务中的所有异常、注意后台任务在应用程序关闭期间的行为正确等),来自我的体验一下使用这个模式其实是可以的,至少在.NET Core中是这样。 在某些情况下,以持久方式(使用数据库或队列)处理后台任务是不可能的、不切实际的或不可行的(从性能方面)。

    事实上,Microsoft's implementationBackgroundService 的工作原理完全一样:

            public virtual Task StartAsync(CancellationToken cancellationToken)
            {
                // Store the task we're executing
                _executingTask = ExecuteAsync(_stoppingCts.Token);
    
                // If the task is completed then return it, this will bubble cancellation and failure to the caller
                if (_executingTask.IsCompleted)
                {
                    return _executingTask;
                }
    
                // Otherwise it's running
                return Task.CompletedTask;
            }
    

    这里,ExecuteAsync 是一个抽象任务(将被子类覆盖),它在没有await 的情况下运行。该任务存储在一个实例字段中,因此可以使用CancellationToken 取消它。

    【讨论】:

      猜你喜欢
      • 2011-02-07
      • 2014-05-16
      • 1970-01-01
      • 2023-01-14
      • 1970-01-01
      • 2019-06-28
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多