2011-08-23 10 views
9

Necesité volver a publicar una excepción que se produce al ejecutar un bloque async, después de registrar la excepción.Cómo usar reubicación en flujos de trabajo asíncronos en F #?

Cuando hago lo siguiente, el compilador piensa que no estoy llamando a la función de reubicación desde el controlador. ¿Qué estoy haciendo mal?

let executeAsync context = async { 
    traceContext.Properties.Add("CorrelationId", context.CorrelationId) 
    try 
     do! runAsync context 
     return None 
    with 
     | e when isCriticalException(e) -> 
      logCriticalException e 
      reraise() 
     | e -> 
      logException e 
      return Some(e) 
} 

Respuesta

11

Rough! Creo que esto es imposible, porque reubicar corresponde a una instrucción especial IL que capta la excepción desde la parte superior de la pila, pero la forma en que las expresiones asincrónicas se compilan en una cadena de continuaciones, ¡no creo que la semántica se mantenga!

Por la misma razón, la siguiente no se compilará bien:

try 
    (null:string).ToString() 
with e -> 
    (fun() -> reraise())() 

En estas situaciones, en las que necesito para manejar la excepción fuera de la with corporal real, y que le gustaría emular reraise (que es, preservar el seguimiento de la pila de la excepción), utilizo solución this, por lo que todos juntos el código se vería así:

let inline reraisePreserveStackTrace (e:Exception) = 
    let remoteStackTraceString = typeof<exn>.GetField("_remoteStackTraceString", BindingFlags.Instance ||| BindingFlags.NonPublic); 
    remoteStackTraceString.SetValue(e, e.StackTrace + Environment.NewLine); 
    raise e 

let executeAsync context = async { 
    traceContext.Properties.Add("CorrelationId", context.CorrelationId) 
    try 
     do! runAsync context 
     return None 
    with 
     | e when isCriticalException(e) -> 
      logCriticalException e 
      reraisePreserveStackTrace e 
     | e -> 
      logException e 
      return Some(e) 
} 

actualización: .NET 4. 5 introducido ExceptionDispatchInfo que puede permitir una implementación más limpia de reraisePreserveStackTrace anterior.

+1

Esta respuesta es quizás obsoleta. En .NET 4.5 puede usar la clase 'ExceptionDispatchInfo', que hace esto y también captura la información del cubo de Watson, como el ensamblado de origen y el desplazamiento IL. http://msdn.microsoft.com/en-us/library/system.runtime.exceptionservices.exceptiondispatchinfo(v=vs.110).aspx –

+0

@DaxFohl podría proporcionar una respuesta actualizada utilizando 'ExceptionDispatchInfo'? –

3

Me encontré con un problema similar, en un contexto diferente, pero se reduce a esto.

Las excepciones no pueden arrojarse a un hilo diferente: llamar al reraise() requeriría que un controlador de excepción se ejecute en cierto sentido "por encima" del bloque asincrónico original en el código.

let runAsync context = async {return()} 
let isCriticalException e = true 
let logCriticalException e =() 
let logException e =() 
let executeAsync context = 
    async { 
      do! runAsync context 
      return None 
} 

let run = 
    match executeAsync 5 |> Async.Catch |> Async.RunSynchronously with 
    |Choice1Of2(t) -> 
     printfn "%A" t 
     None 
    |Choice2Of2(exn) -> 
      match exn with 
      | e when isCriticalException(e) -> 
       logCriticalException e 
       raise (new System.Exception("See inner exception",e)) //stack trace will be lost at this point if the exn is not wrapped 
      | e -> 
       logException e 
       Some(e) 

Nota, todavía no se puede utilizar volver a subir, ya que estamos pidiendo a un subproceso diferente, así que envuelva la excepción dentro de otro

+1

Es más específico que no poder llamar reubicación de un hilo diferente, no se puede llamar desde un marco de pila diferente. –

Cuestiones relacionadas