Hangfire: Support to specify the queue name

Created on 30 Nov 2016  路  3Comments  路  Source: HangfireIO/Hangfire

when applying any operation except recurrent, we cannot specify the queue name.

Example:

BackgroundJob.Schedule(
    () => Console.WriteLine("Hello, world"),
    TimeSpan.FromDays(1));

Something like this would be great:

BackgroundJob.Schedule(
    () => Console.WriteLine("Hello, world"),
    TimeSpan.FromDays(1), 
    "application-1");

closest and related existing tickets -

https://github.com/HangfireIO/Hangfire/issues/228
https://github.com/HangfireIO/Hangfire/issues/406

Most helpful comment

this work-around looks to work (I have done the scheduled for now.)

Client
1) provide more data in the ScheduledState, put in a location (ie classlib/dll) which the client can access (does not matter much about the background server)

 public class ScheduledState : IState
    {
        public static readonly string StateName = "Scheduled";

        public ScheduledState(TimeSpan enqueueIn, string queue = "default")
            : this(DateTime.UtcNow.Add(enqueueIn), queue)
        {
        }

        [JsonConstructor]
        public ScheduledState(DateTime enqueueAt, string queue = "default")
        {
            EnqueueAt = enqueueAt;
            ScheduledAt = DateTime.UtcNow;
            Queue = queue;
        }

        public DateTime EnqueueAt { get; }
        public DateTime ScheduledAt { get; }
        public string Queue { get; }
        public string Name => StateName;
        public string Reason { get; set; }
        public bool IsFinal => false;
        public bool IgnoreJobLoadException => false;

        public Dictionary<string, string> SerializeData()
        {
            return new Dictionary<string, string>
            {
                { "Queue", Queue },
                { "EnqueueAt", JobHelper.SerializeDateTime(EnqueueAt) },
                { "ScheduledAt", JobHelper.SerializeDateTime(ScheduledAt) }
            };
        }  
    }

Server
2) add a filter (i chose to use an elect one)

public class UseQueueFromScheduledFilter : IJobFilter, IElectStateFilter
    {
        public void OnStateElection(ElectStateContext context)
        {

            var enqueuedState = context.CandidateState as EnqueuedState;

            if (enqueuedState != null )
            {
                var stateData = context.Connection.GetStateData(context.BackgroundJob.Id);
                string queueName;
                if (stateData.Data.TryGetValue("Queue", out queueName))
                {
                    enqueuedState.Queue = queueName;
                }
            }
        }

server
3) register this (UseQueueFromScheduledFilter ) and also the PreserveOriginalQueueAttribute filter (located here https://github.com/HangfireIO/Hangfire/pull/502#issuecomment-176744750)

these filters will ensure that the job queued in the correct place for the first use and any retry.

Client
4) Schedule a task.

```
ScheduledState scheduledState = new Core.ScheduledState(new TimeSpan(0, 0, 1, 0), "my_app");

var b = new BackgroundJobClient();

b.Create(
    () => Console.WriteLine("Hello, world3!"),
    scheduledState);

```

the code above uses the state from step 1. not the state located in hangfire.

All 3 comments

this work-around looks to work (I have done the scheduled for now.)

Client
1) provide more data in the ScheduledState, put in a location (ie classlib/dll) which the client can access (does not matter much about the background server)

 public class ScheduledState : IState
    {
        public static readonly string StateName = "Scheduled";

        public ScheduledState(TimeSpan enqueueIn, string queue = "default")
            : this(DateTime.UtcNow.Add(enqueueIn), queue)
        {
        }

        [JsonConstructor]
        public ScheduledState(DateTime enqueueAt, string queue = "default")
        {
            EnqueueAt = enqueueAt;
            ScheduledAt = DateTime.UtcNow;
            Queue = queue;
        }

        public DateTime EnqueueAt { get; }
        public DateTime ScheduledAt { get; }
        public string Queue { get; }
        public string Name => StateName;
        public string Reason { get; set; }
        public bool IsFinal => false;
        public bool IgnoreJobLoadException => false;

        public Dictionary<string, string> SerializeData()
        {
            return new Dictionary<string, string>
            {
                { "Queue", Queue },
                { "EnqueueAt", JobHelper.SerializeDateTime(EnqueueAt) },
                { "ScheduledAt", JobHelper.SerializeDateTime(ScheduledAt) }
            };
        }  
    }

Server
2) add a filter (i chose to use an elect one)

public class UseQueueFromScheduledFilter : IJobFilter, IElectStateFilter
    {
        public void OnStateElection(ElectStateContext context)
        {

            var enqueuedState = context.CandidateState as EnqueuedState;

            if (enqueuedState != null )
            {
                var stateData = context.Connection.GetStateData(context.BackgroundJob.Id);
                string queueName;
                if (stateData.Data.TryGetValue("Queue", out queueName))
                {
                    enqueuedState.Queue = queueName;
                }
            }
        }

server
3) register this (UseQueueFromScheduledFilter ) and also the PreserveOriginalQueueAttribute filter (located here https://github.com/HangfireIO/Hangfire/pull/502#issuecomment-176744750)

these filters will ensure that the job queued in the correct place for the first use and any retry.

Client
4) Schedule a task.

```
ScheduledState scheduledState = new Core.ScheduledState(new TimeSpan(0, 0, 1, 0), "my_app");

var b = new BackgroundJobClient();

b.Create(
    () => Console.WriteLine("Hello, world3!"),
    scheduledState);

```

the code above uses the state from step 1. not the state located in hangfire.

forgot to mention

client
1a) unregister the existing and register the following

public class StateHandler : IStateHandler
    {
        public void Apply(ApplyStateContext context, IWriteOnlyTransaction transaction)
        {
            var scheduledState = context.NewState as ScheduledState;
            var originalScheduledState = context.NewState as Hangfire.States.ScheduledState;

            if (scheduledState == null && originalScheduledState == null)
            {
                throw new InvalidOperationException(
                    $"`{GetType().FullName}` state handler can be registered only for the Scheduled state.");
            }

            var datetime = scheduledState?.EnqueueAt ?? originalScheduledState.EnqueueAt;

            var timestamp = JobHelper.ToTimestamp(datetime);
            transaction.AddToSet("schedule", context.BackgroundJob.Id, timestamp);
        }

        public void Unapply(ApplyStateContext context, IWriteOnlyTransaction transaction)
        {
            transaction.RemoveFromSet("schedule", context.BackgroundJob.Id);
        }

        // ReSharper disable once MemberHidesStaticFromOuterClass
        public string StateName => ScheduledState.StateName;
    }

@dbones hi,i used your code ,but when i run it ,threw a exception is
"InvalidOperationException: Hangfire.States.ScheduledState+Handler state handler can be registered only for the Scheduled state."
so i want to ask you what is the mean you said "forgot to mention"?

Was this page helpful?
0 / 5 - 0 ratings

Related issues

dgaspar picture dgaspar  路  4Comments

thurfir picture thurfir  路  4Comments

tompazourek picture tompazourek  路  3Comments

abdelrady picture abdelrady  路  4Comments

nsnail picture nsnail  路  3Comments