web-dev-qa-db-ja.com

asp.net core 2のコントローラーアクションからバックグラウンドタスクを実行する

REST asp.net core 2.0でC#を使用してAPIを使用してWebアプリケーションを開発しています

私が達成したいのは、クライアントがエンドポイントにリクエストを送信するとき、タスクが正常に開始された場合に終了するクライアントリクエストコンテキストから分離されたバックグラウンドタスクを実行することです。

HostedServiceがあることは知っていますが、問題は、サーバーの起動時にHostedServiceが起動することであり、コントローラーからHostedServiceを手動で起動する方法がないことを知っています。

質問を示す簡単なコードを次に示します。

[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);
    }
}
15
Waxren

IHostedServiceと組み合わせて、BlockingCollectionをバックグラウンドタスクのベースとして使用できます。

BlockingCollectionのラッパーを作成して、シングルトンとして注入できるようにします。

public class TasksToRun
{
    private readonly BlockingCollection<TaskSettings> _tasks;

    public TasksToRun() => _tasks = new BlockingCollection<TaskSettings>();

    public Enqueue(TaskSettings settings) => _tasks.Add(settings);

    public Dequeue(CancellationToken token) => _tasks.Take(token);
}

次に、タスクのIHostedService "listen"の実装で、タスクがそれを「実行」するとき。
BlockingCollectionは、コレクションが空の場合、実行を停止します。したがって、whileループはプロセッサー時間を消費しません。
.Takeメソッドは、cancellationTokenを引数として受け入れます。トークンを使用すると、アプリケーションが停止したときに次のタスクの「待機」をキャンセルできます。

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

そしてコントローラーで、実行したいタスクをコレクションに追加するだけです

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

コレクションをブロックするためのラッパーは、シングルトンとして依存性注入用に登録する必要があります

services.AddSingleton<TasksToRun, TasksToRun>();

バックグラウンドサービスの登録

services.AddHostedService<BackgroundService>();
21
Fabio

マイクロソフトは https://docs.Microsoft.com/en-us/aspnet/core/fundamentals/Host/hosted-services?view=aspnetcore-2.1 で同じことを文書化しています

これは、コントローラから割り当てられた作業を取得するBackgroundTaskQueueを使用して実行され、作業はBackgroundServiceから派生したQueueHostedServiceによって実行されます。

3
skjagini