2012-06-04 3 views
5

Bei Verwendung von Task <T> wird eine Ausnahme während der Ausführung der Task während Task.Wait() ausgelöst; Bei Verwendung des MailBoxProcessors von F # wird die Ausnahme verschluckt und muss explizit gemäß this question behandelt werden.Unifying Task <T> und F # MailboxProcessor Ausnahmebehandlung

Dieser Unterschied erschwert die Offenlegung von F # -Agenten gegenüber C# -Code über eine Task. Zum Beispiel dieses Mittel:

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

aus C# aufgerufen werden, sondern die Ausnahme ist nicht auf C# zurückgegeben:

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

statt die Ausnahme zu bekommen, die C# -Code hängt gerade bei task.Wait(). Gibt es eine Möglichkeit, den F # -Agenten dazu zu bringen, sich wie eine Aufgabe zu verhalten? Wenn dies nicht der Fall ist, scheint es nur eingeschränkt möglich zu sein, F # -Agenten anderen .NET-Code zur Verfügung zu stellen.

Antwort

3

Eine Möglichkeit, damit umzugehen, ist, dass der Agent einen DU mit einem Fehlerfall zurückgibt. Sie könnten dann die Ausnahme von außerhalb des Agenten auslösen.

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

Danke, das ist genau das, was ich gesucht habe! Ich habe meinen Kopf nicht um 'return (raise e)' als ein rechtliches Konstrukt, obwohl mein Code eine ähnliche 'Rückkehr' verwendet! failwith "Boom!" '. – Akash

+0

'Raise' ist definiert als Rückgabe von '' T ', aber natürlich kehrt es nie wirklich zurück. Dadurch kann es unabhängig vom umschließenden Ausdruckstyp überall verwendet werden. – Daniel

+0

@Akash: Sie könnten auch schreiben: 'result match res mit Response i -> i | Fehler e -> raise e'. – Daniel