2012-09-30 11 views
9

Sto costruendo una libreria di registrazione che memorizza tutto su una tabella di Azure. Scrivendo a quel tavolo prende ovviamente un sacco di tempo (non più di 1 secondo, ma è ancora troppo da rendere l'utente di attesa), quindi il metodo Log restituisce un'istanza LogResult, ecco la classeImpedire a IIS di uccidere un'attività prima che termini

public class LogResult 
{ 
    public string Id { get; set; } 
    public Task LoggingTask { get; set; } 

    public LogResult(string id, Task task) 
    { 
     Id = id; 
     LoggingTask = task; 
    } 
} 

Ed ecco come il metodo Log finisce

return new LogResult(id, Task.Factory.StartNew(() => 
    DoLogInAzure(account, id, exception, request)) 
); 

per dare al chiamante la possibilità di aspettare che sia completato (se si tratta di una console app, per esempio). Il problema che sto affrontando è che IIS non dovrebbe attendere prima di riportare l'utente la risposta ... e se non aspettare che, IIS non sempre eseguire l'operazione. L'idea è di mostrare all'utente un messaggio "... Se ci contatti, assicurati di menzionare il numero del tuo problema, XXX" e non farlo aspettare fino a quando non è stata scritta la voce del registro.

Esiste un modo per imporre a IIS di attendere fino al termine dell'attività, anche dopo aver restituito la risposta? Sto pensando che potrebbe essere necessario codificare un servizio di Windows che accetta la richiesta in modo asincrono, ma sembra molto lavoro solo per aggiungere una voce di log ... specialmente se posso forzare IIS ad aspettarlo.

Grazie per qualsiasi idea!

+0

Ho sentito che IIS riavvia i "siti web" che occupano troppa memoria ... –

+0

Capisco :) tuttavia, non penso che sia il mio caso, come se attendessi che l'attività finisse, IIS termina il l'esecuzione e la voce del registro sono scritte al 100% delle volte – g3rv4

+0

In alternativa, è possibile utilizzare solo una coda azzurra per accedere. Quindi un gestore di messaggi separato può scrivere in modo asincrono sulla tabella azzurra. Ancora meglio, se si verifica un errore durante l'elaborazione, il messaggio rimane in coda e verrà elaborato nuovamente in seguito. –

risposta

8

Grazie a Damian Schenkelman e al post sul blog di Phil Haack, ho capito il problema e la soluzione. Il problema è che IIS riutilizza i thread quando deve gestire nuove richieste. E poiché non sa che il mio compito sta facendo del lavoro, riutilizza quel thread (che ha senso). Quindi, devo solo notificare a IIS che sto usando quel thread e che non può essere riutilizzato (quindi, deve riutilizzare un altro thread, crearne uno nuovo o farlo attendere). Ho finito per utilizzare il mio TaskFactory che gestisce la creazione dell'attività e registra automaticamente un notificatore in IIS. Per completezza, per aiutare qualche altro folk con lo stesso problema come me, e per leggere un altro suggerimento, ecco quello che ho fatto

public class IISNotifier : IRegisteredObject 
{ 
    public void Stop(bool immediate) 
    { 
     // do nothing, I only run tasks if I know that they won't 
     // take more than a few seconds. 
    } 

    public void Started() 
    { 
     HostingEnvironment.RegisterObject(this); 
    } 

    public void Finished() 
    { 
     HostingEnvironment.UnregisterObject(this); 
    } 
} 

E poi

public class IISTaskFactory 
{ 
    public static Task StartNew(Action act) 
    { 
     IISNotifier notif = new IISNotifier(); 
     notif.Started(); 
     return Task.Factory.StartNew(() => { 
      act.Invoke(); 
      notif.Finished(); 
     }); 
    } 
} 

Ora, quando voglio iniziare un compito registro io faccio solo

return new LogResult(id, IISTaskFactory.StartNew(() => 
    DoLogInAzure(account, id, exception, request)) 
); 

Si può vedere (e scaricare il codice) a https://github.com/gmc-dev/IISTask

11

This post da Phil Hack parla di esecuzione le operazioni in background in un'applicazione ASP.NET.

+0

Ho appena finito di leggere quell'articolo ... Grazie, è davvero qualcosa di cui ho bisogno. Tuttavia, non spiega il motivo per cui il mio compito non viene sempre eseguito ... Lo sto eseguendo sul mio computer e generando 100 eccezioni in un minuto memorizza circa 92 se non aspetto che l'attività venga completata. Il pool di app non viene riavviato ... Ha senso anche questo? – g3rv4

+0

Come stai generando le eccezioni? Quando si attende, è sufficiente bloccare il thread in esecuzione fino al completamento di tutte le attività, ma ciò non dovrebbe essere necessario se il processo è ancora in esecuzione. Il processo si arresta prima che le attività vengano effettivamente eseguite? Utilizza la logica simulata (ad es .: stampa sulla finestra di debug) in ogni attività per assicurarti che tutte le attività avessero il tempo di iniziare e di completare. –

+0

L'eccezione che sto trattando adesso, è b/c, sto assegnando null su una stringa non nullable su un'entità ... quindi, il 'context.SaveChanges();' fallisce. Tuttavia, se generi un'eccezione e non faccio altro, è sempre archiviata. Il modo per farlo fallire sta generando molte eccezioni in un breve periodo di tempo. Immagino che IIS possa riutilizzare il thread per gestire nuove richieste, e in quel momento il mio compito è stato interrotto. Sto facendo 100 richieste (che generano un'eccezione) in meno di 10 secondi, e questo è quando fallisce. – g3rv4

0

Le informazioni non sono sufficienti ma sospetto che potrebbe essere correlato a GC e riferimenti se funziona quando si attende l'operazione. Per il tuo scopo, un modo migliore è utilizzare ETW (EventProvider) e impostare ActivityId per ogni richiesta. Basta configurare una sessione ETW in grado di reindirizzare tutti i messaggi su un file. Puoi mostrare ActivityId (un Guid) all'utente finale.

+2

'Task' funzionerà correttamente anche se non c'è alcun riferimento ad esso, non è questo il problema. – svick

+0

Per verificare se questo è il problema, ho memorizzato tutti i riferimenti al 'Task' in un'istanza singleton ... tuttavia, ciò non ha cambiato il suo comportamento ... Finora, l'unica cosa che funziona, è aspettare per l'attività prima che la risposta venga recuperata – g3rv4

0

Ci scusiamo per non averlo aggiunto come commento, non ho abbastanza rep.

https://msdn.microsoft.com/en-us/library/system.web.hosting.iregisteredobject(v=vs.110).aspx

Le applicazioni possono avere una sola istanza di un tipo registrato.

Questo sembra indicare che risposta accettato di Gervasio Marchand è in qualche modo corretto, in quanto ogni chiamata al suo metodo di supporto statica crea una nuova IISNotifier, che è un IRegisteredObject.

+1

sei riuscito a trovare un esempio in cui questa soluzione non funziona? Mi piacerebbe vedere qualcosa di simile, visto che ho usato questo approccio su un sacco di progetti e non ho mai visto nulla di strano – g3rv4

Problemi correlati