Recentemente ho avuto la necessità di scrivere un batch in C# che nottetempo facesse delle operazioni un po' pesanti su un database, attingendo ai dati, compiendo elaborazioni e alla fine salvando i risultati nel medesimo database. Dopo un po' di test mi sono reso conto che il problema principale era dato dai timeout che saltuariamente il batch otteneva su alcune query con il rischio di compromettere l'integrità del lavoro.

Così, LINQ alla mano mi sono create un piccolo script che, presa in carico una operazione effettua un numero dato di tentativi intervallati da un ragionevole timeout così da dare il tempo al database di fare il suo lavoro.

   1: /// <summary>
   2: /// Does the retry.
   3: /// </summary>
   4: /// <param name="maxRetry">The max retry.</param>
   5: /// <param name="waitTimeout">The wait timeout.</param>
   6: /// <param name="action">The action.</param>
   7: public static void DoRetry(int maxRetry, int waitTimeout, Action<int> action)
   8: {
   9:     for (int current = 1; current <= maxRetry; current++)
  10:     {
  11:         try
  12:         {
  13:             action(current);
  14:             return;
  15:         }
  16:         catch (SqlException ex)
  17:         {
  18:             // TIMEOUT | DEADLOCK
  19:             if (ex.Number != -2 && ex.Number != 1205)
  20:                 throw ex;
  21:         }
  22:  
  23:         Thread.Sleep(waitTimeout);
  24:     }
  25: }

Lo script è davvero semplice e pulito. Chiede in ingresso il numero massimo di tentativi e il tempo di attesa, oltre ad una Action<int> ovvero una lambda expressione con l'azione da compiere. Poi fa un semplicissimo ciclo for che ad ogni iterazione chiama l'Action. Nel caso vada tutto bene esce altrimenti - in caso di timeout o di deadlock - attente il timeout e riprova. Naturalemente in caso di diversa eccezione il metodo notifica l'eccezione al chiamante. Ecco come usarlo:

   1: public void ExecuteLongRunningQuery(int parameter)
   2: {
   3:     Operations.DoRetry(5, 10000,
   4:         n =>
   5:         {
   6:             DataStore.LongRunningQuery(parameter);
   7:         });
   8: }

Poche righe e otteniamo di ripetere la query 5 volte intervallata di 10 secondi l'una dall'altra. Il parametro "n" che viene passato alla lambda è il numero di iterazione corrente e permette di scrivere in un ipotetico log il numero di tentativi effettuati.

Technorati Tags: ,

Commenti (7) -

# | Guido | 15.07.2009 - 19.33

Ciao,
prima di tutto complimenti per il blog Smile

detto questo... ho una domanda probabilmente banale...
per questo semplice caso perche' usare le lambda expression?
percche' non l'uso di un delegato?
o ancora piu' semplicemente mettere direttamente la chimata DataStore.ecc direttamente nel for?
puro virtuosismo?
lo script e' piu' complesso di quello che vedo?
risulta piu' sintetico?
piu' flessibile per futuri utilizzi?
nessuna polemica... solo una dubbio

# | Andrea Boschin | 15.07.2009 - 21.47

Ciao Guido e grazie per il commento.
Capisco il tuo punto di vista e ti rispondo con una provocazione chiedendoti:

perchè no?

Alla fine una lambda è un costrutto come un altro (peraltro è un delegate alla fine), che ha solo il vantaggio di rendere il codice più pulito e leggibile.
E secondo i criteri di riuso del codice l'estrarre una logica e renderla riusabile (il ciclo foreach) è sicuramente meglio che riscriverla sempre uguale mille volte. L'uso delle lambda e dei delegate Action<T> e Func<T> facilita di molto questo compito e il post ha proprio lo scopo di dimostrare questo più che l'esempio nello specifico.

# | Luca Minudel | 15.07.2009 - 22.32

questo post mi ha colpito perchè tempo fa ho dovuto implementare una funzione simile.
in quel caso ho usato i delegate e gli anonymous method con un risultato equivalente


perché usare uno o l'altro? mah! se il team è a suo agio con le lambda userei quelle altrimenti il delegate e l'anonymous method


piuttosto, questa è l'implementazione del command pattern, quindi mi chiederei come "attaccare" anche i parametri maxRetry e waitTimeout al command (che ora è una Action<T> ma potrebbe essere una terna Action<T>, maxRetry, waitTimeout)

# | Andrea Boschin | 15.07.2009 - 23.03

Potrebbe benissimo essere una Action<T,K,J> che è un delegate esistente nel framework. Però fatico a vederne l'utilità, almeno nel mio contesto.

# | Guido | 16.07.2009 - 00.32

>> Capisco il tuo punto di vista e ti rispondo con una provocazione chiedendoti:
>> perchè no? [...]
>> L'uso delle lambda e dei delegate Action<T> e Func<T> facilita di molto questo
>> compito e il post ha proprio lo scopo di dimostrare questo più che l'esempio
>> nello specifico.

perche' no?!
beh... nel caso specifico aggiungere astrazione solo per chiamare una funzione n volte mi sembra come sparare ad una mosca con un cannone Smile

se invece come dici il caso specifico era solo un pretesto per fare pratica ed illustarre le Action<T> allora e' un altro discorso... lo prendo come puro esempio didattico e vivo felice Smile

* Lunga vita e prosperita' *

# | Andrea Boschin | 16.07.2009 - 01.45

Ok, Non mi sembra però che le lambda siano un cannone. Non hanno molto di diverso dai delegate e nel caso specifico risparmiano molto codice (pensa ad esempio al passaggio di "parameter" dal metodo al delegate) quindi sono il cannone migliore Smile
Diciamo che sono strumenti che abbiamo a disposizione, e non vedo perchè discriminarle Tong

# | Andrea Boschin | 16.07.2009 - 01.47

precisazione: nel mio caso i metodi da chiamare n volte erano molteplici. Chiaro che se era uno solo chissenefrega di avere una lambda.

Aggiungi Commento