https://github.com/rspeele/fsharpasyncmethodbuilder
Write F# Async<T> code in C# with async/await
https://github.com/rspeele/fsharpasyncmethodbuilder
Last synced: 25 days ago
JSON representation
Write F# Async<T> code in C# with async/await
- Host: GitHub
- URL: https://github.com/rspeele/fsharpasyncmethodbuilder
- Owner: rspeele
- License: cc0-1.0
- Created: 2017-07-07T00:17:34.000Z (almost 8 years ago)
- Default Branch: master
- Last Pushed: 2017-07-15T03:42:54.000Z (almost 8 years ago)
- Last Synced: 2025-02-10T05:42:35.083Z (3 months ago)
- Language: F#
- Size: 13.7 KB
- Stars: 2
- Watchers: 3
- Forks: 1
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: COPYING
Awesome Lists containing this project
README
# Use F#'s `Async` type in C#
This is a proof-of-concept implementing a method builder compatible with C#
7.0's "arbitrary async returns" feature, for F#'s `Async<_>` type (aka
`FSharpAsync` when used from C#).That is, it lets you write F# async expressions directly in C# using async/await
syntax, instead of writing `Task`s and using `FSharpAsync.AwaitTask` to
convert them.# Example
```csharp
using System;
using System.Threading.Tasks;
using Microsoft.FSharp.Control;
using FAsync;public static class ExampleClass
{
// FAsync is a simple wrapper around FSharpAsync.
// It only exists to point to the compiler to the async method builder to use.
// You can get out the wrapped FSharpAsync easily.
public static FAsync DelayedFactorial(int x)
{
// You can wait Tasks
await Task.Delay(1);
// ... and F# Asyncs
await FSharpAsync.Sleep(1);
// ... and other FAsyncs
if (x > 1)
return x * await DelayedFactorial(x - 1);
else
return x;
}public static void ExampleUsage()
{
// FAsync has an implicit conversion to FSharpAsync.
FSharpAsync fac = DelayedFactorial(1);
// Like in F#, none of the code in the body of an FAsync method runs until it's started.
var result = FSharpAsync.RunSynchronously(fac, null, null);
Console.WriteLine(result);
}
}
```See [Tests/Program.cs](Tests/Program.cs) for more examples.
To use the code yourself, you can copy
[FAsync.fs](FSharpAsyncMethodBuilder/FAsync.fs) into an F# project, add a
reference to NuGet package System.Threading.Tasks.Extensions, then reference
that F# project from your C# projects.# Motivation
This would make sense to use if you have a lot of F# code currently using `async
{ ... }` builders, and are sprinkling in some C# code that interops with it.
It saves some of the overhead of constantly converting between the two task types.I suspect in the real world more systems are the other way around, and would
benefit more from my other project,
[TaskBuilder.fs](https://github.com/rspeele/TaskBuilder.fs), which lets you
write `Task`s from F# code.For me, this was just an exercise in learning how to use the new arbitrary async
return feature in C#, which isn't well documented yet. It may serve as a useful
reference for others trying to convert F# computation expressions to C# async
method builders.Unfortunately, there aren't that many more applications for this technique,
because the async/await feature in C# is not as general-purpose as the
computation expression feature in F#. Any async method builder must support
awaiting _any_ type that implements that awaitable pattern.This means that you can't reasonably write a builder for a monad that has
nothing to do with asynchronous programming, say, `Option` or `Result`, since users would be allowed to await TPL tasks, `ValueTask`, etc.
within your builder. What's more, your `MyMonad` type would appear awaitable
to every other builder, which probably won't be able to handle it the way you
intend.It might be handy for some other async-ish things like
[Hopac](https://github.com/Hopac/Hopac), though I haven't looked into that.# Caveat
There is one subtle but important difference between these async methods and the
F# equivalents. Each `Async<_>` logically represents a "recipe" for a task --
something like a `unit -> Task<_>`. When you have an `Async<_>` in F#, you can
run it as many times as you want with `Async.StartAsTask`, including in parallel
with itself. It'll work fine as long as you don't have them sharing mutable
state created outside the `async` block.With the C# async/await implementation, calling the method creates a single
instance of a state machine which encapsulates the local variables of the
method. If you call the method once and start the resulting `FSharpAsync`
multiple times concurrently, each running `Task` will use the same state
machine, and they'll trample over each others' local variables with disastrous
results. So don't do that!