【问题标题】:How to catch exception thrown by Task run with Async.AwaitTask如何捕获使用 Async.AwaitTask 运行的任务引发的异常
【发布时间】:2018-04-06 05:31:30
【问题描述】:

编辑:事实证明这是一个 F# 错误,可以通过使用自定义选项类型而不是 Fsharp 的“选项”来解决。

在 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

我需要一些方法来捕捉“测试”异步函数中的异常,防止它“冒泡”。

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

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

“ArgumentException 未被用户代码处理。”

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

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

【问题讨论】:

  • 很抱歉让你失望了,但是这段代码对我来说非常好(异常被try-with捕获)。使用 F# 3.1。
  • 它不适合我。见截图:i.gyazo.com/4de542103442e020e20b46bd8d7541e4.pngVisual Studio 2013, .net framework 4.5.1, Fsharp 3.1, Fsharp.core.dll 4.3.1
  • @seguso,Visual Studio 调试器中断的事实并不意味着异常未处理。尝试在没有调试器的情况下运行应用程序,它会崩溃吗?你也可以去Debug -&gt; Windows -&gt; Exception Settings看看ArgumentException那里有没有勾选?
  • @takemyoxigen:Visual Studio 说“ArgumentException 未被用户代码处理”这一事实误导了我。请参阅另一个屏幕截图:i.gyazo.com/883f546c00255b210e53cd095b876cb0.png 但是,如果我按继续,令人难以置信的是,程序会正确终止。所以你是对的。但是,在 Visual Studio 2013 中我没有 Debug > Windows > 异常设置,所以我不明白发生了什么。
  • @seguso 对不起,我的错。在 VS 2013 中,它是 Debug > Exceptions

标签: exception asynchronous exception-handling f# task-parallel-library


【解决方案1】:

TL;DR: 异常确实被捕获,它似乎在异步中返回 None,这在这里令人困惑 - 结果为 null,而不是 None,这会破坏模式匹配并且通常是麻烦。使用你自己的联合类型而不是 Option 来处理它。

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

首先,在 Async.Catch 中返回 Some/None 会使其无用。 Async.Catch 返回Choice1Of2(val) 除非有未捕获的异常,因此代码原样(如果有效)将返回Choice1Of2(Some(string)) 无异常,或Choice1Of2(None) 有异常。要么使用 try/with 并自己处理异常,要么只使用 Async.Catch。

实验:

我们正在捕捉的证据: 将调试输出添加到“with”。代码确实到达那里(添加断点或查看调试输出以获取证据)。我们得到Choice1Of2(null),而不是exc 中的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 的选择,我们得到 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 时,它​​变为 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

不要使用 Option: 有趣的是,如果您使用自定义联合类型,它可以完美运行:

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 也适用于新的 Union:

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 中的以下内容为空。

[<EntryPoint>]
let main argv = 

    let moreTest = async {
        return None
    }

    let moreTestRes = Async.RunSynchronously moreTest
    0

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

【讨论】:

  • 总而言之,1) 这是 f# 中的错误,而不是我的代码中的错误。问题是 res = null 而不是 None。 2)如果我使用自定义联合而不是选项,问题就消失了。 Res = MyCustomNone,不为空。 3)但是,即使我使用自定义联合而不是无,调试器中断的问题,并且错误地说“未处理的异常”,仍然存在:i.gyazo.com/a79583a859f7eac8d83085e8c50f5ecf.png 正确?
  • 好吧,从技术上讲,您的问题的答案是捕获了异常。您看到异常是因为您总是在该异常上中断。取消选中图像中可见的复选框,单击确定/继续,然后再次尝试运行代码——你不应该在 vs 中抛出异常,只是在 with 中被捕获。我的其余答案涵盖了为什么 exc 上的模式匹配不起作用等 - 这似乎是由 F# 错误引起的。
  • 你说:“你总是打破那个例外”。对不起,但在我看来并非如此。复选框显示“当此异常类型为用户未处理时中断”。它没有说“总是休息”。我正在使用 try-catch 处理异常,所以在我看来对话框不应该出现。
  • 如果点击“全部重置”,是否勾选了用户未处理的参数异常?另外,如果您转到工具->选项,调试->常规并取消选中“仅我的代码”,会发生什么?抛出异常后VS还会报错吗?
  • DownloadStringTaskAsync 是您正在调用的东西,它正在引发异常,并且异常进入 Async.AwaitTask (这不是您的代码)。这意味着您的异常已发送到非用户代码 - 因此您在用户未处理时有一个中断,因为您在异常到达非用户代码之前没有处理它。禁用 Just My Code 意味着它将让异常冒泡通过框架代码,并且只有在真正未处理时才会中断。
猜你喜欢
  • 1970-01-01
  • 2022-10-02
  • 2012-03-14
  • 1970-01-01
  • 2016-07-11
  • 1970-01-01
  • 2018-03-13
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多