Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Run a background task from a controller action in ASP.NET Core

I am developing a web application with a REST API using C# with ASP.NET Core 2.0.

What I want to achieve is when the client send a request to an endpoint I will run a background task separated from the client request context which will be ended if the task started successfully.

I know there is HostedService but the problem is that the HostedService starts when the server starts, and as far as I know there is no way to start the HostedService manually from a controller.

Here is a simple code that demonstrates the question.

[Authorize(AuthenticationSchemes = "UsersScheme")] public class UsersController : Controller {     [HttpPost]     public async Task<JsonResult> StartJob([FromForm] string UserId, [FromServices] IBackgroundJobService backgroundService)     {         // check user account         (bool isStarted, string data) result = backgroundService.Start();          return JsonResult(result);     } } 
like image 920
Waxren Avatar asked Apr 13 '18 09:04

Waxren


People also ask

What is background task in .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.

When should I use IHostedService?

The IHostedService interface provides a convenient way to start background tasks in an ASP.NET Core web application (in . NET Core 2.0 and later versions) or in any process/host (starting in . NET Core 2.1 with IHost ).

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.

How to make Controllers handle background tasks?

In order to make them to handle some sort of background task, you need to implement some sort of "global" queue system in your app for the controllers to store the data/events.

How to create an ASP NET Core web application in Visual Studio?

Once Visual Studio opens up, I will select the menu File -> New -> Project. This will open the Create a new Project project popup window. Secondly, in the Create a new Project popup window, I will select ASP.NET Core Web Application from the project template and click on the Next button.

How to prevent a controller action from killing a task?

Create a method that returns a Task, pass in a IServiceProvider to that method and create a new Scope in there to make sure ASP.NET would not kill the task when the controller Action completes. Something like


2 Answers

You still can use IHostedService as base for background tasks in combination with BlockingCollection.

Create wrapper for BlockingCollection so you can inject it as singleton.

public class TasksToRun {     private readonly BlockingCollection<TaskSettings> _tasks;      public TasksToRun() => _tasks = new BlockingCollection<TaskSettings>();      public void Enqueue(TaskSettings settings) => _tasks.Add(settings);      public TaskSettings Dequeue(CancellationToken token) => _tasks.Take(token); } 

Then in implementation of IHostedService "listen" for tasks and when tasks "arrive" execute it.
BlockingCollection will stop execution if collection is empty - so your while loop will not consume processor time.
.Take method accept cancellationToken as argument. With token you can cancel "waiting" for next task when application stops.

public class BackgroundService : IHostedService {     private readonly TasksToRun _tasks;      private CancellationTokenSource _tokenSource;      private Task _currentTask;      public BackgroundService(TasksToRun tasks) => _tasks = tasks;      public async Task StartAsync(CancellationToken cancellationToken)     {         _tokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);         while (cancellationToken.IsCancellationRequested == false)         {             try             {                 var taskToRun = _tasks.Dequeue(_tokenSource.Token);                  // We need to save executable task,                  // so we can gratefully wait for it's completion in Stop method                 _currentTask = ExecuteTask(taskToRun);                                await _currentTask;             }             catch (OperationCanceledException)             {                 // execution cancelled             }         }     }      public async Task StopAsync(CancellationToken cancellationToken)     {         _tokenSource.Cancel(); // cancel "waiting" for task in blocking collection          if (_currentTask == null) return;          // wait when _currentTask is complete         await Task.WhenAny(_currentTask, Task.Delay(-1, cancellationToken));     } } 

And in the controller you simply add task you want to run to our collection

public class JobController : Controller {     private readonly TasksToRun _tasks;      public JobController(TasksToRun tasks) => _tasks = tasks;      public IActionResult PostJob()     {         var settings = CreateTaskSettings();          _tasks.Enqueue(settings);          return Ok();     } } 

Wrapper for blocking collection should be registered for dependency injection as singleton

services.AddSingleton<TasksToRun, TasksToRun>(); 

Register background service

services.AddHostedService<BackgroundService>(); 
like image 175
Fabio Avatar answered Sep 25 '22 08:09

Fabio


Microsoft has documented the same at https://docs.microsoft.com/en-us/aspnet/core/fundamentals/host/hosted-services?view=aspnetcore-2.1

It accomplishes using BackgroundTaskQueue, which gets work assigned from Controller and the work is performed by QueueHostedService which derives from BackgroundService.

like image 28
skjagini Avatar answered Sep 21 '22 08:09

skjagini