首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >F#:异步{}块中正确的“尝试”块?

F#:异步{}块中正确的“尝试”块?
EN

Stack Overflow用户
提问于 2020-01-24 10:33:48
回答 3查看 180关注 0票数 2

为了简化我的场景,让我们假设我有以下简单的代码:

代码语言:javascript
复制
let someCondition = false

let SomeFuncThatThrows () =
    async {
        if someCondition then
            raise <| InvalidOperationException()
        return 0
    }

let DoSomethingWithFoo (foo: int) =
    Console.WriteLine (foo.ToString())

let SomeWrapper () =
    async {
        let! foo = SomeFuncThatThrows()
        DoSomethingWithFoo foo
    }

[<EntryPoint>]
let main argv =
    Async.RunSynchronously (SomeWrapper ())
    0

在执行它时,它显然只是打印"0“。然而,总有一天,环境发生了变化,一些外部因素使someCondition成为true。为了防止程序在这种情况下崩溃,我想要处理异常。然后,对于一个F#新手来说,很容易改变SomeWrapper,添加一个try-with块,大多数人会认为这样做是可行的:

代码语言:javascript
复制
let SomeWrapper () =
    async {
        let! foo =
            try
                SomeFuncThatThrows()
            with
            | :? InvalidOperationException ->
                Console.Error.WriteLine "aborted"
                Environment.Exit 1
                failwith "unreachable"
        DoSomethingWithFoo foo
    }

但是,上面的方法不起作用(异常仍未处理),因为SomeFuncThatThrows返回一个成功的结果:一个Async<int>元素。抛出异常的是let! foo =位,因为它等待异步工作负载。

但是,如果您希望更改SomeWrapper以修复异常处理,许多人可能认为这是可能的:

代码语言:javascript
复制
let SomeWrapper () =
    async {
        let foo =
            try
                let! fooAux = SomeFuncThatThrows()
                fooAux
            with
            | :? InvalidOperationException ->
                Console.Error.WriteLine "aborted"
                Environment.Exit 1
                failwith "unreachable"
        DoSomethingWithFoo foo
    }

但是不,编译器不高兴,因为它表明了以下错误:

/./Program.fs(17,17):错误FS0750:此构造只能在计算表达式(FS0750) (SomeProject)中使用

那么,这似乎是我唯一能解决的办法就是这样:

代码语言:javascript
复制
let SomeWrapper () =
    async {
        try
            let! foo = SomeFuncThatThrows()
            DoSomethingWithFoo foo
        with
        | :? InvalidOperationException ->
            Console.Error.WriteLine "aborted"
            Environment.Exit 1
            failwith "unreachable"
    }

但是,我对这个解决方案并不百分之百满意,因为try-with太宽了,因为它还包含了对DoSomethingWithFoo函数的调用,我想把它放在try-with块之外。有什么更好的方法可以在不编写非惯用F#的情况下修复这个问题?我是否应该将编译器错误报告为微软的F# GitHub repo中的特性请求?

EN

回答 3

Stack Overflow用户

发布于 2020-01-24 15:48:31

可以将对SomeFuncThatThrows的调用包装在包含try...with的新async中。

代码语言:javascript
复制
let SomeWrapper () =
    async {
        let! foo = 
            async {
                try
                    return! SomeFuncThatThrows()
                with
                | :? InvalidOperationException ->
                    Console.Error.WriteLine "aborted"
                    Environment.Exit 1
                    return failwith "unreachable"
            }
        DoSomethingWithFoo foo
    }
票数 2
EN

Stack Overflow用户

发布于 2020-01-24 21:15:33

@nilekirk的回答有效并直接编码了您所寻找的逻辑,但是正如您在注释中所指出的,它是一个相当复杂的语法结构--您需要一个嵌套的async { .. }表达式。

您可以将嵌套的async块提取到一个单独的函数中,从而使代码更加可读性:

代码语言:javascript
复制
let SafeSomeFunc () = async {
    try
        return! SomeFuncThatThrows()
    with
    | :? InvalidOperationException ->
        Console.Error.WriteLine "aborted"
        Environment.Exit 1
        return failwith "unreachable"
}

let SomeWrapper2 () = async {
    let! foo = SafeSomeFunc ()            
    DoSomethingWithFoo foo
}

在这里,我们实际上需要将一些返回值放到with分支中。

票数 1
EN

Stack Overflow用户

发布于 2020-02-04 04:56:24

有更好的方法来解决这个问题而不用编写非惯用的F#吗?

在惯用的F#和函数式代码中,我们尽量避免使用异常和副作用。

Environment.Exit是一个很大的副作用,不要用它.

如果SomeFuncThatThrows()必须能够抛出异常(例如,您不能修改其源代码)。然后尝试将其封装到一个安全函数中,该函数返回一个Option值,然后使用该函数。

可以将整个代码重写为:

代码语言:javascript
复制
let someCondition = true

let SomeFuncThatThrows () =
    async {
        if someCondition then
            raise <| InvalidOperationException()
        return 0
    }

let SomeFunc () =
    async {
        try
            let! foo = SomeFuncThatThrows()
            return Some foo
        with _ ->
            return None
    }

let DoSomethingWithFoo (foo: int) =
    Console.WriteLine (foo.ToString())

let SomeWrapper () =
    async {
        match! SomeFunc() with
        | Some foo -> DoSomethingWithFoo foo
        | None -> Console.Error.WriteLine "aborted"
    }

[<EntryPoint>]
let main argv =
    Async.RunSynchronously (SomeWrapper ())
    0
票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/59894680

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档