So, I've been reading about monads and I wanted to see if I could implement a system for asynchronous computation in a monadic way.
I came up with two solutions:

The first one spawns a thread for each function bound, and this thread waits for the previous thread to finish. Should this bother me? These threads aren't doing anything until they have something to do so I'm not putting big loads on the computer.

using System;

namespace Philistine
{
{
}

{
private T result;

{
result = value;
}

{
}

private void SetResult(T t)
{
result = t;
}

private T Wait()
{
if (!IsDone)
return result;
}

{
if (IsDone)
{
return function(result);
}
else
{
() =>
{
var other = function(Wait());
return other.Wait();
});
}
}

{
}

public void Bind(Action<T> action)
{
}
}
}


The alternative is to try and have linear task chains be executed on a single thread, and try to make use of a thread pool. What bothers me here is that the implementation is quite a bit more complex. The Wait method is implemented like it is because since I'm using ThreadPool I don't have access to Thread.Join and I felt that adding EventWaitHandles would add more overhead than speedup.

using System;
using System.Collections.Generic;
using System.Linq;

namespace Philistine
{
{
}

{
void Run(TIn parameter);
void Invoke(TIn parameter);
}

{
bool IsDone { get; }
void Bind(Action<TOut> action);
TOut Wait();
}

{
public bool IsDone => true;

{
result = value;
}

{
return function(result);
}

{
}

public void Bind(Action<TOut> action)
{
}

public TOut Wait() => result;
}

{
protected TOut result;
public bool IsDone { get; private set; }

{
_ =>
{
result = computation();
Continue();
});
}

protected void Continue()
{
lock (this)
IsDone = true;
{
}
}

{
Monitor.Enter(this);
if (IsDone)
{
Monitor.Exit(this);
return function(result);
}
else
{
var continuation = ContinueWith(function);
Monitor.Exit(this);
return continuation;
}
}

{
Monitor.Enter(this);
if (IsDone)
{
Monitor.Exit(this);
}
else
{
var continuation = ContinueWith(function);
Monitor.Exit(this);
return continuation;
}
}

public void Bind(Action<TOut> action)
{
Monitor.Enter(this);
if (IsDone)
{
Monitor.Exit(this);
}
else
{
ContinueWith(action);
Monitor.Exit(this);
}
}

{
TOtherOut Unwrapped(TOut tIn) => function(tIn).Wait();
}

{
}

private void ContinueWith(Action<TOut> action)
{
}

{
}

public TOut Wait()
{
while (!IsDone)
return result;
}
}

{

{
this.action = action;
}

public void Run(TIn parameter)
{
}

public void Invoke(TIn parameter)
{
action(parameter);
}
}

{

{
this.function = function;
}

public void Run(TIn parameter)
{
_ =>
{
result = function(parameter);
Continue();
});
}

public void Invoke(TIn parameter)
{
result = function(parameter);
Continue();
}
}
}


Both implementations are used like this:

var five = Task.Do(
() =>
{
return 5;
});
var seven = five.Bind(i => i + 2);
var fourteen = seven.Bind(
i =>
{
return i * 2;
});
var treeFiddy = seven.Bind(
i =>
{
});

fourteen.Bind(Console.Out.WriteLine);
treeFiddy.Bind(Console.Out.WriteLine);


Actual question:
Since I don't have any realworld experience with heavily multi-threaded programs, and I don't have solid understanding of the inner workings of threads, I'd like some help in assessing the costs and benefits of both approaches (performance-wise). Would the ideal solution be some combination of both? The first solution, but with a pool? The second solution but without a pool? Better implementation of Wait?

P. S. Please let me know if some parts of the code could use documentation. I've been staring at it for a while now, so in my eyes it all makes sense and I don't like to add unnecessary comments (I think too much verbosity makes it harder to read).

• Could you elaborate one what this code allows which the TPL and System.Threading.Task<T>.ContinueWith (something like this) does not? I feel I'm missing something. – VisualMelon Nov 2 '18 at 18:41
• Regarding comments, we can all agree that too many comments is a bad thing, but inline documentation (///) on (public) methods is generally a good thing (so long as they are correct and you don't change the interface). Describing what a method is meant to do is a great why to make sure that you understand it, make it accessible to those consuming the API, and indeed helps to prevent inadvertently changing its behaviour in future (because its documented inline, so the programmer can actually see it). – VisualMelon Nov 2 '18 at 18:48