Home

Awesome

Ply

NuGet Version

Ply is a high performance TPL library for F#.

The goal of Ply is to be a very low overhead Task abstraction like it is in C#.

Benchmark

see benchmark code

MethodMeanErrorStdDevRatioRatioSDGen 0/1k OpGen 1/1k OpGen 2/1k Op
C# Async Await24.59 us0.8028 us0.8923 us1.000.000.2136--
Ply24.60 us1.1610 us1.3371 us1.000.070.3052--
TaskBuilder.fs v2.1.026.86 us0.6751 us0.6932 us1.090.030.5798--

Allocated Memory/Op is removed as it isn't correct on .NET Core, Gen 0/1k Op is the relevant metric.

Builders

Ply comes bundled with these builders:

builderreturn typetfmnamespace
taskTask<'T>netstandard2.0, netcoreapp2.1FSharp.Control.Tasks
vtaskValueTask<'T>netcoreapp2.1FSharp.Control.Tasks
unitTaskTasknetstandard2.0, netcoreapp2.1FSharp.Control.Tasks
unitVtaskValueTasknetcoreapp2.1FSharp.Control.Tasks
uvtaskValueTask<'T>netcoreapp2.1FSharp.Control.Tasks.Affine.Unsafe
uunitTaskTasknetcoreapp2.1FSharp.Control.Tasks.Affine.Unsafe
uunintVtaskValueTasknetcoreapp2.1FSharp.Control.Tasks.Affine.Unsafe
uplyPly<'T>netstandard2.0,netcoreapp2.1FSharp.Control.Tasks.Affine.Unsafe

More information on when to use which builder:

builderdescription
vtaskNear zero allocation CE, allocates one object for the execution bubble at the start, and two objects per bind if the Task we bind to isn't completed yet.
unitTaskAs the TPL doesn't know about F#'s unit type Task.FromResult(()) won't ever return a cached task. On netcoreapp we can check for a succesful completion instead to directly return a CompletedTask removing the task allocation.
unitVtaskCE shorthand for doing if vtask.IsCompletedSuccessfully then ValueTask() else ValueTask(vtask.AsTask() :> Task)
uvtaskAn unsafe version of vtask and one of the few zero allocation* CEs Ply comes with. Read about the trade-off under execution bubble
uunitTaskAn unsafe version of unitTask and one of the few zero allocation* CEs Ply comes with. Read about the trade-off under execution bubble
uunitVtaskAn unsafe version of unitVtask and one of the few zero allocation* CEs Ply comes with. Read about the trade-off under execution bubble
uplyCan be enqueued directly onto the caller's state machine, skips Task and execution bubble.

*zero allocation only when any Task (or Task-like) you bind against is already completed.

Execution bubble

An execution bubble is made by any C# async-await method for capturing and restoring async local and synchronization context changes. Any changes would otherwise escape onto the caller context.

It's rare that methods do anything with async locals or synchronization contexts, even in C#. So if you know anything you use doesn't do that either then there's nothing inherently unsafe about using the Unsafe CEs as you don't need any execution bubble for correctness.

Special Thanks

Thanks to @gusty for very valuable SRTP advice, it helped me tremendously to narrow down what specifically was wrong about an earlier approach I took.

Thanks to @rspeele TaskBuilder.fs was a great inspiration in developing Ply.

Next Steps and Improvements