F# Async.FromBeginEnd无法捕获异常

4

我遇到了困难,不知道为什么下面的代码没有捕获异常。这是我第一次使用F#中的异步操作,我相信这是一个简单的问题。

open System
open Microsoft.WindowsAzure
open Microsoft.WindowsAzure.StorageClient
open System.Windows.Forms

let mutable connection = "UseDevelopmentStorage=true;DevelopmentStorageProxyUri=http://ipv4.fiddler"

CloudStorageAccount.SetConfigurationSettingPublisher(fun cName cPublisher ->
                                                      cPublisher.Invoke connection |> ignore)

let storageAccount = CloudStorageAccount.Parse connection

let createTable tableName =
        let client = storageAccount.CreateCloudTableClient()
        async{
            try
                do! Async.FromBeginEnd(tableName, client.BeginCreateTable , client.EndCreateTable)
                MessageBox.Show "Created" |>ignore
            with 
            | :? StorageClientException -> printfn "failed"; MessageBox.Show("failed to create table") |> ignore
            | _ -> printfn "Failed with unknown exception"
        } |> Async.Start

[<EntryPoint; STAThread>]
let main(args) =
    let form = new Form()
    let btn = new Button(Text = "Click")
    btn.Click.AddHandler(fun _ _ -> createTable "SomeNewTable")
    form.Controls.Add btn
    let result = form.ShowDialog()
    0

如果我运行这个程序并且表已经被创建了,它会显示一个 StorageClientException 类型的异常未在代码中处理,具体指向 FromBeginEnd 调用的 client.EndCreateTable 部分。

有点旁题,但如果你的异常处理程序被调用,它可能不会从 UI 线程中调用,所以在那里使用 MessageBox.Show 不是一个好主意。 - Joel Mueller
如果您将Async.Start替换为Async.RunSynchronously,会有什么不同吗? - Joel Mueller
是的,messageBox目前只是用于测试。稍后它将被替换为回调函数。使用RunSynchronously进行交换并不会改变任何东西 :( - Dylan
你的 .NET 版本是哪个?你是否已安装 VS2010 SP1? - ildjarn
3个回答

4
这似乎是一个在VS2010 SP1的FSharp.Core中修复的问题。.NET SynchronizationContext的行为发生了改变(我认为是在.NET 4.0 SP1中),我们需要相应地更改F#运行时,以使async能够正确处理线程亲和性。
我认为你可以在这里获取更新的FSharp.Core:http://www.microsoft.com/download/en/details.aspx?DisplayLang=en&id=15834

很遗憾,我没有成功。我已经安装了SP1,在我按照链接操作时得到了修复选项,但在Visual Studio 11预览版中仍然看到相同的行为。 - Dylan
我无法在我的SP1盒子或Dev11盒子上使用@pad的复制进行重现。那个复制对你有用吗? - Brian

2
由于Don Syme的贡献, 解决方案是关闭“仅限我的代码”调试。 调试 -> 选项和设置 -> 通用 -> 取消选中“启用仅限自己的代码(仅托管)”
这仍然是Windows 8消费者预览版附带的Visual Studio 11 beta的问题。

这绝对不是解决方案。唯一发生的事情是现在调试器在错误发生时无法捕获它! - David Pfeffer
我认为这是与VS和F#有关的问题。正如一些人在这里所说,没有调试器附加时它的行为符合预期。这个解决方案让我能够重新开始解决这个问题。这并不是一个“掩耳盗铃”的修复方法,类似于忽略问题,因为它在运行时是有效的。 - Dylan
通过禁用“仅限我的代码”,您也禁用了VS捕获未处理异常的能力。 - David Pfeffer

2
如果异常的原因是表已经存在,为什么不使用BeginCreateTableIfNotExist/EndCreateTableIfNotExist 呢。
更新:
这个错误与Windows Azure无关。我能够使用一个简单的程序复现相同的行为:
open System
open System.Windows.Forms

let bufferData = Array.zeroCreate<byte> 100000000

let async1 filename =        
    async{
        try 
            use outputFile = System.IO.File.Create(filename)
            do! outputFile.AsyncWrite(bufferData)   
            MessageBox.Show("OK") |> ignore         
        with 
        | :? ArgumentException -> printfn "Failed with ArgumentException"; MessageBox.Show("Failed with ArgumentException") |> ignore         
        | _ -> printfn "Failed with unknown exception"; MessageBox.Show("Failed with unknown exception") |> ignore         
    } |> Async.Start

let main(args) =
    let form = new Form(Text = "Test Form")
    let button1 = new Button(Text = "Start")
    let button2 = new Button(Text = "Start Invalid", Top = button1.Height + 10)        
    form.Controls.AddRange [| button1; button2; |]
    button1.Click.Add(fun args -> async1 "longoutput.dat")
    // Try an invalid filename to test the error case.
    button2.Click.Add(fun args -> async1 "|invalid.dat")    
    let result = form.ShowDialog()
    0

let _ = main([||])

奇怪的是,这段代码在 F# Interactive 中运行良好,但在 Visual Studio 中作为 Windows 应用程序进行调试时无法捕获异常(无论是 Debug 还是 Release 配置)。更奇怪的是,如果作为应用程序在 Visual Studio 外部执行,则再次正常工作。

如果你想知道,这个程序是从a MSDN example改编而来的,它展示了相同的问题。

更新2:

类似的问题已经在http://cs.hubfs.net/topic/Some/0/59516上被提出。正如 @ildjarn 和 @Brian 指出的那样,这个 bug 已经在 VS2010 SP1 中修复。如果没有安装 VS2010 SP1,你可以使用 F# Interactive 测试你的代码,并在 VS 外部执行应用程序,不会有任何问题。


1
这个例子看起来还好,但如果涉及到从 FromBeginEnd 结构中创建的其他操作,那将会使对表服务的请求数量翻倍,对于一个高流量的网站来说,这是非常糟糕的。 - Dylan
@JoelMueller 我非常确定,如果你通过 Fiddler 运行请求,你会看到一个 GET 请求后跟着一个 CREATE 请求。但无论如何,ildjarn 是正确的。这是一个混合应用程序,所以我不能指望总是能够访问表服务。 - Dylan
说句心里话,你的更新让我想知道问题是否由于编译应用程序中缺少“同步上下文”而引起的... - kvb
1
当我将您的编辑作为已编译应用程序运行时,它对我来说表现得很“正常”--即,我会收到一个消息框,上面写着“ArgumentException失败”。您的.NET版本是什么?您是否安装了VS2010 SP1? - ildjarn
我正在使用F# 2.0/.NET 4.0,但没有安装VS2010 SP1 :) - pad
显示剩余4条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接