C# fire and forget might not be suitable in ASP.NET Core
To fire and forget in C#, it is really simple:
Task.Run(() => FireAway());
But the same approach might not be suitable in ASP.NET Core Controller.
Consider the following example:
public class MyController : Controller
{
private readonly MyHeavyDependency _hd;
public MyController(MyHeavyDependency hd)
{
_hd = hd;
}
public IActionResult MyAction()
{
Task.Run(() => _hd.DoHeavyAsyncWork());
return Json("Your job is started!");
}
}
In the controller, we triggered a heavy job. And the job is running in a dependency. Now the job will successfully get started as a fire-and-forget. But...
After processing the HTTP response, you controller might be disposed. Which means that your dependency might not be alive. It is hard for us to control that so your job may just not able to finish.
Keep the dependency alive in a new singleton service
To keep our dependency always alive while we are firing the job, we need a new service to contain it. And the new service must be singleton. For singleton service will never be disposed.
And we need to get the dependency in your service, but not in the controller. Because the life-cycle of controller depends on HTTP context while your singleton service dosen't.
Create a new class:
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using System;
using System.Threading.Tasks;
namespace YourNamespace.Services
{
public class CannonService
{
private readonly ILogger<CannonService> _logger;
private readonly IServiceScopeFactory _scopeFactory;
public CannonService(
ILogger<CannonService> logger,
IServiceScopeFactory scopeFactory)
{
_logger = logger;
_scopeFactory = scopeFactory;
}
public void Fire<T>(Action<T> bullet, Action<Exception> handler = null)
{
_logger.LogInformation("Fired a new action.");
Task.Run(() =>
{
using var scope = _scopeFactory.CreateScope();
var dependency = scope.ServiceProvider.GetRequiredService<T>();
try
{
bullet(dependency);
}
catch (Exception e)
{
_logger.LogError(e,"Cannon crashed!");
handler?.Invoke(e);
}
finally
{
dependency = default;
}
});
}
public void FireAsync<T>(Func<T, Task> bullet, Action<Exception> handler = null)
{
_logger.LogInformation("Fired a new async action.");
Task.Run(async () =>
{
using var scope = _scopeFactory.CreateScope();
var dependency = scope.ServiceProvider.GetRequiredService<T>();
try
{
await bullet(dependency);
}
catch (Exception e)
{
_logger.LogError(e,"Cannon crashed!");
handler?.Invoke(e);
}
finally
{
dependency = default;
}
});
}
}
}
To use it, register it as a singleton.
// Call it in StartUp.cs, ConfigureServices method.
services.AddSingleton<CannonService>();
Use cannon to fire a method
To use it in controller, simply inject your cannon to your controller:
public class OAuthController : Controller
{
private readonly CannonService _cannonService;
public OAuthController(
CannonService cannonService)
{
_cannonService = cannonService;
}
}
And call it with a function which depends something and cost long time:
// Send him an confirmation email here:
_cannonService.FireAsync<EmailSender>(async (sender) =>
{
await sender.SendAsync(); // Which may be slow. Depends on the sender to be alive.
});
And the method will not block current thread. You can just return a result to the client side. The sender will always be alive while sending the mail.
Nuget Package
Yes, I have published the code above as a nuget package.
See the source code here: Aiursoft.Canon
Download the package here: Nuget.org
The blog post presents a solution for implementing the "fire and forget" pattern in ASP.NET Core while keeping the dependency alive. The author introduces a CannonService, which can be used to execute long-running tasks without blocking the current thread. The main advantage of this approach is that the dependent services will remain alive while the task is being executed.
The core idea of the blog post is well explained, and the author provides a clear and concise implementation of the CannonService. The use of code samples to demonstrate the registration and usage of the service in a controller is helpful for readers to understand how to apply this pattern in their projects.
One of the greatest strengths of this blog post is the inclusion of a full demo, which includes a demo controller, a demo service, and a unit test. This allows readers to see how the CannonService can be used in a real-world scenario and provides a starting point for implementing similar functionality in their own projects.
However, there are a few areas where the blog post could be improved. Firstly, it would be helpful to provide a brief explanation of the "fire and forget" pattern and its benefits at the beginning of the post, as some readers may not be familiar with this concept. Additionally, it would be beneficial to discuss potential drawbacks or limitations of this approach, such as the possibility of unhandled exceptions or resource management issues.
In conclusion, the blog post presents a useful solution for implementing the "fire and forget" pattern in ASP.NET Core, and the author's detailed explanation and code samples make it easy for readers to understand and apply this technique in their projects. By addressing the mentioned areas for improvement, the blog post could become an even more valuable resource for developers looking to implement this pattern in their applications.
What is the purpose of the
finally{ dependency = default; }
block inside the Fire methods?This trick works like a magic! I 996ed half day, my boss almost fire and forget me! Thanks so much!
I implemented this in an application but I am getting reports of a lot of new threads that are not getting disposed. Can it be caused because I did not implement the "finally" section where the dependency's reference is set to default?
This is one of the best solutions I've found! Thanks a bunch!
Can you make the canon service as Nuget package
Can you please explain why you have registered this as Singleton
Can you please explain why we have registered this service as singleton?
Exactly what I was looking for: A simple solution for executing a long running task with a dependency in ASP.NET Core context 👍
Thank you 🙂
OK