What should ExecuteAsync do in an ASPNet Core application? - c#

I have an ASP.NET Core 6.0 application which I was running as command line, but now have moved it to run as a service if it is started as one. It works fine, but I had to make a service for it and needed to implement ExecuteAsync:
builder.Services.AddHostedService<ServicePlaceHolder>();
Here is that class:
public class ServicePlaceHolder : BackgroundService
{
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
Log.Information("Service is starting.");
stoppingToken.Register(() => Log.Information("Stopping"));
while (!stoppingToken.IsCancellationRequested)
{
await Task.Delay(TimeSpan.FromSeconds(1), stoppingToken);
}
}
}
But I really do not know what ExecuteAsync should be doing, and don't love this silly infinite loop. What should it do? Can I just await the cancellation?

A BackgroundService implementation is not required to run the ExecuteAsync method the entire lifetime of the application. I have used it to warm up a cache and simply exit, for example:
internal sealed class CacheInitializer : BackgroundService
{
private readonly ICacheService _cacheService;
public CacheInitializer(ICacheService cacheService)
=> _cacheService = cacheService;
protected override Task ExecuteAsync(CancellationToken stoppingToken)
{
return _cacheService.InitializeAsync(stoppingToken);
}
}
That method takes about a couple seconds to run.
The purpose of BackgroundService and IHostedService is to execute something when the entire application has been initialized. How long that takes doesn't matter to the framework.
You also have the option of using IHostedService (BackgroundService is derived from this interface, take a look at the source code), which gives you are StartAsync and StopAsync method. I've used that to start a timer and stop a timer and run something intermittently among other things such as starting a program and stopping a program.
So in your case where you just need to execute some program, you could simply do this:
internal sealed class ProgramRunner : BackgroundService
{
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
using (var p = Process.Start(new ProcessStartInfo
{
FileName = "myProgram.exe",
Arguments = "-c -f",
UseShellExecute = false,
WindowStyle = ProcessWindowStyle.Hidden,
CreateNoWindow = true
}))
{
await p.WaitForExitAsync(stoppingToken);
}
}
}

Related

What to return in StartAsync and StopAsync of a hosted service for .NET Core 5?

