2012-06-04 9 views
5

Quando si utilizza Task <T>, viene generata un'eccezione durante l'esecuzione dell'attività durante Task.Wait(); quando si utilizza MailBoxProcessor di F #, l'eccezione viene inghiottita e deve essere esplicitamente trattata come da this question.Unifying Task <T> e F # MailboxProcessor gestione eccezioni

Questa differenza rende difficile esporre gli agenti F # al codice C# tramite un'attività. Ad esempio, questo agente:

type internal IncrementMessage = 
    Increment of int * AsyncReplyChannel<int> 

type IncrementAgent() = 
    let counter = Agent.Start(fun agent -> 
     let rec loop() = async { let! Increment(msg, replyChannel) = agent.Receive() 
           match msg with 
           | int.MaxValue -> return! failwith "Boom!" 
           | _ as i -> replyChannel.Reply (i + 1) 
              return! loop() } 

     loop()) 

    member x.PostAndAsyncReply i = 
     Async.StartAsTask (counter.PostAndAsyncReply (fun channel -> Increment(i, channel))) 

può essere chiamato da C#, ma l'eccezione non viene restituito in C#:

[Test] 
public void ExceptionHandling() 
{ 
    // 
    // TPL exception behaviour 
    // 
    var task = Task.Factory.StartNew<int>(() => { throw new Exception("Boom!"); }); 

    try 
    { 
     task.Wait(); 
    } 
    catch(AggregateException e) 
    { 
     // Exception available here 
     Console.WriteLine("Task failed with {0}", e.InnerException.Message); 
    } 

    // 
    // F# MailboxProcessor exception behaviour 
    // 
    var incAgent = new IncrementAgent(); 
    task = incAgent.PostAndAsyncReply(int.MaxValue); 

    try 
    { 
     task.Wait(); // deadlock here 
    } 
    catch (AggregateException e) 
    { 
     Console.WriteLine("Agent failed with {0}", e.InnerException.Message); 
    } 
} 

Invece di ottenere l'eccezione, il codice C# si blocca proprio in task.Wait(). C'è un modo per far sì che l'agente F # si comporti come un compito? In caso contrario, sembra che vi sia un uso limitato nell'esporre agenti F # ad altri codici .NET.

risposta

3

Un modo per gestire la cosa è avere l'agente restituisce un DU con un caso di errore. È quindi possibile aumentare l'eccezione dall'esterno dell'agente.

type internal IncrementResponse = 
    | Response of int 
    | Error of exn 

type internal IncrementMessage = 
    | Increment of int * AsyncReplyChannel<IncrementResponse> 

type IncrementAgent() = 
    let counter = Agent.Start(fun agent -> 
     let rec loop() = 
      async { 
      let! Increment(msg, replyChannel) = agent.Receive() 
      match msg with 
      | int.MaxValue -> replyChannel.Reply (Error (Failure "Boom!")) 
      | _ as i -> replyChannel.Reply (Response(i + 1)) 
      return! loop() 
      } 
     loop()) 

    member x.PostAndAsyncReply i = 
     Async.StartAsTask (
      async { 
      let! res = counter.PostAndAsyncReply (fun channel -> Increment(i, channel)) 
      match res with 
      | Response i -> return i 
      | Error e -> return (raise e) 
      } 
     ) 
+0

Grazie, è esattamente quello che stavo cercando! Non ho la mia testa in giro 'return (raise e)' essendo un costrutto legale, anche se il mio codice usava un simile 'return! failwith "Boom!" '. – Akash

+0

'raise' è definito come restituire' 'T', ma naturalmente non restituisce mai realmente. Ciò consente di utilizzarlo ovunque, indipendentemente dal tipo di espressione che lo racchiude. – Daniel

+0

@Akash: potresti anche scrivere: 'restituisci match match con Response i -> i | Errore e -> raise e'. – Daniel