Movatterモバイル変換


[0]ホーム

URL:


Skip to content
DEV Community
Log in Create account

DEV Community

Cover image for Using Background Services in ASP.NET Core
Shawn Wildermuth
Shawn Wildermuth

Posted on • Originally published atwildermuth.com on

Using Background Services in ASP.NET Core

I have a small link shortener that I run for myself. I built it mostly so I could update short links when an URL changes (which bitly didn’t let me do). It’s been running for a few months and I’m really happy with it (GitHub Project). One of the things that I missed was tracking the usage of short links to see how they were being used.

I considered a number of ways to handle this, but I had one hard requirement, I didn’t want to slow down the redirection for some I/O process. After digging into it, I ended up with a solution:BackgroundService inASP.NET Core. TheBackgroundService class allows you to have code running in the background. In my case, I wanted to be able to queue up an item to store the tracking information that didn’t block the code that did the redirection.

So, first I needed a way to fill a queue with work. I could have just used the ThreadPool, but wanted something that was going to be a little safer (so I could control the lifetime). But first I needed a queue.

My first attempt was to simply create a shared Queue object but it wasn’t thread-safe. Digging in, I found theChannel class. This is a class that is specifically about sending messages in a thread-safe way. Before I could use the BackgroundService, I needed to implement a wrapper around theChannel class:

public class AccumulatorQueue : IAccumulatorQueue{  private readonly Channel<Redirect> _queue;  private readonly ILogger<AccumulatorQueue> _logger;  public AccumulatorQueue(ILogger<AccumulatorQueue> logger)  {    var opts = new BoundedChannelOptions(100) { FullMode = BoundedChannelFullMode.Wait };    _queue = Channel.CreateBounded<Redirect>(opts);    _logger = logger;  }  // ...}
Enter fullscreen modeExit fullscreen mode

The idea here was to have a channel for myRedirect model class so that I could queue up messages that could be processed by theBackgroundService service. I added methods to just add/remove from the queue (this way I could Mock the interface if I needed):

public class AccumulatorQueue : IAccumulatorQueue{  // ...  public async ValueTask PushAsync([NotNull] Redirect redirect)  {    await _queue.Writer.WriteAsync(redirect);    _logger.LogInformation("Added Redirect to Queue");  }  public async ValueTask<Redirect> PullAsync(CancellationToken cancellationToken)  {    var result = await _queue.Reader.ReadAsync(cancellationToken);    _logger.LogInformation("Removed Redirect from Queue");    return result;  }}
Enter fullscreen modeExit fullscreen mode

With that in place, I needed to add the queue as a singleton in my service collection:

var builder = WebApplication.CreateBuilder(args);// ...builder.Services.AddSingleton<IAccumulatorQueue, AccumulatorQueue>();
Enter fullscreen modeExit fullscreen mode

In the project, I have aLinkManager class that handles the the short links and redirection. When redirection is about to happen, I just use the queue to add the item:

// ...await _queue.PushAsync(new Redirect(){  Key = key,  Destination = dest,  Referer = ctx.Request.Headers.Referer.FirstOrDefault(),  Origin = ctx.Request.Headers.Origin.FirstOrDefault(),  QueryString = ctx.Request.QueryString.Value,  Time = DateTime.UtcNow});
Enter fullscreen modeExit fullscreen mode

This call should be really fast and allow the redirection to happen quickly without waiting for the actual storage of the information. That’s where theBackgroundService comes in.

The basis of theBackgroundService class is theIHostedService interface. This simple interface just has a StartAsync and StopAsync methods:

public interface IHostedService{  Task StartAsync(CancellationToken cancellationToken);  Task StopAsync(CancellationToken cancellationToken);}
Enter fullscreen modeExit fullscreen mode

This interface allows you to register hosted services that .NET can start. You do this by just adding it to the service collection’s AddHostedService:

builder.Services.AddHostedService<YOURSERVICENAME>();
Enter fullscreen modeExit fullscreen mode

To make this work, I needed to implement my ownBackgroundService class. TheBackgroundService implements this interface and exposes two methods that you would override to handle these calls:

public abstract class BackgroundService : IHostedService, IDisposable{  // ...  protected abstract Task ExecuteAsync(CancellationToken stoppingToken);  public virtual Task StartAsync(CancellationToken cancellationToken);  public virtual Task StopAsync(CancellationToken cancellationToken);}
Enter fullscreen modeExit fullscreen mode

With that information, I was ready to implement my solution. I wasn’t sure what I wanted to do in the background service (call an Azure pub/sub solution, store it in my local storage, etc.), but I knew that I needed it to be handled in the background. So how did I accomplish this. I first sub-classed the BackgroundService (it’s an abstract class) to a class I calledAccumulatorBackgroundService. I first injected the queue I built earlier:

public class AccumulatorBackgroundService : BackgroundService{  private readonly IAccumulatorQueue _queue;  public AccumulatorBackgroundService(IAccumulatorQueue queue,     ...)  {    _queue = queue;    // ...  }
Enter fullscreen modeExit fullscreen mode

With that in place, I had to first override the abstract ExecuteAsync method:

protected async override Task ExecuteAsync(CancellationToken stoppingToken){  while (!stoppingToken.IsCancellationRequested)  {    try    {      var redirect = await _queue.PullAsync(stoppingToken);      await _repo.InsertRedirect(redirect);    }    catch (Exception ex)    {      _logger.LogError($"Failure while processing queue {ex}");    }  }}
Enter fullscreen modeExit fullscreen mode

The ExecuteAsync is a long-running method and I can continue to loop until the stoppingToken is used to cancel the process. Inside that, I just call PullAsync from the queue. This process waits until a new item is added to the queue. Note we’re not polling but waiting on the channel to return with a result. Once that happens, I use a repository object to save the redirect object to the data store.

The only other thing this class needs to do is handle the stopping of the method. This is done by calling the base class (BackgroundService) method for StopAsync with the cancellation token. This is called by the run-time when it needs to shut down the hosted service:

public override async Task StopAsync(CancellationToken cancellationToken){  _logger.LogInformation("Stopping Background Service");  await base.StopAsync(cancellationToken);}
Enter fullscreen modeExit fullscreen mode

Now that we have all the code, we just need to register our queue and hosted service:

builder.Services.AddSingleton<IAccumulatorQueue, AccumulatorQueue>();builder.Services.AddHostedService<AccumulatorBackgroundService>();
Enter fullscreen modeExit fullscreen mode

You can see the code in the GitHub repository if you want to play with it:

https://github.com/shawnwildermuth/ShawnLink

Thanks!

Creative Commons License

This work byShawn Wildermuth is licensed under aCreative Commons Attribution-NonCommercial-NoDerivs 3.0 Unported License.

Based on a work atwildermuth.com.


If you liked this article, see Shawn's courses onPluralsight.

Top comments(0)

Subscribe
pic
Create template

Templates let you quickly answer FAQs or store snippets for re-use.

Dismiss

Are you sure you want to hide this comment? It will become hidden in your post, but will still be visible via the comment'spermalink.

For further actions, you may consider blocking this person and/orreporting abuse

Shawn Wildermuth has been tinkering with computers and software since he got a Vic-20 back in the early ‘80s. He has been a Microsoft MVP, Pluralsight Author, and filmmaker.
  • Location
    Atlanta, GA
  • Education
    Some College
  • Pronouns
    he/him
  • Work
    Mind at Wilder Minds LLC
  • Joined

More fromShawn Wildermuth

DEV Community

We're a place where coders share, stay up-to-date and grow their careers.

Log in Create account

[8]ページ先頭

©2009-2025 Movatter.jp