简体   繁体   中英

Asynchronous computation in F#

Let client be an instance of System.Net.Http.HttpClient . In the following code

var response = await client.PostAsync(url, content);
processResponse(response);

there is no thread block between the first and the second line of code, so if we are in the UI thread, the UI remains responsive during the POST round-trip.

What is the F# code to obtain the same non-blocking behaviour? Is

let response = client.PostAsync(url, content) |> Async.AwaitTask |> Async.RunSynchronously
processResponse(response)

the correct code? I haven't clear whether RunSynchronously is blocking the current thread. If so, how do we obtain the same non-blocking behaviour as await?

EDIT

Maybe a little more context would help.

I have a Visual Studio solution with 2 projects: a WPF/WinForms app and a F# library, referenced by the app. The library provides a function/method, named FSLongWork(), which executes a long I/O operation, eg an HTTP GET or POST to a remote server using HttpClient.GetAsync/PostAsync, and returns a string. The app front-end is a simple window with a button and a label. The button click handler must: 1) Call FSLongWork() in the F# library 2) Write in the Label a content that depends on the string returned in step 1. Of course step 1 must occur asynchronously, to preserve UI responsiveness.

POSSIBLE C# app SOLUTION

F# library:

let FSLongWork() = 
    async {
        do! Async.Sleep(5000);
        return "F#"
    } |> Async.StartAsTask

C# app click handler

private async void button1_Click(object sender, EventArgs e) {
    var s = await FSLongWork();
    label1.Text = s;
}

C# app button handler registration

this.button1.Click += new System.EventHandler(this.button1_Click);

POSSIBLE F# app SOLUTION

F# library:

let FSLongWork() = 
    async {
        do! Async.Sleep(5000);
        return "F#"
    }

F# app click handler

let button1_Click (sender : obj) e = 
    async {
        let! s = FSLongWork()
        label1.Text <- s
    }

F# app button handler registration

button1.Click.Add(RoutedEventHandler(fun sender e -> button1_Click sender e |> Async.StartImmediate)

The problem I see is that the F# library function (FSLongWork) is different in the two solutions (|> Async.StartAsTask is only in the first), which is not good in term of reusability.

We can use the first implementation in F# (change let! s = FSLongWork() to let! s = FSLongWork() |> Async.AwaitTask ).

And the second implementation can be used in C# (change var s = await FSLongWork(); to var s2 = await Microsoft.FSharp.Control.FSharpAsync.StartAsTask(FSLongWork(), null, null); ).

Yet it looks a bit awkward to me: the natural F# implementation would be the second (without Async.StartAsTask ), but this requires to reference Microsoft.FSharp and the use of the rather ugly Microsoft.FSharp.Control.FSharpAsync.StartAsTask(FSLongWork(), null, null); in the C# app.

On the other hand, the first implementation (with Async.StartAsTask ), leads to a more natural use in C# (simply await FSLongWork() ), but implies am async->Task->async round-trip when used by a F# app

Is there a way to write the F# library so that a C# user doesn't need to reference FSharp.Core and without influencing how the F# function is implemented?

see here: https://docs.microsoft.com/en-us/dotnet/fsharp/tutorials/asynchronous-and-concurrent-programming/async

  1. Async.RunSynchronously will start an async workflow on another thread and await its result.
  2. Async.Start will start an async workflow on another thread, and will not await its result.

So in this case:

async {
  let! response = client.PostAsync(url, content) |> Async.AwaitTask
  processResponse response
} |> Async.Start

In terms of good integration I would expect to be able to do as follows:

F# library:

let FSLongWork() = 
    async {
        do! Async.Sleep(5000);
        return "F#"
    }

C# app click handler

private async void button1_Click(object sender, EventArgs e) {
    var s = await FSLongWork();
    label1.Text = s;
}

In other words, I would expect that the conversion between Async<> and Task<> was automatic/implicit when I use await in C#. I thought that this was possible and that I wasn't able to find the way to do it, hence my questions. Apparently, though, it is not possible and some manual conversion plumbing is required (as for example in the the possible solutions I reported).

Maybe it could be material for a future feature request.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM