I am trying to create the example class below (taken from here) in F#.
public class LoggingMiddleware
{
private AppFunc next;
public LoggingMiddleware(Func<IDictionary<string, object>, Task> next)
{
this.next = next;
}
public async Task Invoke(IDictionary<string, object> environment)
{
Console.WriteLine("Begin Request");
await next.Invoke(environment);
Console.WriteLine("End Request");
}
}
However, I'm struggling both with converting between the typical .NET types and the typical F# types and using the F# async workflows to achieve what is happening with the C# await.
The biggest (current) issue with F# async workflows and .Net task is that there is no direct analogy to a Task that has no return type.
From this question, I'll use this function binding to await Tasks with no return value:
let awaitTask = Async.AwaitIAsyncResult >> Async.Ignore
With that, you can directly translate your example class to:
type FSharpMiddleware(next: Func<IDictionary<string,obj>, Task>) =
member this.Invoke (environment: IDictionary<string,obj>) : Task =
async {
printfn "Begin Request"
do!
awaitTask <| next.Invoke environment
printfn "End Request"
} |> Async.StartAsTask :> Task
Related
In C# you can annotate methods with async like this:
class Foo
{
public async void Bar()
{
}
}
This is different to an F# async; I believe that in F# these are called tasks.
So, how do I write a C#-style async member function in F#?
// Not real code
type Foo () =
member async this.Bar () =
()
The solution must compile to IL with the same public interface as the C# above.
A C# async method is just a method returning a value of type Task<T>. The C# compiler uses the async keyword to determine that you are allowed to use await inside the code block, but as far as I know, it is not represented in any way in the compiled code.
You say "the solution must compile to the same IL" - that's not going to be easily possible, because F# implements asynchronous operations differently. However, you can get it to compile to IL that has the same public interface using something like this:
type Foo () =
member this.Bar () = Async.StartAsTask <| async {
// yadda yadda
}
The asynchronous operation is implemented using standard F# async workflow, so under the cover, this creates an F# Async<T>, but the Async.StartAsTask operation turns that into a Task<T> type, which is what C# expects.
EDIT: Another alternative is to use the TaskBuilder computation expression, which lets you directly create .NET tasks using something like this:
type Foo () =
member this.Bar () = task {
// yadda yadda
}
Async.StartAsTask should do it. This will give you Threading.Tasks.Task<'a>
let bar =
async{
return ()
}
|>Async.StartAsTask
I need to write a function that will return a Task in F#.
The closest I came to is:
let Test : Async<bool> =
async {
printfn "3"
true
}
but.. that doesn't work; the compiler says I'm giving an Async instead of an Async.
Am I correct to understand that there is no Task object and the return type should be defined as Async?
What is the correct syntax for this?
F# doesn't have a computation expression for the Task construct built-in by default.
However if you want to use it, you can (thanks to rspeele). You'll have to add this as a nuget package: https://github.com/rspeele/TaskBuilder.fs
Then you can use it like this:
open FSharp.Control.Tasks.V2
let taskMethod : Task<bool> =
task {
printfn "3"
return true
}
If you want to stick with the built-in Async approach you can do something like this:
open System.Threading.Tasks
let asyncMethod : Async<bool> =
async {
printfn "bob"
return true
}
let taskMethod : Task<bool> =
asyncMethod |> Async.StartAsTask
I am having problems with a test in a netcoreapp2.2 .net core test project.
Before the tests starts I need to fetch some data that will be shared between the tests.
However, when running the following test from command line it will hang.
Executing the test like this:
dotnet test --filter "Test async initialization"
The faulty code looks like this:
let c = new HttpClient (BaseAddress = (Uri "https://swapi.co/api/people/1/"))
let luke =
async {
return! c.GetStringAsync "" |> Async.AwaitTask
} |> Async.RunSynchronously
[<Fact>]
let ``Test async initialization`` () =
Assert.NotNull(luke)
While if I put the creation of the HttpClient inside the luke fetcher like this it works:
let luke =
let c = new HttpClient (BaseAddress = (Uri "https://swapi.co/api/people/1/"))
async {
return! c.GetStringAsync "" |> Async.AwaitTask
} |> Async.RunSynchronously
[<Fact>]
let ``Test async initialization`` () =
Assert.NotNull(luke)
This means I can't share the same HttpClient between different fetchers.
Anyone knows what is going on, and how to share the same client between multiple functions?
The problem is caused because the "initialization" code isn't really initialization code. Those are just two static fields that will be evaluated only when requested. If you debug the unit test you'll see that c and luke execute only when execution reaches the line
Assert.NotNull(luke)
If you use a decompiler like JustDecompile you'll see that the module's code is placed in a static class called Tests$ whose static constructor initializes its own c and luke properties. Test async initialization is placed in a Tests class with its own c and luke properties that delegate to the Tests$ class.
Long story sort, none of that "initialization" code runs until the value of luke is requested. I don't know why that ends up blocking the test, most likely there's a conflict with the test runner. It's enough that the initialization code doesn't run at initialization.
To make the initialization code run when it should, a "classic" test type can be used :
namespace MyTests
open System
open Xunit
open System.Net.Http
open Xunit.Abstractions
type Tests() =
static let c = new HttpClient (BaseAddress = (Uri "https://swapi.co/api/people/1/"))
static let luke =
async {
return! c.GetStringAsync "" |> Async.AwaitTask
} |> Async.RunSynchronously
static do
//Pity we can't actually print here
printfn "Even more initialization!"
[<Fact>]
let ``Test async initialization`` () =
Assert.NotNull(luke)
The static bindings in this case are executed before any of the tests, as they should, and the code doesn't block. This initialization will happen only once.
To capture output the test class constructor should accept an ITestOutputHelper parameter. That's easy to do now that we have a test class :
type Tests(output:ITestOutputHelper) =
...
[<Fact>]
let ``Test async initialization`` () =
Assert.NotNull(luke)
output.WriteLine "It worked!"
Per-test initialization should go in a do block :
type Tests(output:ITestOutputHelper) =
do
output.WriteLine "This prints before each test"
When I try to call an async method that is in C# library from my F# code.
I get the following compilation error.
This expression was expected to have type Async<'a> but here has type Threading.Thread.Tasks.Task
SendMessageAsync is in C# library and returns Threading.Thread.Tasks.Task<MyType>
let sendEmailAsync message =
async {
let! response = client.SendMessageAsync(message)
return response
}
For converting between Task<'T> and Async<'T> there is a built-in Async.AwaitTask function.
To convert between a plain Task and Async<unit> you can create a helper function:
type Async with
member this.AwaitPlainTask (task : Task) =
task.ContinueWith(fun t -> ())
|> Async.AwaitTask
Then you can call it like this:
let sendEmailAsync message =
async {
let! response = Async.AwaitPlainTask <|client.SendMessageAsync(message)
return response
}
Of course, in this case, the response can't be anything other than (), so you might as well just write:
let sendEmailAsync message = Async.AwaitPlainTask <|client.SendMessageAsync(message)
I have the following code in F#:
let CreateSampleDataFromJson<'T>(path) =
let uri = new Uri(path)
async {
let file = StorageFile.GetFileFromApplicationUriAsync(uri)
let jsonText = FileIO.ReadTextAsync(file)
return JsonObject<'T>.Parse(jsonText)
}
The problem I'm having is that file is an IAsyncOperation<StorageFile> and not a StorageFile as ReadTextAsync expects.
In C# you can do something similar to this:
var file = await StorageFile.GetFileFromApplicationUriAsync(uri)
i.e.
public async Task<T> CreateSampleDataFromUrl<T>(string path)
{
var uri = new Uri(path);
var file = await StorageFile.GetFileFromApplicationUriAsync(uri);
var jsonText = await FileIO.ReadTextAsync(file);
return JsonObject<T>.Parse(jsonText);
}
The problem is that I don't know how to await an IAsyncOperation in F#. The usual let! doesn't work. i.e. the following fails to compile:
async {
let! file = StorageFile.GetFileFromApplicationUriAsync(uri)
With the compiler error:
error FS0001: This expression was expected to have type Async<'a> but here has type IAsyncOperation<StorageFile>
I found a document that said there's an AsTask() extension method defined in the System.WindowsRuntimeSystemExtensions class which I can use as follows:
let! file = StorageFile.GetFileFromApplicationUriAsync(uri).AsTask() |> Async.AwaitTask
Is there a standard way of doing this or something available in an F# library somewhere that makes this a bit nicer?
Your solution seems fine by me. If you're looking for a nicer syntax, how about rolling it into a function like this (without the possibly gratuitous type annotations):
let await<'a> (op: IAsyncOperation<'a>) : Async<'a> =
op.AsTask() |> Async.AwaitTask
This will give you the almost exact same syntax you'd see in c#:
async {
let! file = await <| StorageFile.GetFileFromApplicationUriAsync(uri)
...
}
The compiler errors you were getting with your previous approaches are to be expected. All async workflow cares about is the F#-specific Async type. This type gives you a way to interop with the rest of .NET world through Tasks, but that's it. IAsyncOperation is from a 'different part of the world', I wouldn't expect F# core libraries to support it anytime soon.