BackgroundJob.ContinueWith on a RecurringJob

RecurringJob.AddOrUpdate("parent_id", ParentFunc, Cron.Daily(0, 0)); BackgroundJob.ContinueWith("parent_id", ChildFunc);

I can’t get this code to work. Is there anyway I can run a job every time, after another recurring job completes? I know we can do the below but then what is the point of having continue with if we always combine the two together?

RecurringJob.AddOrUpdate("parent_id", () => { ParentFunc(); ChildFunc(); }, Cron.Daily(0, 0));

Actually, you can’t do:

RecurringJob.AddOrUpdate("parent_id", () => { ParentFunc(); ChildFunc(); }, Cron.Daily(0, 0));

you will get the error: A lambda expression with a statement body cannot be converted into an expression tree.

I am looking for a way to continue with after a recurring function as well. Even if I could do the above method it wouldn’t work in my case because the function I want to continue with is a function that checks the status of the previous task to be reported back to the UI. If I were to check it inside the original task then the status would always be “processing”. Has anybody found a way to accomplish this yet?

The error I get when I do a continue with on a recurring task is: Input was not in a correct format

The error is coming from a database lookup where it tries to convert the jobID into an integer. Scheduled jobs use jobID’s that are integers as strings, while recurring jobs use jobIDs that are alphanumeric and they generate new (integer) job IDs for each iteration of the task as it runs.

If no suggestions for continue with on a recurring task does anybody have a suggestion for running a function whenever any task completes? For now I am adding a BackgroundJob.Schedule to the end of every recurring function that schedules a job at DateTime.Now.AddSeconds(30) to update the UI with the status of the current job.

You schedule a recurring job:

RecurringJob.AddOrUpdate("parent_id", () => ParentFunc(null), Cron.Daily(0, 0));

Then you can schedule continuations from the recurring job method itself, e.g.

public static void ParentFunc(PerformContext context)
{
    // do all prerequisites before running child job(s),
    // then enqueue one or more continuations:
    BackgroundJob.ContinueWith(context.BackgroundJob.Id, () => ChildFunc(args));
}

The point of having continuations, compared to running both functions in sequence, is:

  1. Continuations may run in parallel, e.g. parent job produces a list of clients to email newsletters to, and child jobs send those emails in parallel.
  2. If a continuation fails, it doesn’t affect the parent job or the other continuations. Only the failed continuation will be retried and not the entire job. So all the other clients won’t receive duplicate emails if send fails for one of them.

But this only works if ChildFunc is some static function. A much more useful use case would be to pass the child function to the parent function at runtime. But hangfire can not use delegates as function arguments which makes much of hangfire really useless imo cause nobody wants to have a bunch of static functions but provide functions and objects at runtime.

How can I pass the child function to the parent function so that I can call ContinueWith inside the recurring job execution? Is this even possible? I try this for ages now and in the end it all comes down to “hangfire doesn’t support delegates as parameters”.

Yeah the fact that Hangfire is all static classes and methods is really annoying. I am against the overuse of static and singletons in general.
Being able to instantiate a Hangfire client in a using would be much better.

Why does anyone like the singleton pattern? It is just static with a new name.
Here is a nice video for those that wanna learn about why not to overuse static functions and singleton pattern: https://www.youtube.com/watch?v=-FRm3VPhseI

We solved this using another job that just enqueues the parent job and continuation.

public class RecurringBackgroundJobs {
  public void SetRecurringJobWithContinuation(string jobName, string cronExpression) {
    RecurringJob.AddOrUpdate
        ( jobName
        , () => EnqueueJobWithContinuation()
        , cronExpression );
  }

  public void EnqueueJobWithContinuation() {
    BackgroundJob.ContinueJobWith<ISomeOtherInterface>
        ( BackgroundJob.Enqueue<ISomeInterface>((someInstance) => someInstance.SomeFunction())
        , (someOtherInstance) => someOtherInstance.SomeOtherFunction()
        , JobContinuationOptions.OnlyOnSucceededState );
  }
}