2016-06-02 45 views
5

Ho un elenco di processi ricorrenti di installazione in Hangfire, il tutto con un intervallo di 20 minuti.Interrompi Hangfire che avvia un processo ricorrente se è già in esecuzione

Tutti chiamano lo stesso metodo, ma con parametri diversi. ad es.

ID lavoro TEST_1 => MyTestMethod (1)
ID lavoro test_50 => MyTestMethod (50)
ID lavoro test_37 => MyTestMethod (37)

A volte, un lavoro potrebbe richiede più tempo dell'intervallo, ovvero più di 20 minuti, e voglio assicurarmi che il lavoro non venga eseguito di nuovo mentre un'altra istanza è già in esecuzione.

L'DisableConcurrentExecutionAttribute non è adatto qui, perché il metodo può essere eseguito contemporaneamente, ma non con gli stessi parametri.

Ho tentato di avere un Dictionary<string, DateTime> statico che monitorava se un lavoro era già in esecuzione in modo che potesse bloccare chiamate di metodo concorrenti, ma il problema è che se l'applicazione si riavvia per qualsiasi motivo, quindi "dimentica" quali lavori sono attualmente in esecuzione (Hangfire ovviamente continuerà a funzionare in background)

UPDATE
ho anche provato ad aggiungere un tavolo JobState al mio database per tenere traccia che i lavori sono in esecuzione e quindi il controllo quel tavolo all'avvio MyTestMethod, ma ciò dipende dall'impostazione MyTestMethod in esecuzione = 1 all'avvio e in esecuzione = 0 quando termina e se il thread si arresta a metà del processo (per qualsiasi motivo) che potrebbe non accadere, impedendo così il processo di nuovo.

Sono sicuro che posso risolvere questo semplicemente interrogando lo stato del processo Hangfire per ID lavoro - Non riesco proprio a trovare come farlo?

+0

Check this out: http://docs.hangfire.io/en/latest /background-processing/tracking-progress.html (ti suggerisco di guardare l'esempio di Evidenziatore per comprendere il contesto dell'esempio). – aethercowboy

risposta

3

C'è un attributo è possibile utilizzare per impedire l'esecuzione simultanea (per un tempo):

[DisableConcurrentExecution(3600)] // argument in seconds, e.g., an hour 
public void MyTestMethod(int id) { 
    // ... 
} 
+1

Per quanto ne so, funziona solo per il metodo indipendentemente dai suoi parametri, ad es. l'id è passato al metodo. Avrò bisogno di eseguire lo stesso metodo contemporaneamente, solo non con lo stesso parametro che è stato passato ad esso (spero che abbia senso) – Jimbo

+2

Buon punto.Non ho notato che l'hai detto. Ci sono alcune cose aggiuntive da provare, uno è un filtro: https://gist.github.com/odinserj/a8332a3f486773baa009 e controlla i suggerimenti qui: https://discuss.hangfire.io/t/checking-for- a-job-state/57/7 – aethercowboy

+1

L'attributo Brilliant, DisableMultipleQueuedItemsFilter sembra fare esattamente ciò di cui ho bisogno - lo verificherà, quindi puoi postarlo come risposta e segnare alcuni punti: P – Jimbo

6

ho avuto un requisito analogo, in pratica ho voluto usare la DisableConcurrentExecutionAttribute ma hanno questa tiene conto dei parametri. In questo modo, se un lavoro viene accodato con lo stesso parametro, verrà comunque eseguito solo in modo non parallelo. Ho preso l'esempio DisableMultipleQueuedItemsFilter che rimuove effettivamente i lavori e ha modificato DisableConcurrentExecutionAttribute per utilizzare i parametri. La differenza è che i lavori verranno accodati se hanno lo stesso elenco di parametri che non verranno eseguiti in parallelo.

Un esempio completo può essere visto qui con entrambi gli attributi: https://gist.github.com/sbosell/3831f5bb893b20e82c72467baf8aefea

Il codice rilevanti per l'attributo:

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); 
     } 
    }