How to run a background service on demand - not on application startup or on a timer
Asked Answered
P

5

7

In a .Net 5 Web API, I would like to run a background task that sends out bulk emails and SMSes. I know I can create a service that inherits from BackgroundService, and then add it to the DI Container in the Startup.ConfigureServices method like this:

services.AddHostedService<EmailAndSmsService>();

But that runs the service immediately - i.e. on application startup. I would like to run the service when the API receives a request from the front-end. i.e. in a controller's action method.

I've been looking at "Background tasks with hosted services" on Microsoft's documentation, and if I'm not mistaken, this is what i need to do (Look at the section titled "Consuming a scoped service in a background task"):

https://learn.microsoft.com/en-us/aspnet/core/fundamentals/host/hosted-services?view=aspnetcore-5.0&tabs=visual-studio

Is this correct? Do I basically need to create two services, one that does the actual work, and one that calls the service that does the actual work? Am I on the right path?

Thanks

Paquin answered 28/1, 2021 at 14:7 Comment(2)
Checkout hangfire.ioPeeper
There is a concept of a worker queue from your documentation, letting the service start and wait for work to do seems reasonable to me.Humming
C
8

You need to look at a "queued background service" where you can submit "jobs" to it and it will perform those jobs in a background queue.

The work flow goes like this:

  1. Caller sends a request to the service with some parameters
  2. Service generates a "job" object and returns an ID immediately via 202 (accepted) response
  3. Service places this job in to a queue that is being maintained by a BackgroundService
  4. Caller can query the job status and get information about how much has been done and how much is left to go using this job ID
  5. Service finishes the job, puts the job in to a "completed" state and goes back to waiting on the queue to produce more jobs

Here is a very long-winded explanation on how it works: https://mcmap.net/q/692331/-appropriate-solution-for-long-running-computations-in-azure-app-service-and-net-core-3-1

Here is an example I made a while back: https://github.com/sonicmouse/ComputationService

Crosscurrent answered 28/1, 2021 at 20:21 Comment(1)
Thanks Andy for this solution. It's awesome.Heathenize
A
2

In a .Net 5 Web API, I would like to run a background task that sends out bulk emails and SMSes.

I would recommend against an in-memory background task for this. The problem with an in-memory queue and in-memory background task is that the work is lost when (not if) the ASP.NET app is restarted.

Instead, I recommend using a durable queue with a separate background service. The idea is similar to Andy's solution, but durable queues don't lose emails/SMSes when the web app is restarted. The general approach is the same: the HTTP request handler writes to the queue and then returns, and the background service reads from the queue and does the actual email/sms.

It is often possible to keep the background service in-process (as long as you use a durable queue, not an in-memory queue), but I usually recommend moving it to a separate process (Azure Function/WebJob / AWS Lambda, Win32 service, etc). This allows separate scale-out of web servers vs backend servers.

Annuitant answered 6/4, 2021 at 12:41 Comment(0)
O
1

ASP.NET and its .NET Core counterpart are made specifically to only spin up when a request comes in. This makes it very suitable for what you're trying to do.

All we need is a Service that contains our code for sending e-mails, like this.

public interface IEmailService
{
    void SendMessages(EmailPoco email);
}

public class EmailService
{
    public void SendMessages(EmailPoco email)
    {
        // Send e-mail messages
    }
}

Then we can inject it into the ServiceCollection in our Startup

public void ConfigureServices(IServiceCollection services)
{
    services.AddSingleton<ITokenStoreService, TokenStoreService>();
}

And finally we add a controller with a single endpoint which we can use to trigger this process.

[Route("api/[controller]")]
[ApiController]
public class EmailController : MiddlewareControllerBase
{
    public EmailController(IEmailService emailService)
    {
        this.emailService = emailService;
    }

    [HttpPost]
    public IActionResult Post([FromBody] EmailPoco email)
    {
        return this.emailService.SendMessage(email);
    }
}

If you expect the process to take a long time, you could resort to running it asynchronously or doing the work in a separate Thread and returning the response before it is finished.

If the process consumes a large number of resources, and you don't want it to interfere with the other functionality of your existing API you may consider simply splitting off this component in a separate API Project, and sending a request from your existing API using HttpClient.

Attempting to run a background process, something akin to a Windows service that only sporadically does work, seems further from what you describe that you want, but is another option.

On the whole, the ASP request pipeline is a good fit for initiating action only when a request comes in, and being dormant otherwise.

Oppen answered 28/1, 2021 at 14:27 Comment(0)
L
1

The .NET Core API framework is set up for short-lived tasks. Anything more than 60-90 seconds run-time should probably be done in a separate service.

My recommendation would be to follow the path you suggested: Create a background service that is reading from a queue waiting for a task, then have your API service add your long-running tasks to the background service's queue.

More info in this (possibly duplicate) question.

Leodora answered 28/1, 2021 at 14:59 Comment(0)
C
1
Creep answered 28/1, 2021 at 19:59 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.