从 ASP.NET Core 中的控制器操作运行后台任务 [英] Run a background task from a controller action in ASP.NET Core

查看:29
本文介绍了从 ASP.NET Core 中的控制器操作运行后台任务的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在使用 C# 和 ASP.NET Core 2.0 开发带有 REST API 的 Web 应用程序.

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 background task separated from the client request context which will be ended if the task started successfully.

我知道有 HostedService 但问题是 HostedService 在服务器启动时启动,据我所知没有办法启动 HostedService 从控制器手动.

I know there is HostedService but the problem is that the HostedService starts when the server starts, and as far as I know there is no way to start the HostedService manually from a controller.

这是一个演示问题的简单代码.

Here is a simple code that demonstrates the question.

[Authorize(AuthenticationSchemes = "UsersScheme")]
public class UsersController : Controller
{
    [HttpPost]
    public async Task<JsonResult> StartJob([FromForm] string UserId, [FromServices] IBackgroundJobService backgroundService)
    {
        // check user account
        (bool isStarted, string data) result = backgroundService.Start();

        return JsonResult(result);
    }
}

推荐答案

您仍然可以使用 IHostedService 作为后台任务的基础,并结合 BlockingCollection.

You still can use IHostedService as base for background tasks in combination with BlockingCollection.

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);
}

然后在 IHostedService 的实现中监听"任务,当任务到达"时执行它.
如果集合为空,BlockingCollection 将停止执行 - 因此您的 while 循环不会消耗处理器时间.
.Take 方法接受 cancellationToken 作为参数.使用令牌,您可以在应用程序停止时取消等待"下一个任务.

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>();

注册后台服务

services.AddHostedService<BackgroundService>();

这篇关于从 ASP.NET Core 中的控制器操作运行后台任务的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