Reputation: 4766
In my project, I have a network call to sending Email, I don't want to wait for the response, because the most likely Email provider sends the Emails successfully. Which of the following methods is better and what is the difference?
Method 1
: await SendAsync
and use 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
: Does not await SendAsync
public IActionResult Index()
{
SendAsync();
return View();
}
private async Task SendAsync()
{
_logger.LogInformation("Before");
await Task.Delay(10000); // Email send
_logger.LogInformation("After");
}
Both methods work with awaiting in Task.Delay(10000);
line
Upvotes: 2
Views: 2440
Reputation: 456467
In my project, I have a network call to sending Email, I don't want to wait for the response, because the most likely Email provider sends the Emails successfully.
Most email providers work by having you send into a queue, and then the actual email is sent later, when they process work out of that queue. So, sending into the queue is fast and very reliable.
So this means that your SendEmailAsync
or whatever API should be quite fast, and shouldn't require returning early. Since SendEmailAsync
actually sends to a queue, the only thing it represents is "please accept my request to send this email".
Which of the following methods is better and what is the difference?
Neither.
Since sending emails is just a queue write, and you don't want your request to be lost, the appropriate approach is not to use fire-and-forget at all:
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");
}
If, for some reason, you're using an email provider that doesn't queue, then you can create your own queue (Azure Storage Queue, Amazon SQS, RabbitMQ, etc) and change SendAsync
to write a message to that queue. Then have a separate background process (Azure Function, Amazon Lambda, etc) read from that queue and send the emails to the email provider.
Upvotes: 2
Reputation: 5805
Both are equivalent. Even though there are many who advise against using this pattern - if you know what you are doing (like logging, taking care to handle all exceptions within the task, taking care that the background task behaves correctly during application shutdown etc.), from my experience it is actually ok to use this pattern, at least in .NET Core. There are cases where it's not possible or practical or feasible (performance-wise) to handle the background task in a persistent manner (with a DB or a queue).
In fact, Microsoft's implementation of BackgroundService
works exactly like that:
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;
}
Here, ExecuteAsync
is an abstract Task (to be overriden by the subclass) that is run without await
. The task is stored in an instance field just so it can be cancelled with a CancellationToken
.
Upvotes: 0