Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Running background task on demand in asp.net core 3.x

I'm trying to start a background task on demand, whenever I receive a certain request from my api end point. All the task does is sending an email, delayed by 30 seconds. So I though BackgroundService would fit. But the problem is it looks like the BackgroundService is mostly for recurring tasks, and not to be executed on demand per this answer.

So what other alternatives I have, im hoping not to have to rely on 3rd parties libraries like Hangfire? I'm using asp.net core 3.1.

This is my background service.

using System;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;

namespace ProjectX.Services {
    public  class EmailOfflineService : BackgroundService {

        private readonly ILogger<EmailOfflineService> log;
        private readonly EmailService emailService;
        public EmailOfflineService(
            ILogger<EmailOfflineService> log, 
            EmailService emailService
        ) {
            this.emailService = emailService;
            this.log = log;
        }

        protected async override Task ExecuteAsync(CancellationToken stoppingToken)
        {

            log.LogDebug("Email Offline Service Starting...");
            stoppingToken.Register(() => log.LogDebug("Email Offline Service is stopping."));

            while(!stoppingToken.IsCancellationRequested)
            {
                // wait for 30 seconds before sending
                await Task.Delay(1000 * 30, stoppingToken);

                await emailService.EmailOffline();
                
                // End the background service
                break;
            }
            log.LogDebug("Email Offline Service is stoped.");
        }
    }
}
like image 926
Yehia A.Salam Avatar asked Jul 13 '20 18:07

Yehia A.Salam


People also ask

What is background service in ASP.NET Core?

In ASP.NET Core, background tasks can be implemented as hosted services. A hosted service is a class with background task logic that implements the IHostedService interface. This article provides three hosted service examples: Background task that runs on a timer. Hosted service that activates a scoped service.

What is IHostedService in .NET core?

Since . NET Core 2.0, the framework provides a new interface named IHostedService helping you to easily implement hosted services. The basic idea is that you can register multiple background tasks (hosted services) that run in the background while your web host or host is running, as shown in the image 6-26.


3 Answers

You could try to combine an async queue with BackgroundService.

public class BackgroundEmailService : BackgroundService
{
    private readonly IBackgroundTaskQueue _queue;

    public BackgroundEmailService(IBackgroundTaskQueue queue)
    {
        _queue = queue;
    }

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        while (!stoppingToken.IsCancellationRequested)
        {
            var job = await _queue.DequeueAsync(stoppingToken);
            
            _ = ExecuteJobAsync(job, stoppingToken);
        }
    }

    private async Task ExecuteJobAsync(JobInfo job, CancellationToken stoppingToken)
    {
        try
        {
            await Task.Delay(TimeSpan.FromSeconds(30), stoppingToken);
            // todo send email
        }
        catch (Exception ex)
        {
            // todo log exception
        }
    }
}

public interface IBackgroundTaskQueue
{
    void EnqueueJob(JobInfo job);

    Task<JobInfo> DequeueAsync(CancellationToken cancellationToken);
}

This way you may inject IBackgroundTaskQueue inside your controller and enqueue jobs into it while JobInfo will contain some basic information for executing the job in background, e.g.:

public class JobInfo
{
    public string EmailAddress { get; set; }
    public string Body { get; set; }
}

An example background queue (inspired by the ASP.NET Core documentation):

public class BackgroundTaskQueue : IBackgroundTaskQueue
{
    private ConcurrentQueue<JobInfo> _jobs = new ConcurrentQueue<JobInfo>();
    private SemaphoreSlim _signal = new SemaphoreSlim(0);

    public void EnqueueJob(JobInfo job)
    {
        if (job == null)
        {
            throw new ArgumentNullException(nameof(job));
        }

        _jobs.Enqueue(job);
        _signal.Release();
    }

    public async Task<JobInfo> DequeueAsync(CancellationToken cancellationToken)
    {
        await _signal.WaitAsync(cancellationToken);
        _jobs.TryDequeue(out var job);

        return job;
    }
}
like image 196
Federico Dipuma Avatar answered Oct 19 '22 12:10

Federico Dipuma


I think the simplest approach is to make a fire-and-forget call in the code of handling the request to send a email, like this -

//all done, time to send email
Task.Run(async () => 
{
    await emailService.EmailOffline(emailInfo).ConfigureAwait(false); //assume all necessary info to send email is saved in emailInfo
});

This will fire up a thread to send email. The code will return immediately to the caller. In your EmailOffline method, you can include time-delay logic as needed. Make sure to include error logging logic in it also, otherwise exceptions from EmailOffline may be silently swallowed.

P.S. - Answer to Coastpear and FlyingV -

No need to concern the end of calling context. The job will be done on a separate thread, which is totally independent of the calling context.

I have used similar mechanism in production for a couple of years, zero problem so far.

If your site is not supper busy, and the work is not critical, this is the easiest solution. Just make sure you catch and log error inside your worker (EmailOffline, in this example).

If you need more reliable solution, I'd suggest using a mature queue product like AWS SQS, do not bother to create one by yourself. It is not an easy job to create a really good queue system.

like image 27
ch_g Avatar answered Oct 19 '22 13:10

ch_g


Use Hangfire, it's Background Methods functionality is great, and provides you with a nice dashboard for free: https://docs.hangfire.io/en/latest/background-methods/index.html

like image 28
Luke Storry Avatar answered Oct 19 '22 12:10

Luke Storry