2012-03-20 12 views
13

En una aplicación de consola normal/síncrona/de un solo subproceso, NDC.Push funciona bien para administrar el 'elemento actual' (potencialmente en múltiples niveles de anidamiento, pero solo 1 nivel para este ejemplo).cómo administrar una pila log4net tipo NDC con métodos async/await? (¿por pila de tareas?)

Por ejemplo:

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

Esto dará lugar a la salida de esperar que ingrese, mostrando una entrada de 'trozo X' NDC justo a la derecha del 'Programa' (el patrón predeterminado para el configurador básico)

232 [9] Programa INFO trozo 1 - Empezando a hacer el trabajo

5279 [9] trozo Programa INFO 1 - terminación

5279 [9] trozo Programa INFO 2 - Comenzando a hacer el trabajo

10292 [9] Programa INFO trozo 2 - terminación

10292 [9] Programa INFO trozo 3 - Comenzando a hacer el trabajo

15299 [9] INFO Programa fragmento 3 - Trabajo de acabado

Sin embargo, no puedo encontrar la manera de mantener eso utilizando los métodos asíncronos 'normales'.

Por ejemplo, tratando de hacer esto:

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

les muestra toda la partida "normalmente", pero cuando se completa la tarea en un subproceso diferente, se pierde la pila (que estaba esperando la log4net.LogicalThreadContext gustaría ser TPL-'consciente ', supongo).

234 [10] Programa INFO trozo 1 - Empezando a hacer el trabajo

265 [10] Programa INFO trozo 2 - Comenzando a hacer el trabajo

265 [10] Programa INFO trozo 3 - Comenzando para hacer el trabajo

5280 [7] Programa INFO (null) - trabajos de acabado

5280 [12] Programa INFO (null) - terminación

5280 [12] Programa INFO (null) - terminación

Fuera de añadir un nuevo TaskContext (o similar) a Log4net, ¿hay una forma de seguimiento de este tipo de actividad?

El objetivo es realmente hacerlo con la sintaxis de asincronización/espera, ya sea forzando algún tipo de afinidad de hilos o haciendo cosas como mantener un diccionario simultáneo en función de la tarea, probablemente sean opciones viables, pero estoy tratando de mantener tan cerca de la versión síncrona del código como sea posible. :)

+5

FYI, recientemente descubrí que Microsoft arregló 'CallContext' en .NET 4.5 RTW para que funcione con' async'. Entonces, el NDC de log4net y otras soluciones que usan 'Logical * Data' funcionarán como se espera con los métodos' async' (solo en .NET 4.5). –

+0

@StephenCleary ¡increíble! ¡Gracias! –

+0

No estoy seguro de que sea un problema proveniente del contexto del hilo lógico. La implementación de log4net me parece incorrecta porque los hilos padre e hijo comparten la misma pila. El niño debe recibir un clon de la pila padre para que si el padre modifica la pila no perturbe al niño ... –

Respuesta

15

No hay una buena historia para los contextos de llamada lógica async en este momento.

CallContext no se puede utilizar para esto.El CallContext lógico no entiende cómo los métodos async regresan temprano y se reanudan más tarde, por lo que no siempre funcionará correctamente para el código que usa el paralelismo simple como Task.WhenAll.

Actualización:CallContext fue actualizado en .NET 4.5 RTW para trabajar correctamente con async métodos.

Miré en log4net; LogicalThreadContext está documentado como que usa CallContext, pero había un error que hacía que utilizara los contextos no lógicos (corregidos en su SVN el 2 de febrero de 2012; la versión actual de 1.2.11 no incluye esa corrección). Incluso cuando está arreglado, no funcionará con async (porque el CallContext lógico no funciona con async).

Cuando necesito un contexto de llamada lógica async, hago una clase que contiene los datos de contexto y guardo todos mis métodos async en un estilo funcional como miembros de instancia de esa clase. Esto es ciertamente no es una solución ideal, pero es un truco sucio que funciona.

Mientras tanto, vota por favor el suggestion that Microsoft provide some mechanism for this.

P.S. Un diccionario simultáneo codificado por Task no funcionará, porque los métodos async no necesariamente ejecutan tareas (es decir, en su código de ejemplo, en la instrucción using, Task.CurrentId sería null porque no hay ninguna tarea que se esté ejecutando en ese momento).

Y la afinidad de hilos también tiene sus propios problemas. En realidad, terminas necesitando un hilo separado para cada operación asincrónica independiente. Adiós, escalabilidad ...

+0

Lo siento, no estaba claro; para el enfoque de diccionario simultáneo, no estaría usando métodos asíncronos. en absoluto, sino que estaría 'explícitamente' usando tareas (entonces, lo que ya puede hacer en VS2010/.NET4). Me gustaría simplemente adjuntar datos a una Tarea (bueno, sin crear subclase y hacer la mía) y obtener el 'contexto de la tarea actual' similar a HttpContext.Current, aunque realmente no lo he pensado bien. :) –

+0

Ya veo. Bueno, si desea adjuntar datos a una 'Tarea' (que no sea' AsyncState'), podría usar [Propiedades conectadas] (http://connectedproperties.codeplex.com/), pero IMO es igual de fácil de usar ' ConcurrentDictionary' para este escenario. –

+0

@StephenCleary Stephen Toub respondió a su publicación vinculada en User Voice diciendo que su información inicial estaba desactualizada y CallContext es apropiado para async/await. – Lex

Cuestiones relacionadas