When I ported my online multiplayer game, Rodents Online, from NodeJS to .Net core, I needed a way to implement a long running background task in the ASP.Net Core MVC project without blocking other tasks such as controller requests etc. The long running task is expected to do the following:
- Game loop
- Clean up inactive game sessions
My first idea was just to spin up another thread but luckily with .Net Core 3.0, they have introduced a new feature called Background Service.
Assuming you are creating an ASP.Net core project, you can use the following steps:
Step 1: Create your Background Service class
Create a class and inherit the BackgroundService class for the class you created. A barebones Custom background service class should look like the following:
using Microsoft.Extensions.Hosting;
using System;
using System.Threading;
using System.Threading.Tasks;
namespace MinatCoding.TechTips.BackgroundServices
{
public class MyBackgroundService : BackgroundService
{
public BackgroundService()
{
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
}
public override async Task StartAsync(CancellationToken cancellationToken)
{
await ExecuteAsync(cancellationToken);
}
public override Task StopAsync(CancellationToken cancellationToken)
{
return Task.CompletedTask;
}
}
}
When you inherit from Background Service, you will inherit three key methods:
- StartAsync
- Code in this method gets executed when background service starts. This method usually contains code to initialise the resources your background service needs.
- ExecuteAsync
- Code in this method is for your background service's logic.
- StopAsync
- Code in this method gets executed when the background task is stopped. You would usually use this method to clean up resources when the background task is no longer needed.
Step 2: Register your Background Service class
In your start up file, register your background service class in the StartUp.cs class like below:
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using System.Threading.Tasks;
namespace MinatCoding.TechTips.BackgroundServices
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddRazorPages().
services.AddSingleton<IHostedService, MyBackgroundService>();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapRazorPages();
endpoints.MapControllers();
});
}
}
}
If you wish to inject the background service into other parts of your code like the controllers, you can register the background service as follows:
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using System.Threading.Tasks;
namespace MinatCoding.TechTips.BackgroundServices
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddRazorPages().
services.AddSingleton<MyBackgroundService>();
services.AddHostedService(provider => provider.GetService<MyBackgroundService>());
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapRazorPages();
endpoints.MapControllers();
});
}
}
}
How to continuously run your code in the background?
When you run your app, you will notice that the background service is only triggered once. If you wish to execute your code continuously, you will need to wrap your code in the ExecuteAsync() method within a loop like below:
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using System;
using System.Threading;
using System.Threading.Tasks;
namespace MinatCoding.TechTips.BackgroundServices
{
public class MyBackgroundService : BackgroundService
{
public MyBackgroundService()
{
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
await Task.Factory.StartNew(async () =>
{
// loop until task cancellation is triggered
while (!stoppingToken.IsCancellationRequested)
{
//your code here
}
}, stoppingToken);
}
public override async Task StartAsync(CancellationToken cancellationToken)
{
await ExecuteAsync(cancellationToken);
}
public override Task StopAsync(CancellationToken cancellationToken)
{
return Task.CompletedTask;
}
}
}
For my case, I needed to add a delay of 30 milliseconds between each iteration, so I added a Task.Delay() within the loop as shown below:
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using System;
using System.Threading;
using System.Threading.Tasks;
namespace MinatCoding.TechTips.BackgroundServices
{
public class MyBackgroundService : BackgroundService
{
public MyBackgroundService()
{
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
await Task.Factory.StartNew(async () =>
{
// loop until task cancellation is triggered
while (!stoppingToken.IsCancellationRequested)
{
//your code here
await Task.Delay(TimeSpan.FromMilliseconds(30), cancellationToken);
}
}, stoppingToken);
}
public override async Task StartAsync(CancellationToken cancellationToken)
{
await ExecuteAsync(cancellationToken);
}
public override Task StopAsync(CancellationToken cancellationToken)
{
return Task.CompletedTask;
}
}
}