Streamlined Concurrency
"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 |
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.
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.
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.
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.
// 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());
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();
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);
https://dotnetfiddle.net/Widget/Vrbwam
Example From: Khalid Abuhakmeh