BackgroundJob.Schedule does not run on specific queue

We are using Hangfire in a multi-tenant environment and are trying to separate jobs into queues per tenant, but have run into an issue with Schedule.

We have created a custom dynamic queue attribute that sets the queue based on a current application setting. This works perfectly for a regular Enqueue, but as far as I can see BackgroundJob.Schedule runs on a random queue instead of the queue of the scheduled function. No queue state seems to be saved for a scheduled job in the database.

Since any server can pick up a scheduled job it gets enqueued in the queue of whatever server picked up the job. Do we have any way at all to work around this, or do we need hangfire to support this feature for our use-case to be possible?

1 Like

We have found a possible solution using JobFilterAttribute by saving the queue in a table during OnStateElection in the ScheduledState, and then loading it from db during EnqueuedState.

I have this exact problem.

I tried a number of things and the simplest approach I could come up with was to simply wrap the job that was getting scheduled in another job that would then dispatch the original job at the appropriate time. Calling BackgroundJob.Enqueue does respect the queue if set via the Queue attribute, but Schedule does not for whatever reason. My wrapper job then sits in the default queue, waiting for its scheduled time to arrive, at which point it will get executed and the original job gets enqueued directly, at which point it will go into the correct queue by virtue of the attribute being present. My setup involves messages getting processed by handlers, so my wrapper job has two inputs - the message and the type of handler to execute, and then it performs a IBackgroundJobClient.Create(methodCall, state) based on that information.

@ecs, Could you please share your solution, so we can try it on our environment?

Thank you!

The following gist contains the attribute class. You have to handle storage yourself to make it work.

1 Like

This solution also works when you need to schedule task into specific queue with IBackgroundJobClient.Schedule:

…
jobId = _backgroundJobClient.Schedule(task => task.DoWork(corrId, null), TimeSpan.FromSeconds(10));
var taskState = new EnqueuedState(“test-queue”);
_backgroundJobClient.ChangeState(jobId, taskState);

Just wanted to add incase anyone see’s this, Stepan_Fomenko’s solution does not work as expected. If you change the queue, the task no longer runs on delay and instead runs immediately. Therefore changing queue after scheduling isn’t really doing what I need.

I need to delay a job but also put it in a specific queue. Not sure this is possible.

@Foomaniac - hi - it’s been three years but I’m still using the setup I described previously in this thread to do as you’re describing. It requires some wrappers around the core Hangfire abstractions, but it has served us well across a number of systems now. The principle remains the same - you wrap the job that you want to delay in another job that gets scheduled in the default queue, and then once that scheduled job fires all it does internally is immediately enqueue the original job for execution in the intended queue:

var backgroundJobId = _backgroundJobClient.Create(() => yourActualJob(), new EnqueuedState(queue));

Here I’m using EnqueuedState to have complete control over the queue the job actually winds up in - so whether you need to dynamically determine the queue as you would in a multi-tenant application or you simply have a static registry of job types to queue names you have control over where the job will wind up.

1 Like

this solution works, thanks

in our case, we know the queue in the compile time, so we can easily using a attribute like

public class UseQueueAttribute : JobFilterAttribute, IElectStateFilter
{
private readonly string _name;

    public UseQueueAttribute(string name)
    {
        _name = name;
    }

    public void OnStateElection(ElectStateContext context)
    {
        if (context.CandidateState is EnqueuedState e)
        {
            e.Queue = _name;
        }
    }
}

and then you can apply the attribute in your class or interface,

public interface ISomeInterface
{
[UseQueue(“test”)]
Task RunAsync()
}

here you will need to apply to your interface if your service is resolved as interface