Skip to main content

Hangfire: setup and usage

How to setup Hangfire, a job scheduler for .NET

Hangfire is a job scheduler for .NET and .NET Core that lets you run jobs in the background. It features various different job types:

  • Fire-and-forget jobs are executed only once, shortly after creation.
  • Delayed jobs are very similar to fire-and-forget jobs but they wait for a specified amount of time before running.
  • Continuation jobs, which run after their parent job has finished.
  • Recurring jobs, perhaps the most interesting of all, run repeatedly on an interval. If you like Unix’s cron you’ll feel right at home, as this kind of job lets you specify the job interval as a cron expression. If you have never used this I recommend crontab.guru, it has a live expression editor, as well as some examples to get you started.

Hangfire has two more job types ‒batches and batch continuations‒, but they require a pro license so we won’t be talking about them.

This library also comes with an integrated dashboard for ASP.NET Core that lets you see what jobs are running, have ran and will run, as well as manually start any scheduled jobs.

Hangfire's dashboard

Recurring jobs dashboard

Installing Hangfire

Hangfire is compatible with .NET Framework 4.5 or later, .NET Core 1.0 or later and any platform compatible with .NET Standard 1.3, although this guide will cover installation on ASP.NET Core 2.2. Scroll past this section if you’re on a different platform.

To start, you can run the following command in the package manager console (ToolsNuGet Package ManagerPackage Manager Console in Visual Studio):

PM> Install-Package Hangfire.AspNetCore

Or you can install the Hangfire.AspNetCore package through the package manager interface. There are a few Hangfire packages, so you should choose whichever fits your platform:

Setting up Hangfire

First of all you’ll need to add the following lines to your ConfigureServices and Configure methods:

public void ConfigureServices(IServiceCollection services)
{
    services.AddHangfire(conf => conf
        .SetDataCompatibilityLevel(CompatibilityLevel.Version_170)
        .UseSimpleAssemblyNameTypeSerializer()
        .UseRecommendedSerializerSettings());
    services.AddHangfireServer();
}

public void Configure(IApplicationBuilder app)
{
    app.UseHangfireServer();
    app.UseHangfireDashboard(); //Dashboard on /hangfire
}

Setting up storage

Hangfire doesn’t store any job data in memory by default, so you need to provide a storage medium (MySQL, SQL Server, etc).

MySQL storage

The MySQL functionality is provided by the Hangfire.MySql.Core NuGet package, so you’ll need to install that. When you’re done, you must add a line to your Hangfire config:

using Hangfire.MySql.Core;
...
services.AddHangfire(conf => conf
    .SetDataCompatibilityLevel(CompatibilityLevel.Version_170)
    .UseSimpleAssemblyNameTypeSerializer()
    .UseRecommendedSerializerSettings()
    .UseStorage(new MySqlStorage(Configuration.GetConnectionString("Default")))); //Make sure to replace this if needed to fit your configuration setup

And that’s really it! Next time you fire your application up a bunch of Hangfire related tables will be created in your database and you’ll be ready to schedule some jobs.

In-memory storage

If you are sure you’re only ever going to run short-lived jobs that can be interrupted, you may instead want to go for memory storage. This means that you won’t need any databases, but it also means that whenever the application is shut down all scheduled jobs will be lost and not run.
The setup for this is very similar to the one for MySQL storage. First you must install the Hangfire.MemoryStorage.Core package, then add a line to your Hangfire config:

using Hangfire.MemoryStorage;
...
services.AddHangfire(conf => conf
    .SetDataCompatibilityLevel(CompatibilityLevel.Version_170)
    .UseSimpleAssemblyNameTypeSerializer()
    .UseRecommendedSerializerSettings()
    .UseStorage(new MemoryStorage()));

Running jobs

Once you’ve got everything set up, you’re ready to run some jobs!
We’ll be using this method as our background worker throughout the examples:

public void JobMethod()
{
    Console.WriteLine("Job ran! " + DateTime.Now);
}

Jobs can call any methods: public or private, static or not, and they can be anywhere. They can also be registered from anywhere, but on ASP.NET Core I recommend to add job registration methods at the end of the Configure method.

Fire-and-forget jobs

The simplest kind of job, you only need to provide the method it’s got to execute:

BackgroundJob.Enqueue(() => JobMethod());

This will execute the JobMethod function as soon as possible.

One thing to keep in mind for all job types is that the methodCall parameter isn’t a delegate but rather a LINQ Expression, meaning that you’ll have to use a lambda construct even when calling a simple method like above.

