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
216 views
in Technique[技术] by (71.8m points)

c# - Run a background task from a controller action in ASP.NET Core

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.

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);
    }
}
See Question&Answers more detail:os

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

1 Reply

0 votes
by (71.8m points)

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

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

...