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

1

6

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.

Scupper answered 10/1, 2018 at 0:48 Comment(18)
Check this out: hangfire.ioAlicaalicante
@Alicaalicante already using it. I'm starting my job using hangfire as explained above. I omitted mentioning because it is irrelevant as far as I understandScupper
how is it irrelevant? With hangfire you just do it wherever it makes sense for you?Alicaalicante
it doesn't matter whether I use hangfire or start them as simple Tasks. The question remains as it is.Scupper
Why isn't there a method where I can start some a Task because that is not general to the whole framework, it is something you want to do. it doesn't make sense to start background jobs in a method called "Configure" then don't do it. I'm not sure where I'm supposed to start this task wherever it makes sense to structure code in YOUR app.Alicaalicante
@Alicaalicante so how am I supposed to do this if I shouldn't be running persistent background tasks? Putting it in a separate process is such a hassle because it is sharing a lot of functionality with the webserver. It is using the same data that the webserver is already using to do other things. Also, like I said, it is streaming that data to the client on a websocket, so it really is part of the web server. I don't see any place in the web app where I can do this + problem described about EF Core, please read the question.Scupper
shouldn't be running persistent background tasks? who said you shouldn't do it? you can do whatever you like. But since this is not what the framework is designed for, it doesn't provide a common entry point to place that. You have to choose one yourself (which you've done already).Alicaalicante
@Alicaalicante which I've done already and doesn't work correctly because it gets run when I create my migration code using EF Core.. that doesn't make sense, the tasks shouldn't be started in the configuration method that is used by EF Core, there should be another place or the app should know that it is being run by EF Core tools and exclude the part of the code that creates those tasks. Either way, running EF Core migration shouldn't be kicking off those tasks, that's a problem on its own that needs a solution regardless of anything else.Scupper
I really don't understand what migration code you are talking about since you didn't post any code.Alicaalicante
@Alicaalicante edited to add more details about the EF Core toolsScupper
Could your background process be written as a "service" that you add during ConfigureServices? That sees like a logical way of doing it to me, as it would also allow controllers to access the "service" to make calls against it as needed.Imminent
@Spacemonkey if you want to avoid that code being run when you do dotnet ef migrations add InitialCreate switch to design time context factory - add a class to your code implementing IDesignTimeDbContextFactory<YourContext>.Alicaalicante
@BradleyUffner I don't see why DI would matter in this case. The point is that I need to start them when the server starts. They need to start calculating results based on some stream of data and make that available to the web server (and the client) when needed (with live updates, I'm using RX). My web server is essentially always doing something in the background whether the client (me using a browser) is currently connected or not.Scupper
@Alicaalicante that seems like it will solve the issue, I will try. it's late here tho so I might wait until tomorrow's night.Scupper
@Spacemonkey My proposal isn't specifically about DI, it's about writing your background processing functionality as a "service" that just happens to be exposed by DI. The ConfigureServices method seems like a logical place to start (and register) a BackgroundProcessingService (or whatever you want to call your background functionality).Imminent
@BradleyUffner hmm that worked but not exactly as you said. I now start those services when they are first requested from the DI container. As in, I start the service inside my "services.AddSingleton" method. The object is created, started then returned to be registered as a singleton. This solves the problem, but it is a hack. The jobs will only start now when they are first requested with DI, they really should be starting once the app starts app anyway whether relying on the lazy evaluation of DI. btw, any code in ConfigureServices is also run when EF Core tool is run, just like ConfigureScupper
I can start making a small example of what I have in mind, if you have a few minutes to spare. (Hmm, ConfigureServices getting run when tooling is run could throw a wrench in to my plan. I wasn't counting on that.)Imminent
@BradleyUffner I have to go to sleep now it's 3 AM, but I'll be back tomorrow. Thanks for your help :)Scupper
E
8

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>();
Epiphany answered 9/3, 2018 at 12:33 Comment(5)
Please add the applicable information from those link to your answer. "Link only" answers are highly discouraged on this site, as the linked page could go away, making a potentially good answer, worthless.Imminent
@BradleyUffner better now?Epiphany
Much! Thank you.Imminent
@BradleyUffner ;) pleasure to shareEpiphany
It seems a bit odd to me that Dispose will do a cancel. This would mean that after dispose, the cancellation flow still has the possibility to use data from the just disposed object.Polk

© 2022 - 2024 — McMap. All rights reserved.