【问题标题】:Unifying Task<T> and F# MailboxProcessor exception handling统一 Task<T> 和 F# MailboxProcessor 异常处理
【发布时间】:2012-06-05 02:40:49
【问题描述】:

使用Task时,Task.Wait()期间会抛出任务执行过程中的异常;使用 F# 的 MailBoxProcessor 时,异常被吞没,需要按照this question 明确处理。

这种差异使得通过任务将 F# 代理暴露给 C# 代码变得困难。例如,这个代理:

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

可以从C#调用,但是异常不会返回到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);
    }
}

C# 代码没有得到异常,而是挂在 task.Wait() 处。有没有办法让 F# 代理表现得像一个任务?如果没有,似乎将 F# 代理暴露给其他 .NET 代码的用途有限。

【问题讨论】:

    标签: f# mailboxprocessor


    【解决方案1】:

    处理它的一种方法是让代理返回带有错误情况的 DU。然后,您可以从代理外部引发异常。

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

    【讨论】:

    • 谢谢,这正是我想要的!尽管我的代码使用了类似的 return! failwith "Boom!",但我并没有意识到 return (raise e) 是一个合法的构造。
    • raise 被定义为返回'T,但当然它永远不会真正返回。这允许它在任何地方使用,而不管封闭表达式的类型。
    • @Akash:你也可以写:return match res with Response i -&gt; i | Error e -&gt; raise e
    猜你喜欢
    • 1970-01-01
    • 2020-02-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多