Ich entwickle eine Webanwendung mit einer REST Api unter Verwendung von C # mit asp.net core 2.0
Was ich erreichen möchte, ist, wenn der Client eine Anforderung an einen Endpunkt sendet. Ich führe eine Hintergrundaufgabe aus, die vom Clientanforderungskontext getrennt ist und beendet wird, wenn die Aufgabe erfolgreich gestartet wurde.
Ich weiß, dass es HostedService gibt, aber das Problem ist, dass HostedService beim Starten des Servers gestartet wird und es meines Wissens keine Möglichkeit gibt, HostedService manuell von einem Controller aus zu starten.
Hier ist ein einfacher Code, der die Frage demonstriert.
[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);
}
}
Sie können weiterhin IHostedService
als Basis für Hintergrundaufgaben in Kombination mit BlockingCollection
verwenden.
Erstellen Sie einen Wrapper für BlockingCollection
, damit Sie ihn als Singleton einfügen können.
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);
}
Bei der Implementierung von IHostedService
"lauschen" Sie den Aufgaben und wenn Aufgaben "ankommen", führen Sie sie aus.BlockingCollection
stoppt die Ausführung, wenn die Sammlung leer ist - Ihre while
-Schleife verbraucht also keine Prozessorzeit..Take
Methode akzeptiert cancellationToken
als Argument. Mit dem Token können Sie das Warten auf die nächste Aufgabe abbrechen, wenn die Anwendung stoppt.
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));
}
}
Und im Controller fügen Sie einfach die Aufgabe hinzu, die Sie unserer Sammlung hinzufügen möchten
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();
}
}
Der Wrapper zum Blockieren der Erfassung sollte für die Abhängigkeitsinjektion als Singleton registriert werden
services.AddSingleton<TasksToRun, TasksToRun>();
Registrieren Sie den Hintergrunddienst
services.AddHostedService<BackgroundService>();
Microsoft hat dasselbe unter https://docs.Microsoft.com/en-us/aspnet/core/fundamentals/Host/hosted-services?view=aspnetcore-2.1 dokumentiert
Hierfür wird BackgroundTaskQueue verwendet, das die vom Controller zugewiesene Arbeit erhält, und die Arbeit wird von QueueHostedService ausgeführt, der von BackgroundService abgeleitet ist.