This release contains the most amazing update to the batches feature. Now you can create nested batches, attach jobs or batches to an existing batch, regardless of its current state, and lazily cancel running batches. Also, you can create job continuation for batch, and batch continuation for job. These feature allows you to build powerful workflows on top of Hangfire with ease.

Nested Batches

Batches now can consist of other batches, not only of background jobs. Outer batch is called as parent, inner batch is a child one (for continuations, it’s an antecedent/continuation relationship now). You can mix both batches and background jobs together in a single batch.

BatchJob.StartNew(parent =>
{
    parent.Enqueue(() => Console.WriteLine("First"));
    batch.StartNew(child => child.Enqueue(() => Console.WriteLine("Second")));
});

Multiple nesting levels are supported, so each child batch can, in turn, become a parent for another batch, allowing you to create very complex batch hierarchies.

BatchJob.StartNew(batch1 =>
{
    batch1.StartNew(batch2 =>
    {
        batch2.StartNew(batch3 => batch3.Enqueue(() => Console.WriteLine("Nested")));
    });
});

The whole hierarchy, including parent batch, all of its child batches and background jobs are created in a single transaction. So this feature not only allows you to see a group of related batches on a single dashboard page, but also create multiple batches atomically.

var antecedentId = BatchJob.StartNew(batch =>
{
    batch.StartNew(inner => inner.Enqueue(() => Console.WriteLine("First")));
    batch.StartNew(inner => inner.Enqueue(() => Console.WriteLine("Second")));
});

Parent batch is succeeded, if all of its background jobs and batches are succeeded. Parent batch is finished, if all of its batches and background jobs are in a final state. So you can create continuation for multiple batches, not just for a single one. Batch continuations also support the nesting feature.

BatchJob.AwaitBatch(antecedentId, continuation =>
{
    continuation.StartNew(inner => inner.Enqueue(() => Console.WriteLine("First")));
    continuation.StartNew(inner => inner.Enqueue(() => Console.WriteLine("Second")));
});

Batch Modification

This is another interesting feature that allows you to modify existing batches by attaching new background jobs and child batches to them. You can add background jobs in any states, as well as nested batches. If a modified batch has already been finished, it will be move to the started state back.

var batchId = BatchJob.StartNew(batch => batch.Enqueue(() => Console.WriteLine("First")));
BatchJob.Attach(batchId, batch => batch.Enqueue(() => Console.WriteLine("Second")));

This feature helps, if you want a list of records you want to process in parallel, and then execute a continuation. Previously you had to generate a very long chain of continuations, and it was very hard to debug them. Now you can create the structure, and modify a batch later.

var batchId = BatchJob.StartNew(batch => batch.Enqueue(() => ProcessHugeList(batch.Id, ListId)));
BatchJob.AwaitBatch(batchId, batch => batch.Enqueue(() => SendNotification(ListId)));
// ProcessHugeList
BatchJob.Attach(batchId, batch => 
{
    foreach (var record in records)
    {
        batch.Enqueue(() => ProcessRecord(ListId, record.Id)));
    }
});

Batches now can be created without any background jobs. Initially such an empty batches are considered as completed, and once some background jobs or child batches are added, they move a batch to the started state (or to another, depending on their state).

var batchId = BatchJob.StartNew(batch => {});
BatchJob.Attach(batchId, batch => batch.Enqueue(() => Console.WriteLine("Hello, world!")));

More Continuations

Did you try to continue batch by a background job? Now it’s possible without creating a batch that consist only of a single background job. Unfortunately we can’t add extension methods for static classes, so let’s create a client first.

var backgroundJob = new BackgroundJobClient();
var batchId = BatchJob.StartNew(/* ... */);

backgroundJob.AwaitBatch(batchId, () => Console.WriteLine("Continuation"));

You can use the new feature in other way, and create batch continuations for regular background jobs. So you are free to define workflows, where synchronous actions are continued by a group of parallel work, and then continue back to a synchronous method.

var jobId = BackgroundJob.Enqueue(() => Console.WriteLine("Antecedent"));
BatchJob.AwaitJob(jobId, batch => batch.Enqueue(() => Console.WriteLine("Continuation")));

Cancellation of a Batch

If you want to stop a batch with millions of background jobs from being executed, not a problem, you can now call the Cancel method, or click the corresponding button in dashboard.

var batchId = BatchJob.StartNew(/* a lot of jobs */);
BatchJob.Cancel(batchId);

This method does not iterate through all the jobs, it simply sets a property of a batch. When a background job is about to execute, job filter checks for a batch status, and move a job to the Deleted state, if a batch has cancelled.

.NET Standard

.NET Standard is supported since the Hangfire.Pro 1.5.0-alpha2 version. It’s a pity that I didn’t release it as 1.5.0 (with configurable expiration time), because wanted to add other features as well.

Configurable Expiration Time

No more methods that use the reflection. if you want to change the default batch expiration time (7 days by default), just pass a parameter to the UseBatches method. Please note that this setting affects all the batches. If you have an interesting use case for different expiration times, just tell me.

GlobalConfiguration.Configuration
    .UseXXX()
    .UseBatches(expirationTime: TimeSpan.FromHours(1));

Upgrading

Run the following code in the Package Manager Console window, or use the Manage NuGet Packages window in Visual Studio to upgrade to the newest version.

PM> Update-Package Hangfire.Pro

The only thing changed in Hangfire.Pro 2.0.0 is the type of some arguments in the IBatchJobClient interface and BatchJob class. ContinueWith methods became obsolete to be replaced with AwaitBatch for consistency with the new features. So, recompilation is required, but storage schema is the same.

Subscribe to monthly updates

Subscribe to receive monthly blog updates. Very low traffic, you are able to unsubscribe at any time.

Comments