Run a background task from a controller action in asp.net core 2

后端 未结 3 662
一向
一向 2020-12-23 09:16

I am developing a web application with a REST Api using C# with asp.net core 2.0

What I want to achieve is when the client send a request to an endpoint I will run a

相关标签:
3条回答
  • You still can use IHostedService as base for background tasks in combination with BlockingCollection.

    Create wrapper for BlockingCollection so you can inject it as singleton.

    public class TasksToRun
    {
        private readonly BlockingCollection<TaskSettings> _tasks;
    
        public TasksToRun() => _tasks = new BlockingCollection<TaskSettings>();
    
        public void Enqueue(TaskSettings settings) => _tasks.Add(settings);
    
        public TaskSettings Dequeue(CancellationToken token) => _tasks.Take(token);
    }
    

    Then in implementation of IHostedService "listen" for tasks and when tasks "arrive" execute it.
    BlockingCollection will stop execution if collection is empty - so your while loop will not consume processor time.
    .Take method accept cancellationToken as argument. With token you can cancel "waiting" for next task when application stops.

    public class BackgroundService : IHostedService
    {
        private readonly TasksToRun _tasks;
    
        private CancellationTokenSource _tokenSource;
    
        private Task _currentTask;
    
        public BackgroundService(TasksToRun tasks) => _tasks = tasks;
    
        public async Task StartAsync(CancellationToken cancellationToken)
        {
            _tokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
            while (cancellationToken.IsCancellationRequested == false)
            {
                try
                {
                    var taskToRun = _tasks.Dequeue(_tokenSource.Token);
    
                    // We need to save executable task, 
                    // so we can gratefully wait for it's completion in Stop method
                    _currentTask = ExecuteTask(taskToRun);               
                    await _currentTask;
                }
                catch (OperationCanceledException)
                {
                    // execution cancelled
                }
            }
        }
    
        public async Task StopAsync(CancellationToken cancellationToken)
        {
            _tokenSource.Cancel(); // cancel "waiting" for task in blocking collection
    
            if (_currentTask == null) return;
    
            // wait when _currentTask is complete
            await Task.WhenAny(_currentTask, Task.Delay(-1, cancellationToken));
        }
    }
    

    And in the controller you simply add task you want to run to our collection

    public class JobController : Controller
    {
        private readonly TasksToRun _tasks;
    
        public JobController(TasksToRun tasks) => _tasks = tasks;
    
        public IActionResult PostJob()
        {
            var settings = CreateTaskSettings();
    
            _tasks.Enqueue(settings);
    
            return Ok();
        }
    }
    

    Wrapper for blocking collection should be registered for dependency injection as singleton

    services.AddSingleton<TasksToRun, TasksToRun>();
    

    Register background service

    services.AddHostedService<BackgroundService>();
    
    0 讨论(0)
  • 2020-12-23 10:03

    If you are trying to create a fire & forget task like logging to db and don't want to lose dbcontext object, I have an easy way for you to implement and answered here in detail.

    0 讨论(0)
  • 2020-12-23 10:16

    Microsoft has documented the same at https://docs.microsoft.com/en-us/aspnet/core/fundamentals/host/hosted-services?view=aspnetcore-2.1

    It accomplishes using BackgroundTaskQueue, which gets work assigned from Controller and the work is performed by QueueHostedService which derives from BackgroundService.

    0 讨论(0)
提交回复
热议问题