Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

https://github.com/kekyo/FSharp.Control.FusionTasks

F# Async workflow <--> .NET Task/ValueTask easy seamless interoperability library.
https://github.com/kekyo/FSharp.Control.FusionTasks

async async-enumerable csharp dotnet fsharp interoperability task valuetask

Last synced: 3 months ago
JSON representation

F# Async workflow <--> .NET Task/ValueTask easy seamless interoperability library.

Awesome Lists containing this project

README

        

# F# FusionTasks

![FusionTasks](https://raw.githubusercontent.com/kekyo/FSharp.Control.FusionTasks/master/Images/FSharp.Control.FusionTasks.128.png)

## Status

| | main | devel |
|:---|:--:|:--:|
| NuGet Package | [![NuGet FusionTasks](https://img.shields.io/nuget/v/FSharp.Control.FusionTasks.svg?style=flat)](https://www.nuget.org/packages/FSharp.Control.FusionTasks) | |
| Continuous integration | [![RelaxVersioner CI build (main)](https://github.com/kekyo/FSharp.Control.FusionTasks/workflows/.NET/badge.svg?branch=main)](https://github.com/kekyo/FSharp.Control.FusionTasks/actions) | [![RelaxVersioner CI build (main)](https://github.com/kekyo/FSharp.Control.FusionTasks/workflows/.NET/badge.svg?branch=devel)](https://github.com/kekyo/FSharp.Control.FusionTasks/actions) |

-----

## What is this?

* F# Async workflow <--> .NET Task/ValueTask easy seamless interoperability library.
* Sample code (F# side):

``` fsharp
let asyncTest = async {
use ms = new MemoryStream()

// FusionTasks directly interpreted System.Threading.Tasks.Task class in F# async-workflow block.
do! ms.WriteAsync(data, 0, data.Length)
do ms.Position <- 0L

// FusionTasks directly interpreted System.Threading.Tasks.Task class in F# async-workflow block.
let! length = ms.ReadAsync(data2, 0, data2.Length)
do length |> should equal data2.Length
}
```

* Sample code (C# side):

``` csharp
using System.Threading.Tasks;
using Microsoft.FSharp.Control;

public async Task AsyncTest(FSharpAsync asyncIntComp)
{
// FusionTasks simple usage F#'s Async direct awaitable.
await FSharpAsync.Sleep(500);
Console.WriteLine("Awaited F# async function (unit).");

// FusionTasks simple usage F#'s Async direct awaitable.
var result = await asyncIntComp;
Console.WriteLine("Awaited F# async function: Result=" + result);
}
```

-----

## Features

* Easy interoperable .NET Task/ValueTask <--> F#'s Async.
* F# async workflow block now supports directly .NET Task/ValueTask handle with let!, do! and use!.
* .NET (C# async-await) now supports directly F#'s Async.
* SyncronizationContext capture operation support (F#: AsyncConfigure method / .NET (C#) AsAsyncConfigured method)
* .NET now supports standard asynchronous sequence called `IAsyncEnumerable`, FusionTasks supports it with `for` expression.

## Benefits

* Easy interoperability, combination and relation standard .NET OSS packages using Task/ValueTask and F#'s Async.
* F# 6.0/4.5 with .NET 6.0/5.0, .NET Core 3.0/2.0 (or higher), .NET Standard 1.6/2.0 and .NET Framework 4.5/4.6.1/4.8.
* Ready to LINQPad 5.

-----

## Environments

* F# 6.0 or higher/4.5
* .NET 6.0
* .NET 5.0
* .NET Core 3.0/2.0 or higher
* .NET Standard 1.6/2.0/2.1
* .NET Framework 4.5/4.6.1/4.8

Combination chart:

| .NET BCL | F# | Details |
|:----|:----|:----|
| .NET 6.0 | F# 6.0 or higher | |
| .NET 5.0 | F# 6.0 or higher | |
| .NET Core 3.1, 3.0 | F# 6.0 or higher | (3.0 is deprecated) |
| .NET Core 2.,2 2.1, 2.0 | F# 6.0 or higher | (2.0 is deprecated) |
| .NET Standard 2.1, 2.0 | F# 6.0 or higher | |
| .NET Standard 1.6 | F# 4.5 | |
| .NET Framework 4.8, 4.6.1 | F# 6.0 or higher | |
| .NET Framework 4.5 | F# 4.5 | |

-----

## How to use

* Search NuGet package and install "FSharp.Control.FusionTasks".
* F# use, autoopen'd namespace "FSharp.Control". "System.Threading.Tasks" is optional.
* C# use, using namespace "System.Threading.Tasks". "Microsoft.FSharp.Control" is optional.

## Samples

### Basic async workflow:

``` fsharp
let asyncTest = async {
use ms = new MemoryStream()

// FusionTasks directly interpreted System.Threading.Tasks.Task class in F# async-workflow block.
// Sure, non-generic Task mapping to Async.
do! ms.WriteAsync(data, 0, data.Length)
do ms.Position <- 0L

// FusionTasks directly interpreted System.Threading.Tasks.Task class in F# async-workflow block.
// Standard usage, same as manually used Async.AwaitTask.
let! length = ms.ReadAsync(data2, 0, data2.Length)
do length |> should equal data2.Length
}
```

### Without async workflow:

``` fsharp
use ms = new MemoryStream()

// Manually conversion by an operator "Async.AsAsync" : Task --> Async<'T>
let asy = ms.ReadAsync(data, 0, data.Length) |> Async.AsAsync
let length = asy |> Async.RunSynchronosly
```

### Without async workflow (CancellationToken):

``` fsharp
use ms = new MemoryStream()
let cts = new CancellationTokenSource()

// Produce with CancellationToken:
// TIPS: FusionTasks cannot handle directly CancellationToken IN ASYNC WORKFLOW.
// Because async workflow semantics implicitly handled CancellationToken with Async.DefaultCancellationToken, CancellationToken and CancelDefaultToken().
// (CancellationToken derived from Async.StartWithContinuations() in async workflow.)
let asy = ms.ReadAsync(data, 0, data.Length).AsAsync(cts.Token)
let length = asy |> Async.RunSynchronosly
```

### Handle Task.ConfigureAwait(...) (Capture/release SynchContext):

``` fsharp
let asyncTest = async {
use ms = new MemoryStream(...)

// We can use ConfigureAwait() on let!/do!.
let! length = ms.ReadAsync(data, 0, data.Length).ConfigureAwait(false)
}
```

NOTE: Older released contains `AsyncConfigure(bool)` method, but it was obsoleted.
Because it existed for avoiding PCL strange linking errors.

### Delegate async continuation - works like TaskCompletionSource<T>:

``` fsharp
open System.Threading

let asyncCalculate() =
// Create AsyncCompletionSource<'T>.
let acs = new AsyncCompletionSource()

// Execution with completely independent another thread...
let thread = new Thread(new ThreadStart(fun _ ->
Thread.Sleep(5000)
// If you captured thread context (normally continuation or callbacks),
// can delegation async continuation using AsyncCompletionSource<'T>.
acs.SetResult(123 * 456)))
thread.Start()

// Async<'T> instance
acs.Async
```

### Standard asynchronous sequence IAsyncEnumerable<T>:

``` fsharp
let asyncTest = async {
// FusionTasks directly interpreted System.Collection.Generic.IAsyncEnumerable in
// F# async-workflow for expression.
for value in FooBarAccessor.EnumerableAsync() do
// Totally asynchronous operation in each asynchronous iteration:
let! result = value |> FooBarCollector.calculate
do! output.WriteAsync(result)

// ... (Continuation is asynchronously behind `for` loop)
}
```

And, we can use `IAsyncEnumerable.ConfigureAwait(bool)` on it.

NOTE: `IAsyncEnumerable` is supported only these environments:

* net461 or higher.
* netstandard2.0 or higher.
* netcoreapp2.1 or higher.

It limitation comes from [NuGet Microsoft.Bcl.AsyncInterfaces 5.0.0.](https://www.nuget.org/packages/Microsoft.Bcl.AsyncInterfaces/)

### Standard asynchronous disposer IAsyncDisposable:

``` fsharp
let asyncTest = async {
// FusionTasks directly interpreted System.IAsyncDisposable in
// F# async-workflow use expression.
// TIP: We can use `use` expression instead of `use!`,
// Because the `use!` will be bound asynchronously BEFORE calling `DisposeAsync()`.
use accessor = DatabaseAccessor.getAsyncDisposableAccessor()

// (Use accessor...)

// (Will be disposed asynchronously, calls `DisposeAsync()` at end of scope...)
}
```

### TIPS: We have to add annotation for arguments if using it in async workflow:

``` fsharp
let asyncInner arg0 = async {
// Cause FS0041:
// A unique overload for method 'Source' could not be determined based on type information prior to this program point.
// A type annotation may be needed.
// --> Because F# compiler conflict arg0 type inferences: Async or Task.
let! result = arg0
let calculated = result + 1
printfn "%d" calculated
}

// Fixed with type annotation Async<'T> or Task<'T>:
let asyncInner (arg0:Async<_>) = async {
let! result = arg0
let calculated = result + 1
printfn "%d" calculated
}
```

### In C# side:

* Really need sample codes? huh? :)

-----

### Easy LINQPad 5 driven:

* Before setup NuGet package (FSharp.Control.FusionTasks) the LINQPad NuGet Manager.

``` fsharp
open System.IO

// Result is Async
let asyncSequenceData =
let r = new Random()
let data = [| for i = 1 to 100 do yield byte (r.Next()) |]
async {
use fs = new MemoryStream()
do! fs.WriteAsync(data, 0, data.Length)
do! fs.FlushAsync()
return fs.ToArray()
}

// Convert to Task and dump:
asyncSequenceData.AsTask().Dump()
```

![LINQPad 5 driven](https://raw.githubusercontent.com/kekyo/FSharp.Control.FusionTasks/master/Images/linqpad5.png)

-----

## "task-like" and ValueTask appendix

* .NET add new "task-like" type. "task-like" means applied a attribute "System.Runtime.CompilerServices.AsyncMethodBuilderAttribute" and declared the async method builder.
* ValueTask overview:
* New standard "task-like" type named for "ValueTask<T>" for C#. FusionTasks supported ValueTask<T> on 1.0.20.
* ValueTask<T> declared by struct (Value type) for goal is improvement performance. But this type has the Task<T> instance inside and finally continuation handle by Task<T>.
* ValueTask<T> performance effective situation maybe chatty-call fragments using both caller C# and awaiter C# code...
* ValueTask<T> a little bit or no effect improvement performance, because usage of senario for FusionTasks.
* "task-like" augumenting is difficult:
* We have to apply to task-like type with the attribute "AsyncMethodBuilderAttribute".
* Means if already declared type (Sure, we have FSharpAsync<'T>) cannot augument and cannot turn to task-like type.
* Therefore cannot directly return for FSharpAsync<'T> from C#'s async-await method.
* And cannot auto handle task-like type by FusionTasks, because no type safe declaration for task-like type...
* For example, if force support task-like type, FusionTasks require augument "Source: taskLike: obj -> FSharpAsync<'T>" overload on FSharpAsync<'T>. This cannot type safe.
* Conclusion:
* So FusionTasks support only "ValueTask<T>" type and cannot support any other "task-like" types.

## Additional resources

* Source codes available only "FSharp.Control.FusionTasks" folder.
* The slides: "How to meets Async and Task" in Seattle F# Users group "MVP Summit Special: A Night of Lightning Talks" 2016.11.09 http://www.slideshare.net/kekyo/asyncs-vs-tasks

## TODO

Improvements more easier/effective interfaces.

-----

## License

* Copyright (c) 2016-2022 Kouji Matsui (@kozy_kekyo)
* Under Apache v2 http://www.apache.org/licenses/LICENSE-2.0

## History

* 2.6.0:
* Final version. Thank you using FusionTasks!
* You are ready to use both FusionTasks and F# 7.0! See [issue #14](https://github.com/kekyo/FSharp.Control.FusionTasks/issues/14).
* Added supporting .NET Core 2.2 environments.
* Minimized package dependency.
* 2.5.0:
* Supported .NET 6.0 and F# 6.0 environment.
* 2.4.0:
* Supported varies for operator `Async.AsAsync`.
* Completed supporting configured capturing context method `ConfigureAwait(bool)` on all Task based instances.
* 2.3.3:
* Supported .NET asynchronous disposer (`IAsyncDisposable`).
* Supported releasing synchronization context by `IAsyncEnumerable.ConfigureAwait(bool)`.
* Fixed minor exception leaking at the continuation for asynchronous sequence.
* 2.3.0:
* Supported .NET asynchronous sequence (`IAsyncEnumerable` and essential types).
* 2.2.0:
* Suppressed Task/ValueTask allocation when they were already completed (#12, @danielmarbach)
* 2.1.1:
* Downgraded FSharp.Core requirements from 5.0.1 to 5.0.0.
* 2.1.0:
* Added .NET 5, .NET Core 3 and .NET Framework 4.8 assemblies.
* Fixed capturing synchronization context at the asynchronous continuations.
* 2.0.2:
* Fixed add xml comments into package.
* 2.0.1:
* Add support ValueTask for non-generic version.
* Fixed XML comments.
* 2.0.0:
* Supported F# 4.5, .NET Standard 2.0 and .NET Core 2.0.
* Sorry, archived all PCL's libraries. Now FusionTasks supports only .NET Framework 4.5, .NET Core 2.0 and .NET Standard 1.6/2.0.
* Solution structure refablished. Changed to .NET Core modern-style.
* 1.1.1:
* Add ValueTask<'T> bind source overload.
* 1.1.0:
* Supported F# 4.1 and .NET Standard 1.6. (Unfortunately deprecated FS40.netcore (netstandard1.4) package, try to migrate to F# 4.1 :)
* 1.0.20:
* Support ValueTask<T> (Exclude net40 and Profile 47 platform, added dependency for System.Threading.Tasks.Extensions).
* Update version for .NET Core F# (1.0.0-alpha-161205).
* 1.0.13:
* Reduce to only contains .NET Core's assembly in FS40.netcore package.
* Refactor folder structures.
* 1.0.12:
* Add .NET Core support (Separated package: FSharp.Control.FusionTasks.FS40.netcore with -Pre option required)
* 1.0.2:
* Support 'for .. in' expressions. (Thx Armin!)
* 1.0.1:
* Fixed cause undefined Async<'T> using combination Async<'T> and Task/Task<T> in async workflow. (Thx Honza!)
* 1.0.0:
* RTM release :clap:
* Add FSharp.Core NuGet references.
* Temporary disable support .NET Core. If reached F# RTM, continue development... (PR welcome!!)
* Add sample codes.
* 0.9.6:
* WIP release.
* 0.9.5:
* WIP release.
* 0.9.4:
* Fixed nuspec reference System, System.Core
* 0.9.3:
* Fixed nuspec frameworkAssemblies.
* 0.9.2:
* Add package targetFramework.
* Updated RelaxVersioner.
* 0.9.1:
* Remove strongly-signed (Unit test doesn't work...)
* Omit synchronizers (AsyncLock, AsyncLazy). Thats moving to FSharp.Control.AsyncPrimitives project (https://github.com/kekyo/FSharp.Control.AsyncPrimitives).
* Add target dnxcore50 into F# 4.0 (for .NET Core 1.0)
* Source codes and documents bit changed.
* 0.5.8:
* Add strongly-signed.
* 0.5.7:
* Add PCL Profile 7.
* 0.5.6:
* Add PCL Profile 78.
* Fixed minor PCL moniker fragments.
* 0.5.5:
* Fixed version number.
* Fixed icon image url.
* 0.5.4:
* Auto open FSharp.Control.
* Manage AppVeyor CI.
* 0.5.3: Implement awaiter classes.
* 0.5.2: Add dependency assemblies.
* 0.5.1: NuGet package support.