Skip to content

kekyo/FSharp.Control.FusionTasks

main
Switch branches/tags

Name already in use

A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?
Code

Latest commit

 

Git stats

Files

Permalink
Failed to load latest commit information.

F# FusionTasks

FusionTasks

Status

main devel
NuGet Package NuGet FusionTasks
Continuous integration RelaxVersioner CI build (main) RelaxVersioner CI build (main)

What is this?

  • F# Async workflow <--> .NET Task/ValueTask easy seamless interoperability library.
  • Sample code (F# side):
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
}
  • Sample code (C# side):
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

  • 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<T>, 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:

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:

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

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:

  • Really need sample codes? huh? :)

Easy LINQPad 5 driven:

  • Before setup NuGet package (FSharp.Control.FusionTasks) the LINQPad NuGet Manager.
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

  • .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
<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

  • 2.6.0:
    • Final version. Thank you using FusionTasks!
    • You are ready to use both FusionTasks and F# 7.0! See issue #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<T>.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 👏
    • 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.

About

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

Topics

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Contributors 4

  •  
  •  
  •  
  •  

Languages