2011-08-23 10 views
9

Potrzebowałem przebić wyjątek, który występuje podczas wykonywania bloku asynchronicznego, po zalogowaniu wyjątku.Jak używać przebić w asynchronicznych przepływach pracy w F #?

Kiedy wykonuję następujące polecenie kompilator uważa, że ​​nie wywołuję funkcji przebicia z programu obsługi. Co ja robię źle?

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

Odpowiedz

11

Szorstki! Myślę, że to niemożliwe, ponieważ przebicie odpowiada specjalnej instrukcji IL, która chwyta wyjątek z góry stosu, ale sposób, w jaki wyrażenia asynchroniczne są zestawiane w łańcuch kontynuacji, nie sądzę, że semantyka będzie trwała!

Z tego samego powodu, co następuje nie zostanie skompilowany albo:

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

W tych sytuacjach, w których muszę obsłużyć wyjątek poza rzeczywistą with ciała, a chcieliby naśladować reraise (czyli jest, zachować ślad stosu wyjątku), używam this rozwiązanie, więc wszyscy razem kod wyglądałby następująco:

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

Aktualizacja: .NET 4. 5 wprowadzono ExceptionDispatchInfo, co może pozwolić na czystszą implementację powyżej reraisePreserveStackTrace.

+1

Ta odpowiedź jest chyba nieaktualne. W .net 4.5 możesz użyć klasy 'ExceptionDispatchInfo', która robi to i przechwytuje informacje o wiadrze Watsona, takie jak zbiór pochodzenia i przesunięcie IL. http://msdn.microsoft.com/en-us/library/system.runtime.exceptionservices.exceptiondispatchinfo(v=vs.110).aspx –

+0

@DaxFohl może dostarczyć zaktualizowaną odpowiedź za pomocą 'ExceptionDispatchInfo'? –

3

Wpadłem na podobny problem, w innym kontekście, ale sprowadza się to do tego.

Wyjątków nie można rzucać na inny wątek - wywołanie reraise() wymagałoby obsługi wyjątku działającej w pewnym sensie "powyżej" oryginalnego bloku asynchronicznego w kodzie.

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) 

Uwaga, nadal nie można używać przebicie, jak jesteśmy teraz dzwoniąc na inny wątek, więc owinąć wyjątek wewnątrz innego jednej

+1

Jest bardziej konkretny niż brak możliwości wywołania przebicia z innego wątku, nie można go wywołać z innej ramki stosu. –

Powiązane problemy