Marklio.AsyncMaybe 1.1.2

Prefix Reserved
dotnet add package Marklio.AsyncMaybe --version 1.1.2
                    
NuGet\Install-Package Marklio.AsyncMaybe -Version 1.1.2
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Marklio.AsyncMaybe" Version="1.1.2" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Marklio.AsyncMaybe" Version="1.1.2" />
                    
Directory.Packages.props
<PackageReference Include="Marklio.AsyncMaybe" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Marklio.AsyncMaybe --version 1.1.2
                    
#r "nuget: Marklio.AsyncMaybe, 1.1.2"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Marklio.AsyncMaybe@1.1.2
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Marklio.AsyncMaybe&version=1.1.2
                    
Install as a Cake Addin
#tool nuget:?package=Marklio.AsyncMaybe&version=1.1.2
                    
Install as a Cake Tool

Marklio.AsyncMaybe

Marklio.AsyncMaybe is a pattern for writing code that allows a single implementation to serve both synchronous and asynchronous codepaths.

You follow a convention for method naming and parameters, and a source generator will automatically generate the public sync and async "APIs" that your library exposes.

All code is expressed as async code, but any given "await" statement can follow the synchronous or asynchronous codepath, eliminating the problems of sync-over-async or vice versa.

Synchronicity of synchronous codepaths is verified, so good test coverage can tell you if you have a bug.

TODO: examples

The AsyncMaybe pattern

AsyncMaybe is the idea that a single implementation expresses the logic, algorithms and general calls, but can run in synchronous mode or asynchronous mode. AsyncMaybe code has the following traits:

  • Have "async" signatures
    • Return Task/ValueTask
    • Take CancellationTokens
  • Are named *AsyncMaybe
  • Take a bool parameter called "useAsync".
    • For extension methods this is the second parameter (after the "this")
    • For all other methods, this is the first parameter
  • When useAsync is false, all "tasks" are expected to complete synchronously. That is, as soon as the "task" is returned, it is in the completed state and its value is available.

Typically, these async maybe implementations aren't part of the public API of a library. Instead, you want to expose fully async or fully sync APIs. AsyncMaybe can generate these for you. Just decorate an AsyncMaybe implementation with the AsyncMaybeWrappers attribute, and the source generator will produce both async and sync wrappers for you!

Enumerators/Iterators

One of the big wrinkles in this plan is the IEnumerable<T>/IAsyncEnumerable<T> split. AsyncMaybe provides a converged view called AsyncMaybeEnumerable that can wrap either of these and produce the necessary interface and ensure that the synchronous path remains truly synchronous.

So, any AsyncMaybe implementation should take AsyncMaybeEnumerable as both input parameters and return values.

AsyncMaybeWrappers options:

All of these options default to false unless otherwise stated

  • IsPublic (default: true) - Indicates whether the produced wrappers should be public. If false, the wrappers will inherit the visibility of the implementation.
  • UseCancellationTokenForSync - If true, the CancellationToken parameter will be exposed to the synchronous wrapper as well. Otherwise, the synchronous implementation will pass default/none.
  • AllowNoCancellation - Normally, it is an error for an AsyncMaybe implementation to not take a CancellationToken as its final parameter. Setting this to true will suppress that error and create uncancellable wrappers.
  • SyncOverrides - If true, it indicates that the synchronous wrapper should have the overrides modifier
  • AsyncOverrides - If true, it indicates that the asynchronous wrapper should have the overrides modifier
  • SyncVirtual - If true, it indicates that the synchronous wrapper should have the virtual modifier
  • AsyncVirtual - If true, it indicates that the asynchronous wrapper should have the virtual modifier
  • SyncAsProperty - If true (and the implementation contains no arguments other than useAsync and a cancellation token), this creates the synchronous wrapper as a Property
  • SyncName - Allows the synchronous wrapper name to be overridden
  • AsyncName - Allows the asynchronous wrapper name to be overridden

AsyncMaybeEnumerable

This type serves as the enumerable bridge between IEnumerable<T> and IAsyncEnumerable<T>. It should be used in the signature of AsyncMaybe implementations instead of either of those two interfaces.

When using an AsyncMaybeEnumerable in an implementation, call WrapAsAsync() to get an IAsyncEnumerable and use await foreach or other async interactions, even for synchronous paths. If everything is done correctly, everything will still be synchronous.

If you need to create an iterator, create an IAsyncEnumerable<T> iterator and call .AsMaybe() on it to return it.

Most of the time, the automatic wrapper generators will take care of converting to IEnumerable or IAsyncEnumerable, but if you need to do this conversion, you can call .WrapAsSync() or .WrapAsAsync(). When you .WrapAsSync(), the infrastructure will enforce that tasks have completed synchronously.

Sync-over-Async

AsyncMaybe is designed to provide "pure" execution. That is, everything is sync, or everything is async. This requires the leaf node calls to call into a synchronous or asynchronous method conditionally based on useAsync. However, .NET async evolved in several time periods where synchronous implementations were discouraged. This has resulted in libraries with only async surface area.

AsyncMaybe can help generate AsyncMaybe patterns over async calls that provide the best protection against deadlocks and produce exceptions in the expected way. This sync-over-async will not provide the best performance code, as it will use Task.Run to run async implementations on the thread pool (typically) and block the entering thread. In many cases that don't require high scalability, this is just fine.

If you want to create such an AsyncMaybe implementation, write an asynchronous implementation with the standard pattern, then decorate it with the AsyncMaybeOverAsync attribute. The source generator will create an AsyncMaybe implementation that can be called in an AsyncMaybe call chain and will be able to create sync codepaths.

TODO: more readme!

Product Compatible and additional computed target framework versions.
.NET net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.
  • net8.0

    • No dependencies.

NuGet packages (2)

Showing the top 2 NuGet packages that depend on Marklio.AsyncMaybe:

Package Downloads
Marklio.Metadata

Allows low-level exploration of PE and metadata constructs.

Marklio.IO

Useful IO patterns, particularly for reading binary file formats.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.1.2 255 4/25/2024
1.1.1 239 4/12/2024
1.1.0-preview1 221 4/6/2023
1.0.0 1,118 7/28/2022
1.0.0-preview13 357 7/5/2022
1.0.0-preview12 296 6/30/2022
1.0.0-preview11 222 6/29/2022
1.0.0-preview10 223 6/29/2022
1.0.0-preview09 265 6/29/2022
1.0.0-preview08 213 6/28/2022
1.0.0-preview07 215 6/23/2022
1.0.0-preview06 226 6/23/2022
1.0.0-preview05 227 6/23/2022
1.0.0-preview04 228 6/23/2022
1.0.0-preview03 235 6/23/2022
1.0.0-preview02 241 6/23/2022
1.0.0-preview01 281 6/23/2022