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);
}
}
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>();
マイクロソフトは https://docs.Microsoft.com/en-us/aspnet/core/fundamentals/Host/hosted-services?view=aspnetcore-2.1 で同じことを文書化しています
これは、コントローラから割り当てられた作業を取得するBackgroundTaskQueueを使用して実行され、作業はBackgroundServiceから派生したQueueHostedServiceによって実行されます。