What is the simplest way to run a single background task from a controller in .NET Core?

前端 未结 2 899
眼角桃花
眼角桃花 2021-01-14 04:54

I have an ASP.NET Core web app, with WebAPI controllers. All I am trying to do is, in some of the controllers, be able to kick off a process that would run in the backgroun

2条回答
  •  天涯浪人
    2021-01-14 05:27

    You have the following options:

    1. IHostedService classes can be long running methods that run in the background for the lifetime of your app. In order to make them to handle some sort of background task, you need to implement some sort of "global" queue system in your app for the controllers to store the data/events. This queue system can be as simple as a Singleton class with a ConcurrentQueue that you pass in to your controller, or something like an IDistributedCache or more complex external pub/sub systems. Then you can just poll the queue in your IHostedService and run certain operations based on it. Here is a microsoft example of IHostedService implementation for handling queues https://docs.microsoft.com/en-us/aspnet/core/fundamentals/host/hosted-services?view=aspnetcore-3.1&tabs=visual-studio#queued-background-tasks Note that the Singleton class approach can cause issues in multi-server environments. Example implementation of the Singleton approach can be like:
    // Needs to be registered as a Singleton in your Startup.cs
    public class BackgroundJobs {
      public ConcurrentQueue BackgroundTasks {get; set;} = new ConcurrentQueue();
    }
    
    public class MyController : ControllerBase{
      private readonly BackgroundJobs _backgroundJobs;
      public MyController(BackgroundJobs backgroundJobs) {
        _backgroundJobs = backgroundJobs;
      }
    
      public async Task FireAndForgetEndPoint(){
        _backgroundJobs.BackgroundTasks.Enqueue("SomeJobIdentifier");
      }
    }
    
    public class MyBackgroundService : IHostedService {
      private readonly BackgroundJobs _backgroundJobs;
      public MyBackgroundService(BackgroundJobs backgroundJobs)
      {
        _backgroundJobs = backgroundJobs;
      }
    
      public void StartAsync(CancellationToken ct)
      {
        while(!ct.IsCancellationRequested)
        {
          if(_backgroundJobs.BackgroundTasks.TryDequeue(out var jobId))
          {
            // Code to do long running operation
          }
        Task.Delay(TimeSpan.FromSeconds(1)); // You really don't want an infinite loop here without having any sort of delays.
        }
      }
    }
    
    1. Create a method that returns a Task, pass in a IServiceProvider to that method and create a new Scope in there to make sure ASP.NET would not kill the task when the controller Action completes. Something like
    IServiceProvider _serviceProvider;
    
    public async Task FireAndForgetEndPoint()
    {
      // Do stuff
      _ = FireAndForgetOperation(_serviceProvider);
      Return Ok();
    }
    
    public async Task FireAndForgetOperation(IServiceProvider serviceProvider)
    {
      using (var scope = _serviceProvider.CreateScope()){
        await Task.Delay(1000);
        //... Long running tasks
      }
    }
    

    Update: Here is the Microsoft example of doing something similar: https://docs.microsoft.com/en-us/aspnet/core/performance/performance-best-practices?view=aspnetcore-3.1#do-not-capture-services-injected-into-the-controllers-on-background-threads

提交回复
热议问题