timonkrebs / MemoizR

Declarative Structured Concurrency for C#
Apache License 2.0
106 stars 3 forks source link
reactiv reactive reactivity signal signals

MemoizR

MemoizR-logo
Streamlined Concurrency

.NET NuGet NuGet

"The world is still short on languages that deal super elegantly and inherently and intuitively with concurrency" Mads Torgersen Lead Designer of C# (https://www.youtube.com/watch?v=Nuw3afaXLUc&t=4402s)

MemoizR is a Declarative Structured Concurrency implementation for .NET that simplifies and enhances error handling, maintainability and state synchronization across multiple threads. It provides a maintainable and efficient way to manage concurrency, making it suitable for both simple and complex multi-threaded scenarios.

the dynamic structured concurrency part is still being worked on.

Inspired From Stephen Cleary — Asynchronous streams https://www.youtube.com/watch?v=-Tq4wLyen7Q&t=706s compared to which is MemoizR/Signals
IEnumerable synchronous asynchronous
Task single value multi value
Observable push based push-pull
IAsyncEnumerable pull based push-pull

Key Features

Inspiration

Similarities and Advantages of MemoizR to ReactiveX and Dataflow

MemoizR shares some similarities with the Dataflow (Task Parallel Library) library in terms of handling concurrency and managing data flows. However, MemoizR offers several advantages, such as implicit Join and LinkTo, which make it a powerful choice for managing concurrent operations and reactive data flows. One notable distinction between MemoizR and ReactiveX lies in their subscription handling. In ReactiveX, it's common to manage subscriptions explicitly, keeping track of when to subscribe and unsubscribe from observable sequences. This can introduce complexities and potential resource leaks.

Implicit Subscription Handling

There are no explicit subscriptions to manage. Instead, MemoizR's dependencies are automatically tracked and synchronized based on your code's structure. When you define dependencies between signals, memos, and reactions, MemoizR handles the subscription and synchronization behind the scenes. This implicit subscription handling simplifies your code and reduces the risk of subscription-related issues.

Dataflow Paradigm

Both MemoizR and the Dataflow library are designed to handle concurrent operations and data flow in a structured manner. They provide abstractions for defining tasks, dependencies, and synchronization, making it easier to manage complex concurrency scenarios.

Implicit LinkTo

MemoizR also provides implicit LinkTo functionality. While in Dataflow, you typically use the LinkTo method to connect dataflow blocks, MemoizR handles the linking of dependencies automatically based on your code's structure. This simplifies the setup and maintenance of data flow relationships.

Usage

// Setup
var f = new MemoFactory();
var v1 = f.CreateSignal(1);
var m1 = f.CreateMemoizR(async() => await v1.Get());
var m2 = f.CreateMemoizR(async() => await v1.Get() * 2);
var m3 = f.CreateMemoizR(async() => await m1.Get() + await m2.Get());

// Get Value manually
await m3.Get(); // Calculates m1 + 2 * m1 => (1 + 2 * 1) = 3

// Change
await Task.Run(async () => await v1.Set(2));
// Synchronization is handled by MemoizR
await m3.Get(); // Calculates m1 + 2 * m1 => (1 + 2 * 2) = 6
await m3.Get(); // No operation, result is still 6

await v1.Set(3); // Setting v1 does not trigger evaluation of the graph
await v1.Set(2); // Setting v1 does not trigger evaluation of the graph
await m3.Get(); // No operation, result is still 6 (because the last time the graph was evaluated, v1 was already 2)

MemoizR can also handle dynamic changes in the graph, making it suitable for scenarios where the structure of the dependency graph may change at runtime.

var m3 = f.CreateMemoizR(async() => await v1.Get() ? await m1.Get() : await m2.Get());

Declarative Structured Concurrency

MemoizR's declarative structured concurrency model enhances maintainability, error handling, and cancellation of complex concurrency use cases. It allows you to set up and manage concurrency in a clear and structured way, making your code easier to understand and maintain.

In summary, MemoizR offers a powerful and intuitive approach to managing concurrency and reactive data flows (Dataflow (Task Parallel Library), Channels), with features like implicit Join and LinkTo that simplify your code and improve maintainability. It also draws inspiration from ReactiveX, making it a versatile choice for reactive programming scenarios but without having to handle subscriptions.

var f = new MemoFactory("DSC");

var child1 = f.CreateConcurrentMapReduce(
    async c =>
    {
        await Task.Delay(3000, c.Token);
        return 3;
    });

// all tasks get canceled if one fails
var c1 = f.CreateConcurrentMapReduce(
    async c =>
    {
        await child1.Get();
        return 4;
    });

var x = await c1.Get();

Reactivity

You can use MemoizR to create reactive data flows easily:

var f = new MemoFactory();
var v1 = f.CreateSignal(1);
var m1 = f.CreateMemoizR(async() => await v1.Get());
var m2 = f.CreateMemoizR(async() => await v1.Get() * 2);
var r1 = f.CreateReaction(m1, m2, (val1, val2) => val1 + val2);

Test it

https://dotnetfiddle.net/Widget/Vrbwam

Example From: Khalid Abuhakmeh