Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

.Net Core Queue Background Tasks

Slender answered my original question about what happens to fire and forget, after the HTTP Response is sent, but Now I'm left with the question how to properly queue background tasks

EDIT

As we all know Async void is generally bad, except for in the case when it comes to event handlers, I would like to execute some background logic without have to have the client wait. My original Idea was to use Fire and Forget

Say I have an event:

public event EventHandler LongRunningTask;

And then someone subscribes a fire and forget task:

LongRunningTask += async(s, e) => { await LongNetworkOperation;};

the web api method is call:

[HttpGet]
public async IActionResult GetTask()
{
    LongRunningTask?.Invoke(this, EventArgs.Empty);
    return Ok();
}

But If I do this my long running task isn't guaranteed to finish, How can I handle running background task without affect the time the time it take to make my request (e.g I don't want to wait for the task to finish first)?

like image 540
johnny 5 Avatar asked Jun 30 '18 14:06

johnny 5


People also ask

What is a background task in ASP NET Core?

Background task that runs on a timer. Hosted service that activates a scoped service. The scoped service can use dependency injection (DI). Queued background tasks that run sequentially. The ASP.NET Core Worker Service template provides a starting point for writing long running service apps.

What is a background task queue in a service?

A background task queue is based on the .NET 4.x QueueBackgroundWorkItem: The BackgroundProcessing method returns a Task, which is awaited in ExecuteAsync. Background tasks in the queue are dequeued and executed in BackgroundProcessing. Work items are awaited before the service stops in StopAsync.

How do I enqueue and dequeue a task queue in Java?

Right-click on the class library project & add an interface with the name "IBackgroundTaskQueue". Right-click & add a new class with the name "BackgroundTaskQueue" and inherit the interface "IBackgroundTaskQueue". Create methods to enqueue & dequeue the requests as shown below.

How do I add a work item to the background queue?

Tap W to add a work item to the background queue. info: Microsoft.Hosting.Lifetime [0] Application started.


2 Answers

Just wanted to add some additional notes to @johnny5 answer. Right now you can use https://devblogs.microsoft.com/dotnet/an-introduction-to-system-threading-channels/ instead of ConcurrentQueue with Semaphore. The code will be something like this:

public class HostedService: BackgroundService
{
        private readonly ILogger _logger;
        private readonly ChannelReader<Stream> _channel;

        public HostedService(
            ILogger logger,
            ChannelReader<Stream> channel)
        {
            _logger = logger;
            _channel = channel;
        }

        protected override async Task ExecuteAsync(CancellationToken cancellationToken)
        {
            await foreach (var item in _channel.ReadAllAsync(cancellationToken))
            {
                try
                {
                    // do your work with data
                }
                catch (Exception e)
                {
                    _logger.Error(e, "An unhandled exception occured");
                }
            }
        }
}

[ApiController]
[Route("api/data/upload")]
public class UploadController : ControllerBase
{
    private readonly ChannelWriter<Stream> _channel;

    public UploadController (
        ChannelWriter<Stream> channel)
    {
        _channel = channel;
    }

    public async Task<IActionResult> Upload([FromForm] FileInfo fileInfo)
    {
        var ms = new MemoryStream();
        await fileInfo.FormFile.CopyToAsync(ms);
        await _channel.WriteAsync(ms);
        return Ok();
    }
}
like image 68
flerka Avatar answered Sep 20 '22 07:09

flerka


.NET Core 2.1 has an IHostedService, which will safely run tasks in the background. I've found an example in the documentation for QueuedHostedService which I've modified to use the BackgroundService.

public class QueuedHostedService : BackgroundService
{
   
    private Task _backgroundTask;
    private readonly ILogger _logger;

    public QueuedHostedService(IBackgroundTaskQueue taskQueue, ILoggerFactory loggerFactory)
    {
        TaskQueue = taskQueue;
        _logger = loggerFactory.CreateLogger<QueuedHostedService>();
    }

    public IBackgroundTaskQueue TaskQueue { get; }

    protected async override Task ExecuteAsync(CancellationToken stoppingToken)
    {
        while (false == stoppingToken.IsCancellationRequested)
        {
            var workItem = await TaskQueue.DequeueAsync(stoppingToken);
            try
            {
                await workItem(stoppingToken);
            }
            catch (Exception ex)
            {
                this._logger.LogError(ex, $"Error occurred executing {nameof(workItem)}.");
            }
        }
    }
}

public interface IBackgroundTaskQueue
{
    void QueueBackgroundWorkItem(Func<CancellationToken, Task> workItem);

    Task<Func<CancellationToken, Task>> DequeueAsync(
        CancellationToken cancellationToken);
}

public class BackgroundTaskQueue : IBackgroundTaskQueue
{
    private ConcurrentQueue<Func<CancellationToken, Task>> _workItems =
        new ConcurrentQueue<Func<CancellationToken, Task>>();

    private SemaphoreSlim _signal = new SemaphoreSlim(0);

    public void QueueBackgroundWorkItem(Func<CancellationToken, Task> workItem)
    {
        if (workItem == null)
        {
            throw new ArgumentNullException(nameof(workItem));
        }

        _workItems.Enqueue(workItem);
        _signal.Release();
    }

    public async Task<Func<CancellationToken, Task>> DequeueAsync( CancellationToken cancellationToken)
    {
        await _signal.WaitAsync(cancellationToken);
        _workItems.TryDequeue(out var workItem);

        return workItem;
    }
}

Now we can safely queue up tasks in the background without affecting the time it takes to respond to a request.

like image 42
johnny 5 Avatar answered Sep 17 '22 07:09

johnny 5