2

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, e.g. 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?

Franco Tiveron
  • 2,364
  • 18
  • 34
  • Sorry, but it is unclear what you are asking... Both solutions make sense, both have some implicit assumptions affecting reusability, it looks like you completely aware of them. Using F# async model requires F# libraries of course. – Alex Netkachov Feb 24 '18 at 03:39

2 Answers2

2

see here: https://learn.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
Alex Netkachov
  • 13,172
  • 6
  • 53
  • 85
  • But in this case processResponse is called in the other thread, not in the primary one, like the await example does. – Franco Tiveron Feb 23 '18 at 09:35
  • Not sure what do you mean by "primary" here. await will continue in the captured synchronisation context: https://blogs.msdn.microsoft.com/pfxteam/2012/01/20/await-synchronizationcontext-and-console-apps/ which basically means that code after await can be continued in the thread pool and probably in another thread. – Alex Netkachov Feb 23 '18 at 12:05
  • With 'primary' I am referring to the UI thread. According to the blog you posted, "_It’s up to the implementation of the captured context to run the delegate in the right place, e.g. in the case of a UI app, that means running the delegate on the UI thread_". in the await case, `processResponse` is executed in the UI thread, but in your proposed solution it isn't. – Franco Tiveron Feb 23 '18 at 21:44
  • This will help you: https://stackoverflow.com/questions/20590982/f-cross-thread-ui-exception-in-winforms-app – Alex Netkachov Feb 24 '18 at 01:23
1

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.

Franco Tiveron
  • 2,364
  • 18
  • 34