2012-06-04 18 views
5

Podczas korzystania z zadania <T> podczas wykonywania zadania jest zgłaszany wyjątek podczas Task.Wait(); podczas korzystania z MailBoxProcessor F #, wyjątek zostaje połknięty i musi być jawnie rozpatrzony zgodnie z this question.Ujednolicanie zadania i obsługa wyjątków Faila MailboxProcessor

Ta różnica utrudnia wystawienie agentów F # na kod C# za pomocą zadania. Na przykład, ten agent:

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

może być wywołana z C#, ale sytuacja ta nie jest zwracana do 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); 
    } 
} 

Zamiast się wyjątek, kod C# po prostu wisi w task.Wait(). Czy istnieje sposób, aby agent F # zachowywał się jak zadanie? Jeśli nie, wydaje się, że korzystanie z agentów F # jest ograniczone do innego kodu .NET.

Odpowiedz

3

Jednym ze sposobów radzenia sobie z nim jest zwrócenie przez agenta DU z przypadkiem błędu. Następnie możesz zgłosić wyjątek spoza agenta.

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

Dzięki, właśnie tego szukałem! Nie mam głowy wokół 'return (raise e)' będąc legalną konstrukcją, mimo że mój kod używał podobnego 'powrotu! failwith "Boom!" '. – Akash

+0

'raise' jest zdefiniowane jako zwracające' 'T', ale oczywiście nigdy tak naprawdę nie wraca. Dzięki temu można go używać w dowolnym miejscu, niezależnie od typu otaczającego wyrażenia. – Daniel

+0

@Akash: Możesz również napisać: 'return match res z odpowiedzią i -> i | Błąd e -> podnieś e'. – Daniel