C# asynchronous flow control library inspired by Node.js async. Provides composable primitives for orchestrating callback-based and Task-based async workflows in a clean, readable manner.
Nested async callbacks quickly become unreadable — especially in game development where complex sequences of loading, animation, networking, and logic are the norm. CsAsync turns deeply nested "callback hell" into flat, linear, and maintainable code.
| Primitive | Game Use Case |
|---|---|
Waterfall |
Game initialization — load config → preload assets → init subsystems → show main menu, each step waiting for the previous. Cutscene sequences — play dialogue → wait for input → trigger animation → advance story, with built-in timeout to skip. |
Waterfall + retry |
Matchmaking / login — retry failed server connections up to N times before giving up. |
Whilst |
Turn-based game loop — continue processing turns while game is not over. AI patrol — execute patrol route while enemy is alive. |
DoWhilst |
Login flow — attempt login at least once, retry while credentials fail. Loading retry — attempt to load asset, retry on failure until success or max count. |
Each<T> |
Batch asset loading — iterate over an asset list, loading each texture/model sequentially with progress reporting. Entity initialization — spawn a list of NPCs one by one to avoid frame spikes. |
Parallel |
Concurrent asset preloading — load textures, audio, and config in parallel with a concurrency cap to control memory/IO. Multiplayer ready-check — wait for all players to signal ready simultaneously. |
- API orchestration — chain multiple backend calls where each depends on the previous.
- File processing — process a directory of files sequentially or in parallel batches.
- UI workflows — step-by-step wizards, multi-page form submissions.
- Data pipelines — ETL operations with per-stage timeout and error recovery.
dotnet add package CsAsync| Class | Description |
|---|---|
Waterfall |
Executes tasks sequentially; each task invokes a callback to proceed |
Whilst |
Loops while a test condition returns true, executing an async body each iteration |
DoWhilst |
Like Whilst, but executes the body at least once before testing the condition |
Each<T> |
Iterates over a collection, executing an async operation per item sequentially |
Parallel |
Executes multiple async tasks concurrently with optional concurrency limiting |
All classes support both callback-style (Start) and Task-based (RunAsync) APIs, plus CancellationToken.
Executes tasks sequentially. Each task must invoke its callback to proceed to the next. Stops on first error.
var waterfall = new Waterfall();
waterfall.AddTask(cb =>
{
DoRequest1(() => cb(null));
});
waterfall.AddTask(cb =>
{
DoRequest2(() => cb(null));
});
await waterfall.RunAsync();var options = new WaterfallOptions
{
TimeoutPerTask = TimeSpan.FromSeconds(30),
MaxRetriesPerTask = 2
};
var waterfall = new Waterfall(options);
waterfall.AddTask(cb =>
{
DoRequest(() => cb(null));
});
using var cts = new CancellationTokenSource(TimeSpan.FromMinutes(1));
await waterfall.RunAsync(cts.Token);var waterfall = new Waterfall();
waterfall.AddTask(cb => { /* task 1 */ cb(null); });
waterfall.AddTask(cb => { /* task 2 */ cb(null); });
waterfall.Start(ex =>
{
if (ex != null)
Console.WriteLine($"Failed: {ex.Message}");
else
Console.WriteLine("All done!");
});Loops while a condition is true, re-evaluating before each iteration.
int count = 0;
var whilst = new Whilst(
test: () => count < 5,
fn: cb =>
{
Console.WriteLine($"Iteration {count}");
count++;
cb(null);
});
await whilst.RunAsync();Like Whilst, but executes the body at least once before evaluating the condition.
int count = 0;
var doWhilst = new DoWhilst(
test: () => count < 3,
fn: cb =>
{
Console.WriteLine($"Count: {count}");
count++;
cb(null);
});
await doWhilst.RunAsync();Iterates over a collection sequentially, stopping on the first error.
var items = new[] { "a", "b", "c" };
var each = new Each<string>(items, (item, cb) =>
{
Console.WriteLine($"Processing: {item}");
cb(null);
});
await each.RunAsync();With cancellation:
using var cts = new CancellationTokenSource();
var each = new Each<int>(Enumerable.Range(1, 100), (item, cb) =>
{
if (item > 10) cts.Cancel();
cb(null);
});
await each.RunAsync(cts.Token); // throws OperationCanceledExceptionExecutes multiple tasks concurrently. Fails on the first error.
var parallel = new Parallel();
parallel.AddTask(cb => { DoRequest1(() => cb(null)); });
parallel.AddTask(cb => { DoRequest2(() => cb(null)); });
parallel.AddTask(cb => { DoRequest3(() => cb(null)); });
await parallel.RunAsync();Concurrency limiting:
var options = new ParallelOptions { MaxConcurrency = 3 };
var parallel = new Parallel(options);
for (int i = 0; i < 100; i++)
{
parallel.AddTask(cb => { ProcessItem(i, () => cb(null)); });
}
await parallel.RunAsync(); // At most 3 tasks run concurrentlyWhilstconstructor no longer takes the callback — useStart(callback)orRunAsync()instead.Each<T>is now generic. The non-genericEachis available with[Obsolete]for backward compatibility.Waterfall.AddTasknow acceptsAction<Action<Exception>>directly. TheTaskCallbackoverload is deprecated.- All classes now support
CancellationTokenviaStart(callback, ct)andRunAsync(ct). DoWhilstandParallelare newly added.
MIT