Stop Hangfire starting a recurring job if it is already running

I have a list of recurring jobs in Hangfire, all with an interval of 20 minutes.

They all call the same method, but with different parameters. eg.

Job id test_1 => MyTestMethod (1)
Job id test_50 => MyTestMethod (50)
Job id test_37 => MyTestMethod (37)

Sometimes a task may take longer than its interval, that is, more than 20 minutes, and I want to make sure that the task does not start again while another instance is already running.

DisableConcurrentExecutionAttribute is not suitable here, because the CAN method can be executed simultaneously, just not with the same parameters.

I tried to set a static Dictionary<string, DateTime> , which kept track of whether the task was already running so that it could block simultaneous method calls, but the problem is that if the application restarts for some reason, it "forgets" which tasks (Hangfire, of course, will continue to run in the background)

UPDATE
I also tried adding the JobState table to my database to keep track of which jobs are running and then checking this table when MyTestMethod starts, but it depends on the setting MyTestMethod running = 1 when it starts and works = 0 when it ends, and if the flow falls halfway through this task (for some reason), which may not happen, thereby preventing the task from being repeated.

I'm sure I can solve this by simply requesting the status of the Hangfire job by job ID - I just canโ€™t find how to do this?

+5
source share
2 answers

I had a similar requirement, basically I wanted to use DisableConcurrentExecutionAttribute, but take into account its parameters. Thus, if a job is queued with the same parameter, it will still not work in parallel. I took the DisableMultipleQueuedItemsFilter example, which actually removes jobs and modifies the DisableConcurrentExecutionAttribute to use parameters. The difference is that jobs will be queued if they have the same list of parameters that they will not execute in parallel.

A complete example can be seen here with both attributes: https://gist.github.com/sbosell/3831f5bb893b20e82c72467baf8aefea

Corresponding code for the attribute:

  public class DisableConcurrentExecutionWithParametersAttribute : JobFilterAttribute, IServerFilter { private readonly int _timeoutInSeconds; public DisableConcurrentExecutionWithParametersAttribute (int timeoutInSeconds) { if (timeoutInSeconds < 0) throw new ArgumentException("Timeout argument value should be greater that zero."); _timeoutInSeconds = timeoutInSeconds; } public void OnPerforming(PerformingContext filterContext) { var resource = GetResource(filterContext.BackgroundJob.Job); var timeout = TimeSpan.FromSeconds(_timeoutInSeconds); var distributedLock = filterContext.Connection.AcquireDistributedLock(resource, timeout); filterContext.Items["DistributedLock"] = distributedLock; } public void OnPerformed(PerformedContext filterContext) { if (!filterContext.Items.ContainsKey("DistributedLock")) { throw new InvalidOperationException("Can not release a distributed lock: it was not acquired."); } var distributedLock = (IDisposable)filterContext.Items["DistributedLock"]; distributedLock.Dispose(); } private static string GetFingerprint(Job job) { var parameters = string.Empty; if (job?.Arguments != null) { parameters = string.Join(".", job.Arguments); } if (job?.Type == null || job.Method == null) { return string.Empty; } var payload = $"{job.Type.FullName}.{job.Method.Name}.{parameters}"; var hash = SHA256.Create().ComputeHash(System.Text.Encoding.UTF8.GetBytes(payload)); var fingerprint = Convert.ToBase64String(hash); return fingerprint; } private static string GetResource(Job job) { return GetFingerprint(job); } } 
+6
source

There is an attribute that you can use to prevent simultaneous execution (for a while):

 [DisableConcurrentExecution(3600)] // argument in seconds, eg, an hour public void MyTestMethod(int id) { // ... } 
+3
source

All Articles