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
Related
I'm trying build a worker service on Core 5.0. My tree is basically like that =>
1 -) Program.cs 2-) Worker.cs 3-) MyStartUp.cs 4-) Client.cs
In MyStartUp.cs I am getting a list and calling Client class some servers according to list.
In the Client class, I connect to the devices and write the data I read to the database.
Device count nearly 1200, server way is TCP/IP.
What is your best suggestion for write a worker service like that?
How can I use threads in it best form?
Below is my first try. This form is working but it's so slow for 1000 different client because there is so much reads in client.
public class Worker : BackgroundService
{
private readonly ILogger<Worker> _logger;
public Worker(ILogger<Worker> logger)
{
_logger = logger;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
StartUp startUp = new StartUp();
}
}
public class StartUp
{
public StartUp()
{
//... get client datas and initialize client object
StartClients();
}
public void StartClients()
{
foreach (var item in ClientList)
{
new Thread(item.Run).Start();
}
}
}
public class Client
{
System.Timers.Timer timer ;
public Client()
{
timer = new Timer();
timer.Interval = 100;
timer.Elapsed += Timer_Elapsed;
//... initialize client connection and database
}
public void Run()
{
timer.Start();
}
private void Timer_Elapsed(object sender, ElapsedEventArgs e)
{
//... write values of read client to database
}
}
Say that you have 1k timers that run every 100ms, and say that each timer tick takes 50ms to execute. That means each timer needs 500ms/s, or 50% of one core, and you would need 500 cores to keep up with the work. You probably do not have that many cores, nor IO to process the requests, and that means the work will start piling up and your computer will more or less freeze since it does not have time to update the UI.
50ms might be an overestimation of the time used, but even at 5ms you would probably have issues unless you are running this on a monster server.
The solution would be to decrease the polling frequency to something more reasonable, say every 100s instead of every 100ms. Or to have one or more threads that polls your devices as fast as they can. For example something like:
private BlockingCollection<MyClient> clients = new ();
private List<Task> workers = new ();
public void StartWorker(){
workers.Add(Task.Run(Run));
void Run(){
foreach(var client in clients.GetConsumingEnumerable()){
// Do processing
clients.Add(client); // re-add client to queue
}
}
}
public void CloseAllWorkers(){
clients.CompleteAdding();
Task.WhenAll(workers).Wait();
}
I would note that usages of Thread is mostly superseded by tasks. And that creating a thread just to start a System.Timers.Timer is completely useless since the timer will run the tick event on the threadpool, regardless of the thread that started it. At least unless a synchronization object was specified.
I'm trying to execute logic on start up of my web API and this code will also be async.
I've thought of IStartupFilter but this is sync, which is a problem. StartUp.Configure() is also not an option, sync.
I've thought of IHostedService but this runs before the application is done loading and up for requests.
Any other ways of doing this ?
Something you probably didn't realize is that running the application has 2 parts, that are hidden by default when you call IHost.Run/IHost.RunAsync.
Basically, this:
var host = CreateHostBuilder(args).Build();
await host.RunAsync();
Is equivalent to this:
var host = CreateHostBuilder(args).Build();
await host.StartAsync();
await host.WaitForShutdownAsync();
When StartAsync returns, the application has already started and is ready to consume requests, so you probably want to do this:
var host = CreateHostBuilder(args).Build();
await host.StartAsync();
await PerformSomeWorkAfterStartupAsync();
await host.WaitForShutdownAsync();
Implement IStartupTask, register StartupTaskRunner and YourStartupTask in DI:
services
.AddStartupTasksRunner()
.AddStartupTask<YourStartupTask>();
And here the code based on Andrew's Lock post:
public interface IAsyncStartupTask
{
Task OnStartupAsync(CancellationToken cancellationToken);
}
internal class StartupTasksRunner : IHostedService
{
private readonly IEnumerable<IAsyncStartupTask> _startupTasks;
private readonly IHostApplicationLifetime _applicationLifetime;
private readonly SemaphoreSlim _semaphore;
public StartupTasksRunner(IEnumerable<IAsyncStartupTask> startupTasks, IHostApplicationLifetime applicationLifetime)
{
_startupTasks = startupTasks;
_applicationLifetime = applicationLifetime;
_semaphore = new SemaphoreSlim(0);
applicationLifetime.ApplicationStarted.Register(() => _semaphore.Release());
}
public async Task StartAsync(CancellationToken cancellationToken)
{
// wait for ApplicationStarted event to execute when app is listening to web requests
// await _semaphore.WaitAsync(cancellationToken);
foreach (var task in _startupTasks)
{
try
{
await task.OnStartupAsync(cancellationToken).ConfigureAwait(false);
}
catch
{
// stop the application if failing startup task if fatal
//_applicationLifetime.StopApplication();
throw;
}
}
}
public Task StopAsync(CancellationToken cancellationToken)
{
return Task.CompletedTask;
}
}
public static class ServiceCollectionExtensions
{
public static IServiceCollection AddStartupTasksRunner(this IServiceCollection services)
{
return services.AddHostedService<StartupTasksRunner>();
}
public static IServiceCollection AddStartupTask<TStartupTask>(this IServiceCollection services)
where TStartupTask : class, IAsyncStartupTask
{
return services.AddSingleton<IAsyncStartupTask, TStartupTask>();
}
}
Notes
The app startup order is:
Start HostedService's
Start Kesterl Server
Fire ApplicationStarted event
If you want to run your startup task before HostedServices started you need to make sure StartupTasksRunner registered before any other HostedService (they are started in registration order).
If you want to run your startup task after all HostedServices started but before application starts to receive web requests make sure you are registering StartupTasksRunner after any other HostedService.
If you want to run your startup task after Kestrel Server is started uncomment // await _semaphore.WaitAsync(cancellationToken); line. Be aware that it may run concurrently with web requests handling.
Also be aware that exceptions thrown by IHostedService.StartAsync method will be swallowed by framework. So if successful execution of startup tasks is necessary for your application uncomment // _applicationLifetime.StopApplication();
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
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.
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>();