louthy / language-ext

C# functional language extensions - a base class library for functional programming
MIT License
6.42k stars 416 forks source link

v5 await more than a single forked IO, similar to Task.WhenAll #1330

Closed kirill-gerasimenko-da closed 1 month ago

kirill-gerasimenko-da commented 4 months ago

Hi!

I'm trying to figure out how to properly/idiomatically await all of the forked operations I start.

I have two Eff<Unit> which I fork and I would like to wait for both of them to finish. Both of these effects start background threads (I can't control that), which then call the callback I pass to them. These components lifetime is controlled via CancellationToken passed from the Eff's runtime (so they should stop and release the threads and all resources once CancellationToken passed via the environment is cancelled, at least I hope so).

This is how I do it right now (some pieces copied from the examples, like infiniteLoop):

var startBot1 = from _ in startBot(callback1) select unit;
var startBot2 = from _ in startBot(callback2) select unit;

var startAllBots = 
     from fork1 in startBot1.Fork()
     from fork2 in startBot2.Fork()
     from _ in infiniteLoop()
     select unit;

static IO<Unit> infiniteLoop() => from _ in wait(1) from r in infiniteLoop() select unit;
static IO<Unit> wait(int milliseconds) => IO.yield(milliseconds);

It works as I would expect, but I'm not confident that it's the correct way. I'd assume there could be something like IO.AwaitAll(Seq<ForkIO> ioSeq) or something like that that would have similar semantics with Task.WhenAll for example.

Is there already a way to have such semantics?

Thanks!

louthy commented 4 months ago

fork1 and fork2 are ForkIO values. Each has two members: Cancel and Await. So, you can either let them run independently and let them finish in their own time, or Await them like so:

    public static IO<B> Apply<A, B>(this IO<Func<A, B>> ff, IO<A> fa) =>
        from tf in ff.Fork()
        from ta in fa.Fork()
        from f in tf.Await
        from a in ta.Await
        select f(a);

I'll add an AwaitAll at some point. It should be fairly easy to see how it would work.

kirill-gerasimenko-da commented 4 months ago

Thanks!

Would it be easy to also add AwaitAny once you get there?

louthy commented 1 month ago

I'll add an AwaitAll at some point. It should be fairly easy to see how it would work.

This will be available in the next release.

Would it be easy to also add AwaitAny once you get there?

As will this.