This library contains additional computation expressions for the task CE utilizing the Resumable Code introduced in F# 6.0.
-
ValueTask<'T>- This utilizes .NET's ValueTask (which is essentially a Discriminated Union of'Value | Task<'Value>) for possibly better performance in synchronous scenarios. Similar to F#'s Task ExpressionvalueTaskvalueTaskUnitpoolingValueTask
-
ColdTask<'T>- Alias forunit -> Task<'T>. Allows for lazy evaluation (also known as Cold) of the tasks, similar to F#'s Async being cold.coldTask
-
CancellableTask<'T>- Alias forCancellationToken -> Task<'T>. Allows for lazy evaluation (also known as Cold) of the tasks, similar to F#'s Async being cold. Additionally, allows for flowing a CancellationToken through the computation, similar to F#'s Async cancellation support.cancellableTaskbackgroundCancellableTask
-
CancellableValueTask<'T>- Alias forCancellationToken -> ValueTask<'T>. Allows for lazy evaluation (also known as Cold) of the tasks, similar to F#'s Async being cold. Additionally, allows for flowing a CancellationToken through the computation, similar to F#'s Async cancellation support.cancellableValueTaskcancellablePoolingValueTask
-
ParallelAsync<'T>- Utilizes the applicative syntax to allow parallel execution of Async<'T> expressions. See this discussion as to why this is a separate computation expression.parallelAsync
-
AsyncEx<'T>- Variation of F# async semantics described further below with examples.asyncEx- This can also be accessed under
IcedTasks.Polyfill.Asyncwhich will shadow the F# Async CE.async
-
Task<'T>- Polyfill for fixes to F# Task CE. Can be accessed underIcedTasks.Polyfill.Taskwhich will shadow the F# Task CE.taskbackgroundTask
-
Task- a CE for aTaskthat has no return value.taskUnitbackgroundTaskUnit
See Choosing a builder for a builder-by-builder comparison, including aliases, background builders, unit builders, and .NET 6+ pooling builders.
AsyncEx is similar to Async except in the following ways:
-
Allows
usefor IAsyncDisposableopen IcedTasks let fakeDisposable () = { new IAsyncDisposable with member __.DisposeAsync() = ValueTask.CompletedTask } let myAsyncEx = asyncEx { use _ = fakeDisposable () return 42 } -
Allows
let!/do!against Tasks/ValueTasks/any Awaitableopen IcedTasks let myAsyncEx = asyncEx { let! _ = task { return 42 } // Task<T> let! _ = valueTask { return 42 } // ValueTask<T> let! _ = Task.Yield() // YieldAwaitable return 42 } -
When Tasks throw exceptions they will use the behavior described in Async.Await overload (esp. AwaitTask without throwing AggregateException
let data = "lol" let inner = asyncEx { do! task { do! Task.Yield() raise (ArgumentException "foo") return data } :> Task } let outer = asyncEx { try do! inner return () with | :? ArgumentException -> // Should be this exception and not AggregationException return () | ex -> return raise (Exception("Should not throw this type of exception", ex)) } -
Use IAsyncEnumerable with
forkeyword. This example uses TaskSeq but you can use anyIAsyncEnumerable<T>.open IcedTasks open FSharp.Control let myAsyncEx = asyncEx { let items = taskSeq { // IAsyncEnumerable<T> yield 42 do! Task.Delay(100) yield 1701 } let mutable sum = 0 for i in items do sum <- sum + i return sum }
For ValueTasks
- F# doesn't currently have a
valueTaskcomputation expression. Until this PR is merged.
open IcedTasks
let myValueTask = task {
let! theAnswer = valueTask { return 42 }
return theAnswer
}
- You want control over when your tasks are started
- You want to be able to re-run these executable tasks
- You don't want to pollute your methods/functions with extra CancellationToken parameters
- You want the computation to handle checking cancellation before every bind.
Short example:
open IcedTasks
let coldTask_dont_start_immediately = task {
let mutable someValue = null
let fooColdTask = coldTask { someValue <- 42 }
do! Async.Sleep(100)
// ColdTasks will not execute until they are called, similar to how Async works
Expect.equal someValue null ""
// Calling fooColdTask will start to execute it
do! fooColdTask ()
Expect.equal someValue 42 ""
}
The examples show cancellableTask but cancellableValueTask can be swapped in.
Accessing the context's CancellationToken:
-
Binding against
CancellationToken -> Task<_>let writeJunkToFile = let path = Path.GetTempFileName() cancellableTask { let junk = Array.zeroCreate bufferSize use file = File.Create(path) for i = 1 to manyIterations do // You can do! directly against a function with the signature of `CancellationToken -> Task<_>` to access the context's `CancellationToken`. This is slightly more performant. do! fun ct -> file.WriteAsync(junk, 0, junk.Length, ct) } -
Binding against
CancellableTask.getCancellationTokenlet writeJunkToFile = let path = Path.GetTempFileName() cancellableTask { let junk = Array.zeroCreate bufferSize use file = File.Create(path) // You can bind against `CancellableTask.getCancellationToken` to get the current context's `CancellationToken`. let! ct = CancellableTask.getCancellationToken () for i = 1 to manyIterations do do! file.WriteAsync(junk, 0, junk.Length, ct) }
Short example:
let executeWriting = task {
// CancellableTask is an alias for `CancellationToken -> Task<_>` so we'll need to pass in a `CancellationToken`.
// For this example we'll use a `CancellationTokenSource` but if you were using something like ASP.NET, passing in `httpContext.RequestAborted` would be appropriate.
use cts = new CancellationTokenSource()
// call writeJunkToFile from our previous example
do! writeJunkToFile cts.Token
}
- When you want to execute multiple asyncs in parallel and wait for all of them to complete.
Short example:
open IcedTasks
let exampleHttpCall url = async {
// Pretend we're executing an HttpClient call
return 42
}
let getDataFromAFewSites = parallelAsync {
let! result1 = exampleHttpCall "howManyPlantsDoIOwn"
and! result2 = exampleHttpCall "whatsTheTemperature"
and! result3 = exampleHttpCall "whereIsMyPhone"
// Do something meaningful with results
return ()
}
| GitHub Actions |
|---|
| Package | Stable | Prerelease |
|---|---|---|
| IcedTasks |
Install one of these:
- The .NET SDK version that
global.jsonspecifies. The test projects also run on .NET 8.0 and .NET 9.0, so install those SDKs or runtimes too. CI installs the 8.x, 9.x, and 10.x SDKs. - VS Code Dev Containers. The dev container installs .NET 10.0, 9.0, and 8.0.
CONFIGURATIONsets the configuration of the dotnet commands. The default isDebug. When thePublishtarget runs, the default isRelease.CONFIGURATION=Release ./build.shadds-c Releaseto commands such asdotnet build.
CImakes the build run in CI mode. Restore retries on failure,CheckFormatCodeonly checks the format, andPublishToNuGetandGitHubReleasecan run.RELEASE_VERSIONsets the version for theReleasetarget. See Releasing.NUGET_TOKENis the API key thatPublishToNuGetuses. The publish workflow gets a short-lived key through NuGet trusted publishing.GITHUB_TOKENis the token thatGitHubReleaseuses to create the GitHub release and upload the NuGet packages.
> build.cmd <optional buildtarget> // on windows
$ ./build.sh <optional buildtarget>// on unix
The default target is DotnetPack. It runs Clean, DotnetRestore, CheckFormatCode, DotnetBuild, DotnetTest, and DotnetPack. Off CI, CheckFormatCode formats the source files in place. The build writes the library to src/IcedTasks/bin/<configuration>/<target framework>/IcedTasks.dll for each target framework, and the packages to dist/.
Clean- Cleans thebin,temp, anddistdirectories, and thebinandobjdirectories of the source and test projects.DotnetRestore- Runs dotnet restore on the solution file.UpdateChangelog- Moves theUnreleasedchanges inCHANGELOG.mdinto a new version entry.Releaseuses it.CheckFormatCode- On CI, runs Fantomas with--checkand fails if a file needs formatting. Locally, formats the code.DotnetBuild- Runs dotnet build on the solution file.FSharpAnalyzers- Runs the F# analyzers frompackages/analyzerson the source projects. The build does not create that directory. The analyzers also run after each build ofsrc, and their warnings do not fail the build.DotnetTest- Runs dotnet test on the solution file.WatchTests- Runs dotnet watch test on each test project.GenerateAssemblyInfo- GeneratesAssemblyInfo.fsfor the source projects.DotnetPack- Runs dotnet pack and writes the packages todist/.PublishToNuGet- Pushes the packages indist/to NuGet withdotnet nuget push. It runs only on CI.GitRelease- CommitsCHANGELOG.mdandAssemblyInfo.fswith the release notes in the commit body, pushes the commit, and pushes a git tag for the version. It runs only on themasterbranch.GitHubRelease- Publishes a GitHub Release with the release notes and the NuGet packages. It runs only on CI.FormatCode- Runs Fantomas on all F# files.BuildDocs- Generates the documentation fromdocsSrcand the XML documentation comments insrc. The documentation pages reference theReleasebuild, so setCONFIGURATION=Release.CleanDocsCache- Cleans the fsdocs cache.BuildDocsruns it first.WatchDocs- Generates the documentation and starts a local web server. It rebuilds when files indocsSrcor the projects insrcchange.Release- RunsUpdateChangelog,GenerateAssemblyInfo, andGitRelease. Use it on a local machine.Publish- Runs the default target chain,GenerateAssemblyInfo,PublishToNuGet, andGitHubRelease. The publish workflow runs it on CI.
A release has two steps. A maintainer runs the Release target on a local machine. The pushed tag then starts the publish workflow, which runs the Publish target on CI.
- Update
CHANGELOG.md. Add an "Unreleased" section with the release notes for this version, in Keep a Changelog format.
NOTE: Add a link to the pull request next to each release note. The Release target adds the new notes to the body of the git commit. GitHub then shows on the pull request that a commit referenced it, for example "added a commit that referenced this pull request". The build script writes the commit message "Bump version to x.y.z". A reader of a pull request can then see which version released the change. A reader of the CHANGELOG can find the discussion behind each change.
This example adds an "Unreleased" section to a CHANGELOG.md that already has a released 0.1.0 section:
## [Unreleased]
### Added
- Does cool stuff!
### Fixed
- Fixes that silly oversight
## [0.1.0] - 2017-03-17
First release
### Added
- This release already has lots of features
[Unreleased]: https://github.com/TheAngryByrd/IcedTasks/compare/v0.1.0...HEAD
[0.1.0]: https://github.com/TheAngryByrd/IcedTasks/releases/tag/v0.1.0
- On the
masterbranch, run theReleasetarget. Give the version in theRELEASE_VERSIONenvironment variable or as a parameter after the target name. The target does these steps:
- It updates
CHANGELOG.mdand moves the changes from theUnreleasedsection into a new0.2.0section.- If the changelog has prerelease versions of 0.2.0, it also collects their changes into the final 0.2.0 entry.
- It makes the commit
Bump version to 0.2.0with the new changelog section in the commit body. - It pushes the commit and the git tag
v0.2.0.
macOS/Linux Parameter:
./build.sh Release 0.2.0
macOS/Linux Environment Variable:
RELEASE_VERSION=0.2.0 ./build.sh Release
- The tag push starts
.github/workflows/publish.yml. The workflow gets a NuGet API key through NuGet trusted publishing and runs./build.sh Publish. ThePublishtarget builds, tests, and packs the library, publishes the packages to NuGet, and creates a GitHub release for the tag.