Alternative Solution To HostingEnvironment.QueueBackgroundWorkItem In .NET Core


Answer :

Update December 2019: ASP.NET Core 3.0 supports an easy way to implement background tasks using Microsoft.NET.Sdk.Worker. It's excellent and works really well.

As @axelheer mentioned IHostedService is the way to go in .NET Core 2.0 and above.

I needed a lightweight like for like ASP.NET Core replacement for HostingEnvironment.QueueBackgroundWorkItem, so I wrote DalSoft.Hosting.BackgroundQueue which uses.NET Core's 2.0 IHostedService.

PM> Install-Package DalSoft.Hosting.BackgroundQueue

In your ASP.NET Core Startup.cs:

public void ConfigureServices(IServiceCollection services) {    services.AddBackgroundQueue(onException:exception =>    {     }); } 

To queue a background Task just add BackgroundQueue to your controller's constructor and call Enqueue.

public EmailController(BackgroundQueue backgroundQueue) {    _backgroundQueue = backgroundQueue; }  [HttpPost, Route("/")] public IActionResult SendEmail([FromBody]emailRequest) {    _backgroundQueue.Enqueue(async cancellationToken =>    {       await _smtp.SendMailAsync(emailRequest.From, emailRequest.To, request.Body);    });     return Ok(); } 

QueueBackgroundWorkItem is gone, but we've got IApplicationLifetime instead of IRegisteredObject, which is being used by the former one. And it looks quite promising for such scenarios, I think.

The idea (and I'm still not quite sure, if it's a pretty bad one; thus, beware!) is to register a singleton, which spawns and observes new tasks. Within that singleton we can furthermore register a "stopped event" in order to proper await still running tasks.

This "concept" could be used for short running stuff like logging, mail sending, and the like. Things, that should not take much time, but would produce unnecessary delays for the current request.

public class BackgroundPool {     protected ILogger<BackgroundPool> Logger { get; }      public BackgroundPool(ILogger<BackgroundPool> logger, IApplicationLifetime lifetime)     {         if (logger == null)             throw new ArgumentNullException(nameof(logger));         if (lifetime == null)             throw new ArgumentNullException(nameof(lifetime));          lifetime.ApplicationStopped.Register(() =>         {             lock (currentTasksLock)             {                 Task.WaitAll(currentTasks.ToArray());             }              logger.LogInformation(BackgroundEvents.Close, "Background pool closed.");         });          Logger = logger;     }      private readonly object currentTasksLock = new object();      private readonly List<Task> currentTasks = new List<Task>();      public void SendStuff(Stuff whatever)     {         var task = Task.Run(async () =>         {             Logger.LogInformation(BackgroundEvents.Send, "Sending stuff...");              try             {                 // do THE stuff                  Logger.LogInformation(BackgroundEvents.SendDone, "Send stuff returns.");             }             catch (Exception ex)             {                 Logger.LogError(BackgroundEvents.SendFail, ex, "Send stuff failed.");             }         });          lock (currentTasksLock)         {             currentTasks.Add(task);              currentTasks.RemoveAll(t => t.IsCompleted);         }     } } 

Such a BackgroundPool should be registered as a singleton and can be used by any other component via DI. I'm currently using it for sending mails and it works fine (tested mail sending during app shutdown too).

Note: accessing stuff like the current HttpContext within the background task should not work. The old solution uses UnsafeQueueUserWorkItem to prohibit that anyway.

What do you think?

Update:

With ASP.NET Core 2.0 there's new stuff for background tasks, which get's better with ASP.NET Core 2.1: Implementing background tasks in .NET Core 2.x webapps or microservices with IHostedService and the BackgroundService class


You can use Hangfire (http://hangfire.io/) for background jobs in .NET Core.

For example :

var jobId = BackgroundJob.Enqueue(     () => Console.WriteLine("Fire-and-forget!")); 

Comments

Popular posts from this blog

Converting A String To Int In Groovy

"Cannot Create Cache Directory /home//.composer/cache/repo/https---packagist.org/, Or Directory Is Not Writable. Proceeding Without Cache"

Android How Can I Convert A String To A Editable