Trying to roughly follow MSDN, I've added a hosted service after my scoped services in StartUp class.
public void ConfigureServices(IServiceCollection services)
{
...
services.AddScoped<IUtilityService, UtilityService>();
services.AddHostedService<StartupService>();
...
}
I've implemented StartAsync like this.
public class StartupService : IHostedService
{
private IServiceProvider Provider { get; }
public StartupService(IServiceProvider provider)
{
Provider = provider;
}
public Task StartAsync(CancellationToken cancellationToken)
{
IServiceScope scope = Provider.CreateScope();
IUtilityService service = scope.ServiceProvider
.GetRequiredService<IUtilityService>();
service.Seed();
return Task.CompletedTask;
}
public Task StopAsync(CancellationToken cancellationToken)
{
throw new NotImplementedException();
}
}
I've read a number of articles and blogs but it's above my ability to understand what should be returned at the end of the methods. It seems to work for now but I can clearly see that I'm breaching the idea by not using asynchronous calls and returninig a dummy (not even that at stop!) so I can safely conclude that I'm doing it wrong (although not apparently but I'm sure it's coming to bite my behind in the future).
What should I return in the implementation to ensure I'm "working with" not agains the framework?
StartAsync needs to return a Task, which may or may not be running (but ideally it should be running, thats the point of a HostedService - an operation/task that runs for the lifetime of the application, or just for some extended period of time longer than normal).
It looks like you are trying to perform extra startup items using a HostedService, instead of just trying to run a task/operation that will last for the entire lifetime of the application.
If this is the case, you can have a pretty simple setup. The thing you want to return from your StartAsync() method is a Task. When you return a Task.CompletedTask, you are saying that the work is already done and there is no code executing - the task is completed. What you want to return is your code that is doing your extra startup items that is running inside of a Task object. The good thing about the HostedService in asp.net is that it does not matter how long the task runs for (since it is meant to run tasks for the entire lifetime of the app).
One important note before the code example - if you are using a Scoped service in your task, then you need to generate a scope with the IServiceScopeFactory, read about that in this StackOverflow post
If you refactor your service method to return a task, you could just return that:
public Task StartAsync(CancellationToken)
{
IServiceScope scope = Provider.CreateScope();
IUtilityService service = scope.ServiceProvider
.GetRequiredService<IUtilityService>();
// If Seed returns a Task
return service.Seed();
}
If you have multiple service methods that all return a task, you could return a task that is waiting for all of the tasks to finish
public Task StartAsync(CancellationToken)
{
IServiceScope scope = Provider.CreateScope();
IUtilityService service = scope.ServiceProvider
.GetRequiredService<IUtilityService>();
ISomeOtherService someOtherService = scope.ServiceProvider
.GetRequiredService<ISomeOtherService>();
var tasks = new List<Task>();
tasks.Add(service.Seed());
tasks.Add(someOtherService.SomeOtherStartupTask());
return Task.WhenAll(tasks);
}
If your startup tasks do alot of CPU bound work, just return a Task.Run(() => {});
public Task StartAsync(CancellationToken)
{
// Return a task which represents my long running cpu startup work...
return Task.Run(() => {
IServiceScope scope = Provider.CreateScope();
IUtilityService service = scope.ServiceProvider
.GetRequiredService<IUtilityService>();
service.LongRunningCpuStartupMethod1();
service.LongRunningCpuStartupMethod2();
}
}
To use your cancellation token, some of the example code below shows how it can be done, by Catching a TaskCanceledException in a Try/Catch, and forcefully exiting our running loop.
Then we move on to tasks that will run for the entire application lifetime.
Heres the base class that I use for all of my HostedService implementations that are designed to never stop running until the application shuts down.
public abstract class HostedService : IHostedService
{
// Example untested base class code kindly provided by David Fowler: https://gist.github.com/davidfowl/a7dd5064d9dcf35b6eae1a7953d615e3
private Task _executingTask;
private CancellationTokenSource _cts;
public Task StartAsync(CancellationToken cancellationToken)
{
// Create a linked token so we can trigger cancellation outside of this token's cancellation
_cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
// Store the task we're executing
_executingTask = ExecuteAsync(_cts.Token);
// If the task is completed then return it, otherwise it's running
return _executingTask.IsCompleted ? _executingTask : Task.CompletedTask;
}
public virtual async Task StopAsync(CancellationToken cancellationToken)
{
// Stop called without start
if (_executingTask == null)
{
return;
}
// Signal cancellation to the executing method
_cts.Cancel();
// Wait until the task completes or the stop token triggers
await Task.WhenAny(_executingTask, Task.Delay(-1, cancellationToken));
// Throw if cancellation triggered
cancellationToken.ThrowIfCancellationRequested();
}
// Derived classes should override this and execute a long running method until
// cancellation is requested
protected abstract Task ExecuteAsync(CancellationToken cancellationToken);
}
In this Base Class, you will see that when StartAsync is called, we invoke our ExecuteAsync() method which returns a Task that contains a while loop - the Task will not stop running until our cancellation token is triggered, or the application gracefully/forcefully stops.
The ExecuteAsync() method needs to be implemented by any class inheriting from this base class, which should be all of your HostedService's.
Here is an example HostedService implementation that inherits from this Base class designed to checkin every 30 seconds. You will notice that the ExecuteAsync() method enters into a while loop and never exits - it will 'tick' once every second, and this is where you can invoke other methods such as checking in to another server on some regular interval. All of the code in this loop is returned in the Task to StartAsync() and returned to the caller. The task will not die until the while loop exits or the application dies, or the cancellation token is triggered.
public class CounterHostedService : HostedService
{
private readonly IServiceScopeFactory _scopeFactory;
private readonly ILog _logger;
public CounterHostedService(IServiceScopeFactory scopeFactory, ILog logger)
{
_scopeFactory = scopeFactory;
_logger = logger;
}
// Checkin every 30 seconds
private int CheckinFrequency = 30;
private DateTime CheckedIn;
protected override async Task ExecuteAsync(CancellationToken cancellationToken)
{
int counter = 0;
var runningTasks = new List<Task>();
while (true)
{
// This loop will run for the lifetime of the application.
// Time since last checkin is checked every tick. If time since last exceeds the frequency, we perform the action without breaking the execution of our main Task
var timeSinceCheckin = (DateTime.UtcNow - CheckedIn).TotalSeconds;
if (timeSinceCheckin > CheckinFrequency)
{
var checkinTask = Checkin();
runningTasks.Add(checkinTask);
}
try
{
// The loop will 'tick' every second.
await Task.Delay(TimeSpan.FromSeconds(1), cancellationToken);
}
catch (TaskCanceledException)
{
// Break out of the long running task because the Task was cancelled externally
break;
}
counter++;
}
}
// Custom override of StopAsync. This is only triggered when the application
// GRACEFULLY shuts down. If it is not graceful, this code will not execute. Neither will the code for StopAsync in the base method.
public override async Task StopAsync(CancellationToken cancellationToken)
{
_logger.Info($"HostedService Gracefully Shutting down");
// Perform base StopAsync
await base.StopAsync(cancellationToken);
}
// Creates a task that performs a checkin, and returns the running task
private Task Checkin()
{
return Task.Run(async () =>
{
// await DoTheThingThatWillCheckin();
});
}
}
Notice you can also override the StopAsync() method to do some logging, and anything else needed for your shutdown events. Try to avoid critical logic in StopAsync, as its not guaranteed to be called.
I have a service framework that contains many services and I can see each service's status in a web panel as well. So, in my solution:
In StartAsync method, I initialize and start all jobs, so the system waits for the jobs to be finished, after finishing, I return Task.CompletedTask
In StopAsync, I try to stop all jobs and ensure they're stopped successfully, then return Task.CompletedTask

Multiple IHostedService in a console app execution order and delay time

I have two background services.One of them perform measurements via serialport and the other one called SettingsWorker fetching measurement settings(like number of sensors,modules,fetching delays and so on) and share it with via static variable.
public class MeasurementWorker : BackgroundService
while (!stoppingToken.IsCancellationRequested)
{
try
{
....
await Task.Delay(_measurementSettings.MeasurementFetchPeriod, stoppingToken);
}
}
public class SettingsWorker : BackgroundService
while (!stoppingToken.IsCancellationRequested)
{
try
{
....
await Task.Delay(_measurementSettings.MeasurementFetchPeriod, stoppingToken);
}
}
Here is my program class.
.ConfigureServices((hostContext, services) =>
{
services.AddHostedService<SettingsWorker>();
services.AddHostedService<MeasurementWorker>();
}).UseSerilog();
When my application starts all the time SettingWorker must be executed first and set shared static variable.In my observations ordering services.AddHostedService changes execution order when application starts.
If I add SettingsWorker in the first order is it guaranteed that it will be firstly executed background task ?
If there are multiple background services like in my case, when any service hasn't finished execution and not reach await Task.Delay line yet,could it possibly stop and another background service starts to run just because delay time has been reached.
I did some experiments about overlapping invocations.I created two background services as follows.(BackgroundService2 is the same with BackgroundService1 except it logs as BackgroundService 2)
public class BackgroundService1 : BackgroundService
{
private int executionCount = 0;
private readonly ILogger<BackgroundService1> _logger;
private Timer _timer;
public BackgroundService1(ILogger<BackgroundService1> logger)
{
_logger = logger;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
_logger.LogInformation($"BackgroundService 1 start to work. Thread ID : {Thread.CurrentThread.ManagedThreadId} Count: {executionCount}");
executionCount++;
_logger.LogInformation($"BackgroundService 1 done work. Thread ID : {Thread.CurrentThread.ManagedThreadId} Count: {executionCount}");
await Task.Delay(100, stoppingToken);
}
}
Here is snapshot of overlapping calls.
If we don't want to invoke a backgroundservice unless the previous call has finished we have to control it via static shared variable like ;
if (!numberOfActiveJobs==1) {
Interlocked.Increment(numberOfActiveJobs);
Although I didn't find a documentation about which background service works first when application starts up,it seems it depend on the registration order of background services.
Note (I also observed that thread which starts background job,finishes the job.Like:Thread 7 starts job,(can be possibly interrupted by another thread) thread 7 finishes job

How to start HostedService in MVC Core app without http request

In my MVC .NET core 2.2 app there is HostedService which doing background work.
It is register in ConfigureServices method of Startap class
services.AddHostedService<Engines.KontolerTimer>();
Since this is background service independent of users requests I want to start my background service immediately when app starts.
Now is case to my HostedService staring after first user request.
What is proper way to start HostedService when MVC Core app start
My serivce looks like this one https://learn.microsoft.com/en-us/aspnet/core/fundamentals/host/hosted-services?view=aspnetcore-2.2
internal class TimedHostedService : IHostedService, IDisposable
{
private readonly ILogger _logger;
private Timer _timer;
public TimedHostedService(ILogger<TimedHostedService> logger)
{
_logger = logger;
}
public Task StartAsync(CancellationToken cancellationToken)
{
_logger.LogInformation("Timed Background Service is starting.");
_timer = new Timer(DoWork, null, TimeSpan.Zero,
TimeSpan.FromSeconds(5));
return Task.CompletedTask;
}
private void DoWork(object state)
{
_logger.LogInformation("Timed Background Service is working.");
}
public Task StopAsync(CancellationToken cancellationToken)
{
_logger.LogInformation("Timed Background Service is stopping.");
_timer?.Change(Timeout.Infinite, 0);
return Task.CompletedTask;
}
public void Dispose()
{
_timer?.Dispose();
}
}
Looks like I have problem staring app at all.
My porgram cs looks like
public class Program
{
public static void Main(string[] args)
{
CreateWebHostBuilder(args).Build().Run();
}
public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseSerilog((ctx, config) => { config.ReadFrom.Configuration(ctx.Configuration); })
.UseStartup<Startup>();
}
And I do not hit any break point before first user request.
Am I miss something, this is default .Net Core app created by VS2017
Here is my starup.cs
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
private Models.Configuration.SerialPortConfiguration serialPortConfiguration;
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.Configure<CookiePolicyOptions>(options =>
{
// This lambda determines whether user consent for non-essential cookies is needed for a given request.
options.CheckConsentNeeded = context => true;
options.MinimumSameSitePolicy = SameSiteMode.None;
});
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(
Configuration.GetConnectionString("DefaultConnection")));
services.AddIdentity<ApplicationUser, ApplicationRole>(options => options.Stores.MaxLengthForKeys = 128)
.AddDefaultUI(UIFramework.Bootstrap4)
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
services.AddDbContext<Data.Parking.parkingContext>(options =>
options.UseSqlServer(
Configuration.GetConnectionString("DefaultConnection")));
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
services.AddHostedService<Engines.KontolerTimer>();}
When you run this using Visual Studio, you are likely using IIS Express, which isn't going to run your ASP.NET Core project until the first request is made (that's really just how IIS works by default). This applies when using the InProcess hosting-model that's new with ASP.NET Core 2.2, which I expect you must be using in order to see this issue. See this GitHub issue for more.
You can prove this theory by removing the AspNetCoreHostingModel XML element from the .csproj file that you're using to host the ASP.NET Core application (which will switch it back to the OutOfProcess mode). It looks like there's a "Hosting Model" option under "Debug" in the project properties dialog of VS2017 that you can change to "Out Of Process" if you don't want to edit the .csproj directly.
If you want the hosting-model to be out-of-process only for a production site, you could use a Web.config transform, for example. If you want it to be out-of-process both during development and in production, just changing the property I called out above will be enough as this gets converted automatically into a Web.config property. If you would prefer to use the in-process model, enabling preload in the IIS application is a good option (described here).
Background services start when your application starts, then it's up to you to synchronize with it.
You can implement a background service by using the BackgroundService class from the namespace Microsoft.Extensions.Hosting(Microsoft.Extensions.Hosting.Abstractions assembly):
First the declare the interface of your service (in this case it is empty, not nice, but clean):
public interface IMyService : IHostedService
{
}
Then, declare your service. The following snippet declares a service that at startup waist for 5 seconds, and then executes a task every 2 minutes and half:
internal sealed class MyService : BackgroundService, IMyService
{
private const int InitialDelay = 5 * 1000; //5 seconds;
private const int Delay = (5 * 60 * 1000) / 2; // 2.5 minutes
private readonly ILogger<MyService> m_Logger;
public MyService(ILogger<MyService> logger, IServiceProvider serviceProvider)
{
if (logger == null)
throw new ArgumentNullException(nameof(logger));
if (serviceProvider == null)
throw new ArgumentNullException(nameof(serviceProvider));
this.m_Logger = logger;
this.m_ServiceProvider = serviceProvider;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
try
{
m_Logger.LogDebug($"MyService is starting.");
stoppingToken.Register(() => m_Logger.LogDebug($"MyService background task is stopping because cancelled."));
if (!stoppingToken.IsCancellationRequested)
{
m_Logger.LogDebug($"MyService is waiting to be scheduled.");
await Task.Delay(InitialDelay, stoppingToken);
}
m_Logger.LogDebug($"MyService is working.");
while (!stoppingToken.IsCancellationRequested)
{
await DoSomethingAsync();
await Task.Delay(Delay);
}
m_Logger.LogDebug($"MyService background task is stopping.");
}
catch (Exception ex)
{
m_Logger.LogDebug("MyService encountered a fatal error while w task is stopping: {Exception}.", ex.ToString());
}
}
private async Task DoSomethingAsync()
{
// do something here
await Task.Delay(1000);
}
}
As you can see, it's up to you to keep the background service "alive". Finally, you have to register it in your Startup.cs at the end of your ConfigureServices method:
services.AddSingleton<Microsoft.Extensions.Hosting.IHostedService, MyService>();
This is sufficient to have the service started. keep in mind that your application could be actually started at a later time if hosted in IIS: your application is (re)started everytime your assembly is recycled. Instead, using Kestrel, provides a single instance application which will not be recycled.
For those using .Net Core 2.1 or lower, the Background class is not available, but you can get the definition from github (I post what I used in the past as the github repository can be moved):
//borrowed from .NET Core 2.1 (we are currently targeting 2.0.3)
// Copyright (c) .NET Foundation. Licensed under the Apache License, Version 2.0.
/// <summary>
/// Base class for implementing a long running <see cref="IHostedService"/>.
/// </summary>
public abstract class BackgroundService : IHostedService, IDisposable
{
private Task _executingTask;
private readonly CancellationTokenSource _stoppingCts =
new CancellationTokenSource();
protected abstract Task ExecuteAsync(CancellationToken cancellationToken);
public virtual Task StartAsync(CancellationToken cancellationToken)
{
// Store the task we're executing
_executingTask = ExecuteAsync(_stoppingCts.Token);
// If the task is completed then return it,
// this will bubble cancellation and failure to the caller
if (_executingTask.IsCompleted)
{
return _executingTask;
}
// Otherwise it's running
return Task.CompletedTask;
}
public virtual async Task StopAsync(CancellationToken cancellationToken)
{
// Stop called without start
if (_executingTask == null)
{
return;
}
try
{
// Signal cancellation to the executing method
_stoppingCts.Cancel();
}
finally
{
// Wait until the task completes or the stop token triggers
await Task.WhenAny(_executingTask, Task.Delay(Timeout.Infinite,
cancellationToken));
}
}
public virtual void Dispose()
{
_stoppingCts.Cancel();
}
}
For me... background tasks weren't starting until the first page request.
But then I noticed in my Publish / Edit, I didn't have Destination Url set. (and also I didn't have a home Index page)...
Once I added a valid Destination Url... that page would popup after publishing and be my "first" page request and background tasks would start.
If you want o have a Service doing background tasks (similar to old Windows Services) I would suggest you to use: https://learn.microsoft.com/en-us/aspnet/core/fundamentals/host/generic-host?view=aspnetcore-2.2 instead of a WebHost.
WebHost add a lot of stuff that probably you won't need since seems a simple background job (assuming that reading your code).
Hosted services do start when the host is starting. With the WebHost, the hosted services will be started right after the application has started. This means that if implemented correctly, your hosted service will run without requiring a request to come in.
When I try your example hosted service on a fresh ASP.NET Core application, it works just fine, so if it is not working for you, then apparently your actual implementation KontolerTimer is not correct.

