2012-03-20 10 views
13

In un'app console sincrona/sincrona/a thread singolo, NDC.Push funziona correttamente per la gestione dell'elemento corrente (potenzialmente a più livelli di nidificazione, ma solo 1 livello per questo esempio).come gestire uno stack log4net simile a NDC con metodi async/await? (per-Task stack?)

Ad esempio:

private static ILog s_logger = LogManager.GetLogger("Program"); 

static void Main(string[] args) 
{ 
    BasicConfigurator.Configure(); 

    DoSomeWork("chunk 1"); 
    DoSomeWork("chunk 2"); 
    DoSomeWork("chunk 3"); 
} 

static void DoSomeWork(string chunkName) 
{ 
    using (NDC.Push(chunkName)) 
    { 
     s_logger.Info("Starting to do work"); 
     Thread.Sleep(5000); 
     s_logger.Info("Finishing work"); 
    } 
} 

Ciò comporterà l'uscita si aspettano log, mostrando un 'pezzo X' ingresso NDC appena a destra del 'Programma' (il modello predefinito per il configuratore di base)

232 [9] INFO Programma pezzo 1 - Iniziare a fare il lavoro

5279 [9] INFO Programma pezzo 1 - Finiture edili

5279 [9] INFO programma pezzo 2 - Avvio di fare un lavoro

10292 [9] Programma INFO pezzo 2 - Finiture edili

10292 [9] INFO Programma pezzo 3 - Iniziare a fare il lavoro

15299 [9] INFO Programma pezzo 3 - Finiture edili

Comunque, io non riesco a capire come mantenere che l'utilizzo di metodi asincroni 'normali'.

Per esempio, cercando di fare questo:

private static ILog s_logger = LogManager.GetLogger("Program"); 

static void Main(string[] args) 
{ 
    BasicConfigurator.Configure(); 

    var task1 = DoSomeWork("chunk 1"); 
    var task2 = DoSomeWork("chunk 2"); 
    var task3 = DoSomeWork("chunk 3"); 

    Task.WaitAll(task1, task2, task3); 
} 

static async Task DoSomeWork(string chunkName) 
{ 
    using (log4net.LogicalThreadContext.Stacks["NDC"].Push(chunkName)) 
    //using (log4net.ThreadContext.Stacks["NDC"].Push(chunkName)) 
    { 
     s_logger.Info("Starting to do work"); 
     await Task.Delay(5000); 
     s_logger.Info("Finishing work"); 
    } 
} 

mostra loro tutti a partire "normalmente", ma quando l'attività viene completata su un thread diverso, lo stack è perduto (speravo la log4net.LogicalThreadContext avrei essere TPL-'ware 'credo).

234 [10] INFO Programma pezzo 1 - Iniziare a fare il lavoro

265 [10] INFO programma pezzo 2 - Avvio di fare un lavoro

265 [10] INFO Programma pezzo 3 - Avvio per fare il lavoro

5280 [7] INFO programma (null) - Finiture

5280 [12] INFO programma (null) - Finiture edili

5280 [12] INFO programma (null) - Finiture edili

Al di fuori di aggiunta di un nuovo TaskContext (o simile) per log4net, c'è un modo di tracciare questo tipo di attività?

L'obiettivo è davvero farlo con lo zucchero syntax asincrono/attendi: forzare un certo tipo di affinità per i thread o fare cose come mantenere un dizionario concorrente attorno a un compito per tipo sono probabilmente opzioni praticabili, ma sto cercando di mantenere il più vicino possibile alla versione sincrona del codice. :)

+5

FYI, ho scoperto di recente che Microsoft ha risolto 'CallContext' in .NET 4.5 RTW per lavorare con' async'. Pertanto, NDC di log4net e altre soluzioni che utilizzano 'Logical * Data' funzioneranno come previsto con i metodi' async' (solo su .NET 4.5). –

+0

@StephenCleary fantastico! Grazie! –

+0

Non sono sicuro che si tratti di un problema proveniente dal contesto del thread logico. L'implementazione di log4net mi sembra sbagliata perché i thread padre e figlio condividono lo stesso stack. Il bambino dovrebbe ricevere un clone dello stack genitore in modo che se il genitore modifica lo stack non perturba il bambino ... –

risposta

15

Non esiste una buona storia per i contesti delle chiamate logiche async in questo momento.

CallContext non può essere utilizzato per questo.Il logico CallContext non capisce come i metodi async ritornino in anticipo e riprendano più tardi, quindi non funzionerà sempre correttamente per il codice che utilizza il parallelismo semplice come Task.WhenAll.

Aggiornamento:CallContext è stato aggiornato a .NET 4.5 RTW per funzionare correttamente con async metodi.

Ho cercato su log4net; LogicalThreadContext è documentato come usando CallContext, ma c'era un bug che lo faceva usare i contesti non-logici (fissati nel loro SVN il 2 febbraio 2012, la versione corrente 1.2.11 non include quella correzione). Anche se è stato risolto, tuttavia, non funzionerà ancora con async (perché il logico CallContext non funziona con async).

Quando ho bisogno di un contesto di chiamata logica async, creo una classe che contenga i dati di contesto e tenga tutti i miei metodi async in uno stile funzionale come membri di istanza di quella classe. Questo è certamente non è una soluzione ideale, ma è un hack sporco che funziona.

Nel frattempo, si prega di invertire lo suggestion that Microsoft provide some mechanism for this.

P.S. Un dizionario concorrente digitato da Task non funziona, perché i metodi async non sono necessariamente in esecuzione attività (ad esempio, nel codice di esempio, nell'istruzione using, Task.CurrentId sarebbe null perché in quel punto non è in esecuzione alcuna attività).

E anche l'affinità del thread ha i suoi problemi. In realtà finisci per aver bisogno di un thread separato per ogni operazione asincrona indipendente. Addio, scalabilità ...

+0

Scusa se non ero chiaro: per l'approccio del dizionario concorrente, non userei metodi asincroni del tutto, ma invece sarebbe "esplicitamente" utilizzare le attività (quindi, cosa è già possibile fare in VS2010/.NET4). Tipo di desiderio, potrei semplicemente allegare i dati a un'attività (beh, senza sottoclassi e creare il mio) e ottenere il 'contesto di attività corrente' simile a HttpContext.Current, anche se non l'ho nemmeno pensato. :) –

+0

Capisco. Bene, se si desidera allegare dati a un 'Task' (diverso da' AsyncState'), è possibile utilizzare [Proprietà connesse] (http://connectedproperties.codeplex.com/), ma IMO è altrettanto facile da usare ' ConcurrentDictionary' per questo scenario. –

+0

@StephenCleary Stephen Toub ha risposto al tuo post collegato su User Voice dicendo che le tue informazioni iniziali erano obsolete e CallContext è appropriato per async/await. – Lex