Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
144 views
in Technique[技术] by (71.8m points)

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

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 background, but the controller should go ahead and return before that process is done. I don't want the consumers of the service to have to wait for this job to finish.

I have seen all of the posts about IHostedService and BackgroundService, but none of them seem to be what I want. Also, all these examples show you how to set things up, but not how to actually call it, or I am not understanding some of it.

I tried these, but when you register an IHostedService in Startup, it runs immediately at that point in time. This is not what I want. I don't want to run the task at startup, I want to be able to call it from a controller when it needs to. Also, I may have several different ones, so just registering services.AddHostedService() won't work because I might have a MyServiceB and MyServiceC, so how do I get the right one from the controller (I can't just inject IHostedService)?

Ultimately, everything I have seen has been a huge, convoluted mess of code for something that seems like it should be such a simple thing to do. What am I missing?

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

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<string> BackgroundTasks {get; set;} = new ConcurrentQueue<string>();
}

public class MyController : ControllerBase{
  private readonly BackgroundJobs _backgroundJobs;
  public MyController(BackgroundJobs backgroundJobs) {
    _backgroundJobs = backgroundJobs;
  }

  public async Task<ActionResult> 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<ActionResult> 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


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...