Skip to content

gonglei007/CsAsync

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

30 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

CsAsync

中文文档

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.

Why CsAsync?

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.

Game Development Scenarios

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.

General Scenarios

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

Installation

dotnet add package CsAsync

API Overview

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.


Waterfall

Executes tasks sequentially. Each task must invoke its callback to proceed to the next. Stops on first error.

Task-based API (recommended)

var waterfall = new Waterfall();

waterfall.AddTask(cb =>
{
    DoRequest1(() => cb(null));
});

waterfall.AddTask(cb =>
{
    DoRequest2(() => cb(null));
});

await waterfall.RunAsync();

With Timeout and Retry

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);

Callback-style API

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!");
});

Whilst

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();

DoWhilst

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();

Each<T>

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 OperationCanceledException

Parallel

Executes 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 concurrently

Migration from v1.x

  • Whilst constructor no longer takes the callback — use Start(callback) or RunAsync() instead.
  • Each<T> is now generic. The non-generic Each is available with [Obsolete] for backward compatibility.
  • Waterfall.AddTask now accepts Action<Action<Exception>> directly. The TaskCallback overload is deprecated.
  • All classes now support CancellationToken via Start(callback, ct) and RunAsync(ct).
  • DoWhilst and Parallel are newly added.

License

MIT

About

C#的异步流程控制类库。【C# async flow control class code.】

Topics

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages