Home

Awesome

F# FusionTasks

FusionTasks

Status

maindevel
NuGet PackageNuGet FusionTasks
Continuous integrationRelaxVersioner CI build (main)RelaxVersioner CI build (main)

What is this?

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<T> class in F# async-workflow block.
  let! length = ms.ReadAsync(data2, 0, data2.Length)
  do length |> should equal data2.Length
}
using System.Threading.Tasks;
using Microsoft.FSharp.Control;

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

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

Features

Benefits


Environments

Combination chart:

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

How to use

Samples

Basic async workflow:

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<unit>.
  do! ms.WriteAsync(data, 0, data.Length)
  do ms.Position <- 0L

  // FusionTasks directly interpreted System.Threading.Tasks.Task<T> 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:

use ms = new MemoryStream()

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

Without async workflow (CancellationToken):

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):

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>:

open System.Threading

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

  // 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>:

let asyncTest = async {
  // FusionTasks directly interpreted System.Collection.Generic.IAsyncEnumerable<T> 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<T>.ConfigureAwait(bool) on it.

NOTE: IAsyncEnumerable<T> is supported only these environments:

It limitation comes from NuGet Microsoft.Bcl.AsyncInterfaces 5.0.0.

Standard asynchronous disposer IAsyncDisposable:

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:

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<int> or Task<int>.
  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:


Easy LINQPad 5 driven:

open System.IO

// Result is Async<byte[]>
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<byte[]> and dump:
asyncSequenceData.AsTask().Dump()

LINQPad 5 driven


"task-like" and ValueTask appendix

Additional resources

<iframe src="https://www.slideshare.net/slideshow/embed_code/68424602" width="800" height="500" frameborder="0" marginwidth="0" marginheight="0" scrolling="no"></iframe>

TODO

Improvements more easier/effective interfaces.


License

History