How To Call A Background Task With Hosted Service From .NET Core Web API

Introduction

 
Usually, when we want to do long-running tasks, it's not a good practice to block the user interface for a long time and there is a high chance of getting a timeout exception from the API. The best example is when you want to process excel with huge data, you can upload excel to storage and process it in the background. 
 
In this article, I'm going to explain how to create a background service and invoke that service from the .NET Core web API. Let's go step by step.
 
Step 1 - Create a .NET Core Web API Project
 
In Visual Studio, click on File, go to New, and select the appropriate project template.
 
 
Click on "Next" and enter the project name and create.
 
 
It creates a project and structure that looks like this:
 
 
Step 2 - Create a class library project.
 
Right-click on the solution & add a new class library for hosted service. The project structure looks like shown below:
 
 
Next, install the Hosted service NuGet package by executing the below command in the NuGet package manager console in the class library project:
  1. Install-Package Microsoft.Extensions.Hosting -Version 3.1.2  
After executing the command on the console:
 
 
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.
 
Concurrent Queue is the thread-safe class, used to read & write multiple requests into the queue. It provides the FIFO data structure. If we get multiple requests at the same time, it will put them in the queue & process them one by one.
 
Here SemaphoreSlim is used to set the number of threads run concurrently. For example, if set to 2, if we have 5 requests in the queue, it will pick up 2 in parallel.
  1. public class BackgroundTaskQueue : IBackgroundTaskQueue  
  2.    {  
  3.        private ConcurrentQueue<Func<CancellationToken, Task>> _workItems =  
  4.       new ConcurrentQueue<Func<CancellationToken, Task>>();  
  5.        private SemaphoreSlim _signal = new SemaphoreSlim(0);  
  6.   
  7.        public void QueueBackgroundWorkItem(  
  8.            Func<CancellationToken, Task> workItem)  
  9.        {  
  10.            if (workItem == null)  
  11.            {  
  12.                throw new ArgumentNullException(nameof(workItem));  
  13.            }  
  14.   
  15.            _workItems.Enqueue(workItem);  
  16.            _signal.Release();  
  17.        }  
  18.   
  19.        public async Task<Func<CancellationToken, Task>> DequeueAsync(  
  20.            CancellationToken cancellationToken)  
  21.        {  
  22.            await _signal.WaitAsync(cancellationToken);  
  23.            _workItems.TryDequeue(out var workItem);  
  24.   
  25.            return workItem;  
  26.        }  
  27.    }  
Add the method declarations in the interface, this is how the interface looks:
  1. public interface IBackgroundTaskQueue  
  2.     {  
  3.         void QueueBackgroundWorkItem(Func<CancellationToken, Task> workItem);  
  4.   
  5.         Task<Func<CancellationToken, Task>> DequeueAsync(  
  6.             CancellationToken cancellationToken);  
  7.     }  
Right-click & add a new class with the name "QueuedHostedService" and inherit "BackgroundService" from Microsoft.Extensions.Hosting. Copy-paste the below code into that class.
 
For more details, refer to this link.
  1. public class QueuedHostedService : BackgroundService  
  2.     {  
  3.         private readonly ILogger _logger;  
  4.   
  5.         public QueuedHostedService(IBackgroundTaskQueue taskQueue,  
  6.             ILoggerFactory loggerFactory)  
  7.         {  
  8.             TaskQueue = taskQueue;  
  9.             _logger = loggerFactory.CreateLogger<QueuedHostedService>();  
  10.         }  
  11.   
  12.         public IBackgroundTaskQueue TaskQueue { get; }  
  13.   
  14.         protected async override Task ExecuteAsync(  
  15.             CancellationToken cancellationToken)  
  16.         {  
  17.             _logger.LogInformation("Queued Hosted Service is starting.");  
  18.   
  19.             while (!cancellationToken.IsCancellationRequested)  
  20.             {  
  21.                 var workItem = await TaskQueue.DequeueAsync(cancellationToken);  
  22.   
  23.                 try  
  24.                 {  
  25.                     await workItem(cancellationToken);  
  26.                 }  
  27.                 catch (Exception ex)  
  28.                 {  
  29.                     _logger.LogError(ex,  
  30.                        "Error occurred executing {WorkItem}.", nameof(workItem));  
  31.                 }  
  32.             }  
  33.   
  34.             _logger.LogInformation("Queued Hosted Service is stopping.");  
  35.         }  
  36.     }  
So we are done with the class library. Next, we will see how to invoke & use that in our web API project.
 
Step 3 - Invoke the background service in .NET Core Web API.
 
Right-click on dependencies in the Web API project & add the class library reference to it, as shown in the below screenshot.
 
 
Go to Startup.cs file and inject the below 2 dependencies: 
  1. services.AddHostedService<QueuedHostedService>();  
  2. services.AddSingleton<IBackgroundTaskQueue, BackgroundTaskQueue>();  
Next, go to the controller class and inject the "IBackgroundTaskQueue" and invoke the "QueueBackgroundWorkItem" method as shown below.
 
Also, we need to use "IServiceScopeFactory" from Microsoft.Extensions.DependencyInjection because once the API returns its response, all the services including DB context will get disposed of, we need to create again using IServiceScopeFactory. 
  1. public class WeatherForecastController : ControllerBase  
  2.     {  
  3.          
  4.         public IBackgroundTaskQueue _queue { get; }  
  5.         private readonly IServiceScopeFactory _serviceScopeFactory;  
  6.         public WeatherForecastController(IBackgroundTaskQueue queue, IServiceScopeFactory serviceScopeFactory)  
  7.         {  
  8.             _queue = queue;  
  9.             _serviceScopeFactory = serviceScopeFactory;  
  10.         }  
  11.   
  12.         [HttpGet]  
  13.         public IActionResult Get()  
  14.         {  
  15.             _queue.QueueBackgroundWorkItem(async token =>  
  16.             {  
  17.                 using (var scope = _serviceScopeFactory.CreateScope())  
  18.                 {  
  19.                     var scopedServices = scope.ServiceProvider;  
  20.                     int j = 1000;  
  21.                     for (int i = 0; i < j; i++)  
  22.                     {  
  23.                         Console.WriteLine(i);  
  24.                     }  
  25.                     await Task.Delay(TimeSpan.FromSeconds(5), token);  
  26.   
  27.                 }  
  28.             });  
  29.             return Ok("In progress..");  
  30.         }  
  31.     }  
Step 4 - Run & Test
 
Once we hit this API, we will get a response as "In progress.." and background tasks invokes & keep running into the for loop which we created. 
 
The API is returned with a message & the background service started running.
 
 

Summary

 
In this article, we discussed how to create a hosted service in .NET Core & how to invoke that background service in the .NET Core Web API. I'm attaching the sample code with this article. I hope this article is useful. 


Similar Articles