Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

F# async web request, handling exceptions

I'm trying to use async workflows in F# to fetch several web requests.

However, some of my requests are occasionally returning errors (e.g. http 500), and I don't know how to handle this. It appears like my F# program gets stuck in an infinite loop when running in the debugger.

I'm probably missing some stuff, cause examples I seen didn't compile out of the box. First thing I found that helped was this bit of code:

type System.Net.WebRequest with
  member req.GetResponseAsync() =
    Async.BuildPrimitive(req.BeginGetResponse, req.EndGetResponse)

and then I have my bit of code to fetch the requests, which is pretty standard from examples I've seen:

let async_value = async {
  let req = WebRequest.Create(url)
  let! rsp = req.GetResponseAsync()
  return (rsp :?> HttpWebResponse).StatusCode
}

and then I try to get the result:

let status = Async.RunSynchronously(async_value)

But when I run my program in debugger, it breaks at req.EndGetResponse because server returned internal server error 500. If I keep just continuing execution, it gets in a funky loop, breaking at req.EndGetResponse (sometimes several in a row), and at let status = Async.RunSynchronously(async_value).

How do I get around the exception problem so I can get my status code? Also, do I need the type thing I did above? Or am I missing some library/dll for F#/VS 2010 Beta 1, of which this is already a part of?

I actually run several requests in parallel, using Async.RunSynchronously(Async.Parallel(my_array_of_async_values)), though I don't think that is related to the exception issue I'm having.

The fact the examples I've come across only use Async.Run rather than Async.RunSynchronously is probably an indicator I'm missing something... =/

like image 808
jessicah Avatar asked Aug 25 '09 02:08

jessicah


2 Answers

It's now called 'AsyncGetResponse' (no longer 'GetResponseAsync'). And 'Run' was renamed to 'RunSynchronously'. So I don't think you're missing anything substantial here, just name changes in the latest release.

What are your debugger settings with regard to "Tools\Options\Debugging\General\Enable Just My Code" and "Debug\Exceptions" (e.g. set to break when any first-chance CLR exception is thrown or not)? I am unclear if your question involves the program behavior, or the VS tooling behavior (sounds like the latter). This is further confounded by the fact that breakpoint/debugging 'locations' in F# Beta1 have some bugs, especially regarding async workflows, which means that the behavior you see in the debugger may look a little strange even if the program is executing properly...

Are you using VS2008 CTP or VS2010 Beta1?

In any case, it appears the exception due to a 500 response is expected, this is how WebRequest works. Here's a short demo program:

open System
open System.ServiceModel 
open System.ServiceModel.Web 

[<ServiceContract>]
type IMyContract =
    [<OperationContract>]
    [<WebGet(UriTemplate="/Returns500")>]
    abstract Returns500 : unit -> unit
    [<OperationContract>]
    [<WebGet(UriTemplate="/Returns201")>]
    abstract Returns201 : unit -> unit

type MyService() =
    interface IMyContract with
        member this.Returns500() =
            WebOperationContext.Current.OutgoingResponse.StatusCode <- 
                System.Net.HttpStatusCode.InternalServerError 
        member this.Returns201() =
            WebOperationContext.Current.OutgoingResponse.StatusCode <- 
                System.Net.HttpStatusCode.Created 

let addr = "http://localhost/MyService"
let host = new WebServiceHost(typeof<MyService>, new Uri(addr))
host.AddServiceEndpoint(typeof<IMyContract>, new WebHttpBinding(), "") |> ignore
host.Open()

open System.Net

let url500 = "http://localhost/MyService/Returns500"
let url201 = "http://localhost/MyService/Returns201"
let async_value (url:string) = 
    async {  
        let req = WebRequest.Create(url)  
        let! rsp = req.AsyncGetResponse()  
        return (rsp :?> HttpWebResponse).StatusCode
    }
let status = Async.RunSynchronously(async_value url201)
printfn "%A" status
try
    let status = Async.RunSynchronously(async_value url500)
    printfn "%A" status
with e ->
    printfn "%s" (e.ToString())
like image 101
Brian Avatar answered Oct 03 '22 21:10

Brian


You can use try...with inside the async to catch exceptions:

let async_value =
    async {
        let req = WebRequest.Create("http://unknown")
        try
            let! resp = req.AsyncGetResponse()
            return "success"
        with
        |   :? WebException as e -> return "failure"
    }
like image 29
Dmitry Lomov Avatar answered Oct 03 '22 20:10

Dmitry Lomov