Short running background task in .NET Core

I just discovered IHostedService and .NET Core 2.1 BackgroundService class. I think idea is awesome. Documentation.
All examples I found are used for long running tasks (until application die).
But I need it for short time. Which is the correct way of doing it?
For example:
I want to execute a few queries (they will take approx. 10 seconds) after application starts. And only if in development mode. I do not want to delay application startup so IHostedService seems good approach. I can not use Task.Factory.StartNew, because I need dependency injection.
Currently I am doing like this:
public class UpdateTranslatesBackgroundService: BackgroundService
{
private readonly MyService _service;
public UpdateTranslatesBackgroundService(MyService service)
{
//MService injects DbContext, IConfiguration, IMemoryCache, ...
this._service = service;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
await ...
}
}
startup:
public static IServiceProvider Build(IServiceCollection services, ...)
{
//.....
if (hostingEnvironment.IsDevelopment())
services.AddSingleton<IHostedService, UpdateTranslatesBackgroundService>();
//.....
}
But this seems overkill. Is it? Register singleton (that means class exists while application lives). I don't need this. Just create class, run method, dispose class. All in background task.
There's no need to do any magic for this to work.
Simply:
Register the service you need to run in ConfigureServices
Resolve the instance you need in Configure and run it.
To avoid blocking, use Task.Run.
You must register the instance, or dependency injection won't work. That's unavoidable; if you need DI, then you have to do it.
Beyond that, it's trivial to do what you ask, like this:
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.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
services.AddTransient<MyTasks>(); // <--- This
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
// Blocking
app.ApplicationServices.GetRequiredService<MyTasks>().Execute();
// Non-blocking
Task.Run(() => { app.ApplicationServices.GetRequiredService<MyTasks>().Execute(); });
}
else
{
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseMvc();
}
}
public class MyTasks
{
private readonly ILogger _logger;
public MyTasks(ILogger<MyTasks> logger)
{
_logger = logger;
}
public void Execute()
{
_logger.LogInformation("Hello World");
}
}
BackgroundService exists specifically for long running processes; if it's a once of, don't use it.
Well I think there is more then one question here.
First let me point out something you are probably aware of async != multithreaded.
So BackgroundService will not make you app "multithreaded" it can run inside a single thread without no problem. And if you are doing blocking operations on that thread it will still block startup. Lets say in the class you implement all the sql queries in a not real async way something similar to
public class StopStartupService : BackgroundService
{
protected override Task ExecuteAsync(CancellationToken stoppingToken)
{
System.Threading.Thread.Sleep(1000);
return Task.CompletedTask;
}
}
This will still block startup.
So there is another question.
How should you run background jobs?
For this in simple cases Task.Run(Try to avoid Task.Factory.StartNew if you are not sure how to configure it) should do the job, but that is not to say this is the best or a good way to do it. There are a bunch of open source libraries that will do this for you and it might be good to have a look at what they provide. There are a lot of problems you might not be aware of , that can create frustrating bugs if you just use Task.Run
The second question I can see is.
Should I do fire and forget in c#?
For me this is a definite NO(but XAML people might not agree). No matter what you do, you need to keep track of when the thing you are doing is done. In your case you might want to do a rollback in the database if someone stops the app before the queries are done. But more than that you would want to know when you can start using the data that the queries provided. So BackgroundService helps you to simplify the execution but is difficult to keep track of completion.
Should you use a singleton?
As you already mentioned using singletons can be a dangerous thing especially if you don't clean things properly, but more than that the context of the service you are using will be the same for the life time of the object. So with this all depends on your implementation of the service if there will be problems.
I do something like this to do what you want.
public interface IStartupJob
{
Task ExecuteAsync(CancellationToken stoppingToken);
}
public class DBJob : IStartupJob
{
public Task ExecuteAsync(CancellationToken stoppingToken)
{
return Task.Run(() => System.Threading.Thread.Sleep(10000));
}
}
public class StartupJobService<TJob> : IHostedService, IDisposable where TJob: class,IStartupJob
{
//This ensures a single start of the task this is important on a singletone
private readonly Lazy<Task> _executingTask;
private readonly CancellationTokenSource _stoppingCts = new CancellationTokenSource();
public StartupJobService(Func<TJob> factory)
{
//In order for the transient item to be in memory as long as it is needed not to be in memory for the lifetime of the singleton I use a simple factory
_executingTask = new Lazy<Task>(() => factory().ExecuteAsync(_stoppingCts.Token));
}
//You can use this to tell if the job is done
public virtual Task Done => _executingTask.IsValueCreated ? _executingTask.Value : throw new Exception("BackgroundService not started");
public virtual Task StartAsync(CancellationToken cancellationToken)
{
if (_executingTask.Value.IsCompleted)
{
return _executingTask.Value;
}
return Task.CompletedTask;
}
public virtual async Task StopAsync(CancellationToken cancellationToken)
{
if (_executingTask == null)
{
return;
}
try
{
_stoppingCts.Cancel();
}
finally
{
await Task.WhenAny(_executingTask.Value, Task.Delay(Timeout.Infinite,
cancellationToken));
}
}
public virtual void Dispose()
{
_stoppingCts.Cancel();
}
public static void AddService(IServiceCollection services)
{
//Helper to register the job
services.AddTransient<TJob, TJob>();
services.AddSingleton<Func<TJob>>(cont =>
{
return () => cont.GetService<TJob>();
});
services.AddSingleton<IHostedService, StartupJobService<TJob>>();
}
}
There is a library called Communist.StartupTasks that handles this exact scenario. It's available on Nuget.
It's designed specifically to run tasks during application launch in a .NET Core App. It fully supports dependency injection.
Please note that it executes tasks sequentially and it blocks until all tasks are complete (i.e. your app won't accept requests until startup tasks complete).

Where am I supposed to start persistent background tasks in ASP.NET Core?

In my web application (ASP.NET Core), I want to run a job in the background that is listening to a remote server, calculating some results and pushing it to the client on Pusher (a websocket).
I'm not sure where I'm supposed to start this task. Currently I start it at the end of
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
in Startup.cs
but I think there is something wrong about that, it doesn't make sense to start background jobs in a method called "Configure". I was expecting to find a Start method somewhere
Also, when I try to use EF Core to generate initial database migration file, it actually executes that method and starts my tasks.. which clearly doesn't make any sense:
dotnet ef migrations add InitialCreate
running that from console creates migration code which will be used to create the database on SQL Server based on my data models.
Why isn't there a method where I can start some a Task? I don't want this to be on a separate process, it really doesn't need its own process and it is essentially a part of the web server because it does communicate with the client (browser) via a websocket, so it makes sense to run it as part of the web server.
I believe you're looking for this
https://blogs.msdn.microsoft.com/cesardelatorre/2017/11/18/implementing-background-tasks-in-microservices-with-ihostedservice-and-the-backgroundservice-class-net-core-2-x/
And i did a 2 hour self-proclaimed-award-winning hackathon against myself to learn abit of that.
https://github.com/nixxholas/nautilus
You can refer the injections here and implement the abstracts from there too.
Many MVC projects are not really required to operate persistent background tasks. This is why you don't see them baked into a fresh new project via the template. It's better to provide developers an interface to tap on and go ahead with it.
Also, with regards to opening that socket connection for such background tasks, I have yet to establish a solution for that. As far as I know/did, I was only able to broadcast payload to clients that are connected to my own socketmanager so you'll have to look elsewhere for that. I'll definitely beep if there is anything regarding websockets in an IHostedService.
Ok anyway here's what happens.
Put this somewhere in your project, its more of an interface for you to overload with to create your own task
/// Copyright(c) .NET Foundation.Licensed under the Apache License, Version 2.0.
/// <summary>
/// Base class for implementing a long running <see cref="IHostedService"/>.
/// </summary>
public abstract class BackgroundService : IHostedService, IDisposable
{
protected readonly IServiceScopeFactory _scopeFactory;
private Task _executingTask;
private readonly CancellationTokenSource _stoppingCts =
new CancellationTokenSource();
public BackgroundService(IServiceScopeFactory scopeFactory) {
_scopeFactory = scopeFactory;
}
protected abstract Task ExecuteAsync(CancellationToken stoppingToken);
public virtual Task StartAsync(CancellationToken cancellationToken)
{
// Store the task we're executing
_executingTask = ExecuteAsync(_stoppingCts.Token);
// If the task is completed then return it,
// this will bubble cancellation and failure to the caller
if (_executingTask.IsCompleted)
{
return _executingTask;
}
// Otherwise it's running
return Task.CompletedTask;
}
public virtual async Task StopAsync(CancellationToken cancellationToken)
{
// Stop called without start
if (_executingTask == null)
{
return;
}
try
{
// Signal cancellation to the executing method
_stoppingCts.Cancel();
}
finally
{
// Wait until the task completes or the stop token triggers
await Task.WhenAny(_executingTask, Task.Delay(Timeout.Infinite,
cancellationToken));
}
}
public virtual void Dispose()
{
_stoppingCts.Cancel();
}
}
Here's how you can actually use it
public class IncomingEthTxService : BackgroundService
{
public IncomingEthTxService(IServiceScopeFactory scopeFactory) : base(scopeFactory)
{
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
using (var scope = _scopeFactory.CreateScope())
{
var dbContext = scope.ServiceProvider.GetRequiredService<NautilusDbContext>();
Console.WriteLine("[IncomingEthTxService] Service is Running");
// Run something
await Task.Delay(5, stoppingToken);
}
}
}
}
If you noticed, there's a bonus there. You'll have to use a servicescope in order to access db operations because its a singleton.
Inject your service in
// Background Service Dependencies
services.AddSingleton<IHostedService, IncomingEthTxService>();

Categories

Resources