如何使用 Async.AwaitTask 捕获任务 运行 抛出的异常

How to catch exception thrown by Task run with Async.AwaitTask

编辑:这原来是一个 F# 错误,可以通过使用自定义选项类型而不是 Fsharp 的 "Option".

在 F# 中,我试图用 Async.AwaitTask 调用 .net 任务。该任务抛出异常,我似乎无法使用 try-catch 或 Async.Catch 捕获它。而且我知道没有其他方法可以捕获异常。解决办法是什么?问题的原因是什么?感谢您的任何解释。

这是一些测试代码,显示我未能捕获 DownloadStringTaskAsync 抛出的异常:

open System
open System.Net

[<EntryPoint>]
let main argv = 

    let test =
        async{
        let! exc = Async.Catch( async{
            try
                let w = new Net.WebClient();
                let! str = Async.AwaitTask (w.DownloadStringTaskAsync "") // throws ArgumentException
                return Some str
            with 
            | _ -> 
                return None // not caught
            }
            )
        match exc with
        | Choice1Of2 r -> return r
        | Choice2Of2 ext -> return None  // not caught
        }

    let res = Async.RunSynchronously(test)
    let str = Console.ReadLine();
    0 // return an integer exit code

我需要一些方法来捕获 "test" 异步函数中的异常,防止它进入 "bubble up".

我找到了 this 相关问题,但我似乎无法根据我的需要调整答案(涉及任务,而不是任务<'a>)。

编辑:这是显示问题的屏幕截图:https://i.gyazo.com/883f546c00255b210e53cd095b876cb0.png

"ArgumentException was unhandled by user code."

(Visual Studio 2013,.NET 4.5.1,Fsharp 3.1,Fsharp.core.dll 4.3.1.0。)

会不会是代码是正确的,但某些 Fsharp 或 Visual Studio 设置阻止了异常被捕获?

TL;DR: 确实捕获了异常,它似乎是 returning None 异步,这在这里令人困惑 - 结果是null,而不是 None,它搞砸了模式匹配并且通常很麻烦。使用您自己的联合类型而不是 Option 来处理它。

编辑: 哦,@takemyoxygen 关于您为何立即看到它的评论是正确的。 Debug->Exceptions,或者在屏幕截图中可见的弹出窗口中取消勾选“当此异常类型为用户未处理时中断”。看起来 VS 在 THROW 上中断,而不是在未处理时中断。

首先,在 Async.Catch 中 returning Some/None 使其无用。 Async.Catch returns Choice1Of2(val) 除非有未捕获的异常,所以代码原样(如果有效)将 return Choice1Of2(Some(string)) 无一例外,或者 Choice1Of2(None) 有例外。要么使用 try/with 并自己处理异常,要么只使用 Async.Catch.

实验:

证明我们正在捕获: 将调试输出添加到“with”。代码确实到达那里(添加断点或查看调试输出以获取证据)。我们在 exc 中得到 Choice1Of2(null),而不是 Choice1Of2(None)。诡异的。见图:http://i.imgur.com/e8Knx5a.png

open System
open System.Net

[<EntryPoint>]
let main argv = 

    let test =
        async{
        let! exc = Async.Catch( async{
            try
                let w = new Net.WebClient();
                let! str = Async.AwaitTask (w.DownloadStringTaskAsync "") // throws ArgumentException
                return Some str
            with 
            | _ -> 
                System.Diagnostics.Debug.WriteLine "in with" // We get here.
                return None // not caught
            }
            )
        match exc with
        | Choice1Of2 r -> return r
        | Choice2Of2 ext -> return None  // not caught
        }

    let res = Async.RunSynchronously(test)
    let str = Console.ReadLine();
    0 // return an integer exit code

删除 Async.Catch: 不要使用 Async.Catch(尽管保留调试输出)。同样,我们得到了调试输出,所以我们捕获了异常,并且由于我们没有包装在来自 Async.Catch 的 Choice 中,我们得到的是 null 而不是 None。仍然很奇怪。

open System
open System.Net

[<EntryPoint>]
let main argv = 

    let test = async {
        try
            let w = new Net.WebClient();
            let! str = Async.AwaitTask (w.DownloadStringTaskAsync "") // throws ArgumentException
            return Some str
        with 
        | _ -> 
            System.Diagnostics.Debug.WriteLine "in with"
            return None }

    let res = Async.RunSynchronously(test)
    let str = Console.ReadLine();
    0 // return an integer exit code

只使用Async.Catch:不要使用try/with,只使用Async.Catch。这样效果好一些。在 exc 的模式匹配中,我有一个包含异常的 Choice2Of2。但是,当 None 被 return 从最外面的异步中编辑出来时,它变为 null。

open System
open System.Net

[<EntryPoint>]
let main argv = 

    let test = async {
        let! exc = Async.Catch(async {
            let w = new Net.WebClient();
            let! str = Async.AwaitTask (w.DownloadStringTaskAsync "") // throws ArgumentException
            return str })
            
       match exc with      
       | Choice1Of2 v -> return Some v
       | Choice2Of2 ex -> return None  
       }

    let res = Async.RunSynchronously(test)
    let str = Console.ReadLine();
    0 // return an integer exit code

不要使用选项: 有趣的是,如果您使用自定义联合类型,它会完美地工作:

open System
open System.Net

type UnionDemo =
    | StringValue of string
    | ExceptionValue of Exception

[<EntryPoint>]
let main argv = 

    let test = async {
        let! exc = Async.Catch(async {
            let w = new Net.WebClient();
            let! str = Async.AwaitTask (w.DownloadStringTaskAsync "") // throws ArgumentException
            return str })
            
       match exc with      
       | Choice1Of2 v -> return StringValue v
       | Choice2Of2 ex -> return ExceptionValue ex 
       }

    let res = Async.RunSynchronously(test)
    let str = Console.ReadLine();
    0 // return an integer exit code

使用 try/with 而没有 Async.Catch 也适用于新联盟:

open System
open System.Net

type UnionDemo =
    | StringValue of string
    | ExceptionValue of Exception

[<EntryPoint>]
let main argv = 

    let test = async {
        try
            let w = new Net.WebClient();
            let! str = Async.AwaitTask (w.DownloadStringTaskAsync "") // throws ArgumentException
            return StringValue str
        with
        | ex -> return ExceptionValue ex }

    let res = Async.RunSynchronously(test)
    let str = Console.ReadLine();
    0 // return an integer exit code

即使联合定义如下:

type UnionDemo =
    | StringValue of string
    | ExceptionValue

以下在 moreTestRes 中为 null。

[<EntryPoint>]
let main argv = 

    let moreTest = async {
        return None
    }

    let moreTestRes = Async.RunSynchronously moreTest
    0

我不知道为什么会这样(在 F# 4.0 中仍然会发生),但肯定会捕获异常,但 None->null 搞砸了。