Reputation: 38737
Simply put, BatchJob.Cancel
doesn't cancel already-running child jobs, and they instead run to completion, but cancelling the child job by id (when it's running) using BackgroundJob.Delete
results in the child job being cancelled. Injecting either JobCancellationToken
or CancellationToken
into the method yields the same problem.
public class BatchTest
{
public async Task Method1(CancellationToken ct)
{
Console.WriteLine("Started 1");
try
{
// meaningless work to simulate some long-running task
await Task.Delay(TimeSpan.FromSeconds(30), ct);
}
catch (OperationCanceledException)
{
Console.WriteLine("Cancelled 1");
return;
}
Console.WriteLine("Completed 1");
}
}
Client code:
string? childId = null;
var batchJobId = BatchJob.StartNew(act =>
{
childId = act.Enqueue<BatchTest>(t => t.Method1(default));
});
Console.WriteLine("Scheduled");
// Uncomment to immediately cancel (batch children will correctly never start)
// BatchJob.Cancel(batchJobId);
// For the sake of this example, we'll use this code to
// detect when the child job actually starts. In the real
// code, I don't do/need to do this, but I do need the batch
// to be cancellable.
while (true)
{
var details = JobStorage.Current.GetMonitoringApi().JobDetails(childId);
if (details.History.Any(h => h.StateName == ProcessingState.StateName))
{
Console.WriteLine("Started");
break;
}
}
// Allow some time for the job to do some "work" and then cancel it
await Task.Delay(TimeSpan.FromSeconds(10));
BatchJob.Cancel(batchJobId);
//BackgroundJob.Delete(childId!);
Console.WriteLine("Cancelled batch");
If I cancel the batch job as above, Method1
will first output "Started 1"
and after 30s will output "Completed 1"
. It doesn't register that the cancellation has even occurred.
However, if I comment out BatchJob.Cancel(batchJobId);
and uncomment BackgroundJob.Delete(childId!);
, the method correctly prints "Started 1"
followed by "Cancelled 1"
.
And, of course, if I cancel the batch before it gets picked up for execution, it simply never runs. That part works correctly, at least.
From D.A. I feel that I might be misunderstanding what batch cancellation is for, though D.A. even suggests that it sets the batch to deleted, so surely that should also handle the child jobs?
I can't find any way of getting the child job IDs by batch ID, so it's not like I have a means to manually cancel them all without first saving them all in my database.
How can I make batch cancellation work as I expected it to? I'm open to creating an extension method on IBackgroundJob
if it allows me to cancel all of the child jobs.
Upvotes: 2
Views: 426
Reputation: 2054
The Hangfire Server uses multiple threads to perform background jobs. Meaning it can process a background job per thread within the Hangfire server. This allows you to execute background jobs concurrently. By default, the number of threads it uses is 5 per Processor Count. For the BatchJob it uses thread pool. In a thread pool you can't cancel a worker thread. Once put in there you will have to wait until its completion or implement a custom CancellationToken mechanism to stop them. The BatchJob.Cancel(batchId) just set the Deleted state to prevent the other scheduled working threads to start.
Upvotes: 0