25

Is the following a correct pattern to implement long running background work in Asp.Net Core? Or should I be using some form of Task.Run/TaskFactory.StartNew with TaskCreationOptions.LongRunning option?

    public void Configure(IApplicationLifetime lifetime)
    {
        lifetime.ApplicationStarted.Register(() =>
        {
            // not awaiting the 'promise task' here
            var t = DoWorkAsync(lifetime.ApplicationStopping);

            lifetime.ApplicationStopped.Register(() =>
            {
                try
                {
                    // give extra time to complete before shutting down
                    t.Wait(TimeSpan.FromSeconds(10));
                }
                catch (Exception)
                {
                    // ignore
                }
            });
        });
    }

    async Task DoWorkAsync(CancellationToken token)
    {
        while (!token.IsCancellationRequested)
        {
            await // async method
        }
    }
ubi
  • 4,041
  • 3
  • 33
  • 50

2 Answers2

29

Check also .NET Core 2.0 IHostedService. Here is the documentation. From .NET Core 2.1 we will have BackgroundService abstract class. It can be used like this:

public class UpdateBackgroundService: BackgroundService
{
    private readonly DbContext _context;

    public UpdateTranslatesBackgroundService(DbContext context)
    {
        this._context= context;
    }

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        await ...
    }
}

In your startup you just have to register class:

public static IServiceProvider Build(IServiceCollection services)
{
    //.....
    services.AddSingleton<IHostedService, UpdateBackgroundService>();
    services.AddTransient<IHostedService, UpdateBackgroundService>();  //For run at startup and die.
    //.....
}
Makla
  • 9,899
  • 16
  • 72
  • 142
  • Will it also be available when targeting the full .Net Framework? – sroll May 14 '18 at 16:36
  • Yes, because the hosting library, where IHostedService comes from, is available in a netstandard2.0 package. – alsami Aug 06 '18 at 10:30
  • @Makla is this Service running on another thread? Because my request is not finishing until the service is not finishing. – Darem Jun 04 '19 at 10:42
  • 1
    @Darem No they are not. Execution is blocked until you return Task. But If you use async await, you should be safe. [Link](https://github.com/aspnet/AspNetCore/issues/3868#issuecomment-435196394) And another [SO](https://stackoverflow.com/questions/52826483/backgroundservice-tasks-not-running-on-separate-threads-in-net-core-generic-hos). – Makla Jun 04 '19 at 11:12
  • 1
    Can you pass arguments into `ExecuteAsync` like a database ID? – Jess Apr 22 '20 at 18:57
23

Is the following a correct pattern to implement long running background work in Asp.Net Core?

Yes, this is the basic approach to start long-running work on ASP.NET Core. You should certainly not use Task.Run/StartNew/LongRunning - that approach has always been wrong.

Note that your long-running work may be shut down at any time, and that's normal. If you need a more reliable solution, then you should have a separate background system outside of ASP.NET (e.g., Azure functions / AWS lambdas). There are also libraries like Hangfire that give you some reliability but have their own drawbacks.

Update: I've written a blog series on how to implement the more reliable approach, using a durable queue with a separate background system. In my experience, most developers need the more reliable approach because they don't want their long-running work to be lost.

Stephen Cleary
  • 437,863
  • 77
  • 675
  • 810
  • Thanks Stephen - my doubts around this approach was exactly about what you mentioned "work may be shutdown at anytime". Assuming I don't let exceptions out of the async method, when would the runtime shutdown the async work? – ubi Jul 10 '17 at 22:32
  • 3
    @ubi: ASP.NET recycles occasionally just to keep things clean. Also the usual reasons: OS updates, cloud orchestration moving, power outages, etc. – Stephen Cleary Jul 10 '17 at 23:50
  • Thanks again. Can you explain ASP.NET *Core* recycling a bit more? I haven't read about it anywhere else (except IIS app pool recycle). I'm running this as a console app. Does the runtime restart the app or the request pipeline (signal `ApplicationStopping/Stopped` and call the `Configure` methods in `Startup` all over again or something like that)? – ubi Jul 11 '17 at 13:18
  • 1
    @ubi: Ah, since you're self-hosting, it won't recycle. But the other reasons still stand. – Stephen Cleary Jul 11 '17 at 14:17
  • 1
    @StephenCleary I'm confused, doesn't any form of restart simply run the whole application code again? In that case a background task initiated via `Task.Run` will just start again? Or did you actually mean that with `Task.Run` we can't have a *graceful* shutdown of the background task? But then in case of power outage (and various other scenarios) this doesn't seem to be possible as well. So we have to be prepared for it anyway. – freakish Jul 09 '19 at 11:50
  • @freakish: The main difference between application lifetime hooks and `Task.Run` for this scenario is that ASP.NET is aware of the background task with the hook approach. This means it knows it's "busy" even when there are no requests. This allows graceful shutdown and also informs different "monitors" to behave more nicely - consider rolling upgrades, or whole-site shutdowns in a shared hosting scenario. – Stephen Cleary Jul 09 '19 at 13:02
  • @StephenCleary thank you. Let me ask you about my particular scenario: I'm dealing with WebSockets. And I have kind of request/response protocol on top of it. Now in order to process those requests in parallel once I read raw bytes I process them in background via `Task.Run`. So generally they should not be long running tasks. And the connection is always alive so I suppose that ASP.NET will understand that it is still busy. Those background tasks can die when the connection dies though. Would you use `Task.Run` or some complicated queueing to a worker hooked to the application startup? – freakish Jul 09 '19 at 13:26
  • As long as you're OK with `Task.Run` tasks going away when there's no more connection, then I think what you have is fine. I'm not sure if `Task.Run` is strictly *necessary* here; it sounds like you're trying to do what ASP.NET already does. I'd look into SignalR if possible. – Stephen Cleary Jul 09 '19 at 14:02