Delayed jobs

This kind of job can accept either a DateTimeOffset with the date and time at which to run the job, or a TimeSpan with a delay relative to the current time:

BackgroundJob.Schedule(() => JobMethod(), DateTimeOffset.Now.AddDays(1));
//Equivalent to
BackgroundJob.Schedule(() => JobMethod(), TimeSpan.FromDays(1));

Continuation jobs

These will run after a parent job has finished execution. You’ll need the job ID returned by either of the previous job types.

string jobId = BackgroundJob.Enqueue(...);

BackgroundJob.ContinueJobWith(jobId, () => JobMethod());

Recurring jobs

Recurring jobs are special. They can have a custom job ID assigned to them in order to be able to identify them after restarting the application, which allows for changing a job’s interval by simply changing the cron expression in the code, and tracking its execution. For example:

RecurringJob.AddOrUpdate("my_cron_job", () => JobMethod(), "*/10 * * * *"); //Run every 10 minutes

These jobs will only be executed when the current time matches the cron expression, although keep in mind it may take up to 15 seconds for the job to be ran.
You can also optionally specify the timezone the job is running at, which will be UTC by default.

ASP.NET Core job integration

The need to use ASP.NET Core’s own DI framework inside a job will most likely come up, in which case you can use the same methods described above with a generic parameter for a class that will be populated from DI. For example:

//CleanupJob.cs
public class CleanupJob
{
    private readonly ICleanerService CleanerService;

    public CleanupJob(ICleanerService cleanerService)
    {
        this.CleanerService = cleanerService;
    }

    public void Execute()
    {
        this.CleanerService.Cleanup();
    }
}

//Startup.cs
public void Configure()
{
   BackgroundJob.Enqueue<CleanupJob>(o => o.Execute());
}

When running recurring jobs this way you will want to use a IServiceScopeFactory inside the job class, especially if you’re accessing your DbContext:

//CleanupJob.cs
public class CleanupJob
{
        private readonly IServiceScopeFactory Factory;

        public UploadPruneJob(IServiceScopeFactory factory)
        {
            this.Factory = factory;
        }

        public void Execute()
        {
            using (var scope = Factory.CreateScope())
            {
                scope.ServiceProvider.GetRequiredService<ICleanerService>().Cleanup();
            }
        }
}

Conclusion

Hangfire is a really powerful and easy to learn library that can tremendously help you to not have to manually schedule jobs like database maintenance with pesky Tasks or Timers.

Comments

Popular posts from this blog

ConditionalWeakTable, what does it do?

C#'s ConditionalWeakTable, what does it do?C# has many lesser known features, some more useful than others, and one of them is the ConditionalWeakTable<TKey, TValue> (keep in mind that TKey and TValue must be reference types).
You can think of this type as a dictionary where the keys are weakly referenced, meaning that they won’t count when the GC checks if the object has to be collected. Additionally, when the keys do eventually get collected by the GC, that entry will get removed from the dictionary. This means that you can attach arbitrary objects to any object, allowing you to do something like this:publicstaticclassExtensions{privatestatic ConditionalWeakTable<object,dynamic> Table =newConditionalWeakTable<object,dynamic>();publicstaticdynamicData(thisobject obj){if(!Table.TryGetValue(obj,outvar dyn)) Table.Add(obj, dyn =newExpandoObject());return dyn;}}...var myObject ="hello"; myObject.Data().Foo ="bar"; Assert.AreEqual(myO…

C# to JS: LINQ Expressions and ExpressionVisitor

How to translate (some) C# to JavaScriptIf you’ve ever used something like EF Core you may have noticed that methods like LINQ’s Where take an Expression<Func<...>> as the first argument, instead of simply a Func<...>:
Expressions were introduced in .NET 3.5 along with the rest of LINQ and they represent the code tree that a lambda is composed of. For example, if you had a lambda like () => a + b, this is how the resulting expression tree would look like:
.NET 4.0 later introduced ExpressionVisitor, which lets you traverse the expression tree. Take this visitor for example:publicclassConsolePrinterVisitor: ExpressionVisitor {//This is just a helper method to get the Expression, normally you would already have it//so you'd just do ConsolePrinterVisitor.Visit(expression)publicvoidWriteLambda(Expression<Func<object>> expression){this.Visit(expression);}protectedoverride Expression VisitBinary(BinaryExpression node){ Console.Write("("…