Previous post covered almost all functions in BackgroundJob class except for ContinueWith functions family. So here we go :)

The fact that it has the same name as a System.Threading.Tasks.Task function is not without a coincidence, or at least I hope so. This method allows chaining jobs where one will be enqueued when the previous finishes. To repeat - the job won’t be executed, but enqueued. So it will go at the end of the queue. So lets look at the function and overrides signature:

static string ContinueWith(string parentId, Expression<Action> methodCall);
static string ContinueWith(string parentId, Expression<Action> methodCall, JobContinuationOptions options);
static string ContinueWith(string parentId, Expression<Func<Task>> methodCall, JobContinuationOptions options = JobContinuationOptions.OnlyOnSucceededState);    
static string ContinueWith<T>(string parentId, Expression<Action<T>> methodCall);
static string ContinueWith<T>(string parentId, Expression<Action<T>> methodCall, JobContinuationOptions options);
static string ContinueWith<T>(string parentId, Expression<Func<T, Task>> methodCall, JobContinuationOptions options = JobContinuationOptions.OnlyOnSucceededState);

The API enables to fire multiple methods when one finishes, but not doing the opposite. This scenario was recently covered by Batches, but it is a topic for a separate post, and they are available only in the paid version. So let’s have a look at the function and their overrides:

So let’s look at what differentiates the overrides:

  • generic vs. nongeneric
  • synchronous vs. asynchronous. This time, they are not exactly equal because methods differ in the fact that there are two versions for the synchronous override. One with JobContinuationOptions and one without. The asynchronous only has one with default initialization. This was probably done to keep backward compatibility (reflection, assembly binding), and since async support was implemented after ContinueWith.

We looked at the differences, let’s look at the similarities:

  • parentId - remember the unique job id returned by all the functions from enqueue part of the API? This is one of the cases when it comes in handy.
  • JobContinuationOptions - this is an enum and has two options:
    • OnAnyFinishedState - the default one, meaning the job will execute regardless of whatever parent finishes with success, or throws an error.
    • OnlyOnSucceededState - the job will fire only when parent succeeded (didn’t throw any exception, or didn’t timeout)
  • the return value is of curse a unique job id, so we can chain many jobs.

Continuation can introduce some edge cases, here are those came into my mind:

Will continuations be executed once more if we enqueue the parent job that ended successfully?

The short answer is no. Continuation jobs will be enqueued only if they are in AwaitingState (special state for continuation jobs in Hangfire state machine).

What happens to continuations scheduled to execute on success when the parent fails and after retry succeeded?

To put it simply: is it possible for the parent job to finish and not enqueue continuations? No. They are done in one transaction.

What if we continue on a job that already executed?

Continuations will be enqueued immediately.

Why should I use ContinueWith if I can enqueue continuation job at the end of parent job?

To keep the code clean. Hangfire is just a way to execute functions, and in most cases, there is no need for it know, is it running from Hangfire or from a web request. It is just a matter of keeping code clean.

Can a parent job pass parameters to continuations?

No. If that’s the case I would recommend TPL Dataflow or some agent system like Akka.NET or Orleans. Another option is to enqueue the child job at the end of parent job, but this can get messy.

Hi, I'm Szymon Warda. I write code, design IT systems, write this blog, tweet and speak at conferences. If You want to know more go here, or follow me:

What is the problem with key-value databases and how wide column stores solve it.

Key-value databases are very powerful, but there are use cases where its value as a blob gets in the way. Continue reading