Jim
Jim

Reputation: 16002

chaining c# async / await tasks, recreating them as they complete

Aesthetic question really.

Given this code (polling unavoidable):

protected override async Task ExecuteAsync(CancellationToken ct)
{       
    // move the loop here somehow?
    await Task.WhenAll(
        Task.Run(async () => await this.PollA(ct), ct),
        Task.Run(async () => await this.PollB(ct), ct),
        Task.Run(async () => await this.PollC(ct), ct))
        .ConfigureAwait(false);   
}

the polling methods look like this at the moment, each one has a different delay.

private async Task Poll(CancellationToken ct)
{
    while (!ct.IsCancellationRequested)
    {
        await Task.Delay(Math.Max(1000, CONFIGA), ct);
        this._logger.StartAction("poll A status");
        this._logger.StopAction("poll A status");
    }
}

Is there a way to structure a continuation that removes the loop in each of the Poll methods

private async Task Poll(CancellationToken ct)
{
    await Task.Delay(Math.Max(1000, CONFIGA), ct);
    this._logger.StartAction("poll A status");
    this._logger.StopAction("poll A status");
}

This might not even be the right pattern, but it seems better than having three infinite loops.

Task.WhenAny([A,B,C]) => 
// recreate any complete task as soon as it returns 
// and await the new "continuation"?

Upvotes: 0

Views: 185

Answers (2)

Gerrie Pretorius
Gerrie Pretorius

Reputation: 4121

I have an Aesthetic solution, that is probably not advisable to be used since it will probably cause stack overflow eventually. It maybe demonstrates why the loop is a better option.

I must admit do not really understand your example in a real world context. In my mind almost all code that executes for a long time will do it in a finite loop, and thus to check for cancellation after each loop iteration sounds like a good idea to me.

Unless you want your code just to run infinitely until the task is canceled, in which case my aesthetic solution will probably cause a stack overflow if left to long, but it was fun none the less coming up with this code.

I created a Extension method:

public static class Extensions
{
    public static async Task ContinueWithInfinitly(this Task task, Func<Task> continuationAction, CancellationToken cancellationToken)
    {
        await task;
        if (!cancellationToken.IsCancellationRequested)
        {
            var newTask = continuationAction.Invoke();
            await newTask.ContinueWithInfinitly(continuationAction, cancellationToken);
        }
    }
}

Which Base on your code will then be called as follows:

await Task.WhenAll(
                Task.Run(async () => await this.PollA(ct).ContinueWithInfinitly(() => PollA(ct), ct)),
                Task.Run(async () => await this.PollB(ct).ContinueWithInfinitly(() => PollB(ct), ct)),
                Task.Run(async () => await this.PollC(ct).ContinueWithInfinitly(() => PollC(ct), ct)))
                .ConfigureAwait(false);

Although I dont see the point of wrapping each method again in a Task.Run. So i can also just be

await Task.WhenAll(
                this.PollA(ct).ContinueWithInfinitly(() => PollA(ct), ct),
                this.PollB(ct).ContinueWithInfinitly(() => PollB(ct), ct),
                this.PollC(ct).ContinueWithInfinitly(() => PollC(ct), ct))
                .ConfigureAwait(false);

Upvotes: 1

Evk
Evk

Reputation: 101443

You can use Task.WhenAny like this:

private async Task<Tuple<string, int>> Poll(string type, int delay, CancellationToken ct) {
    await Task.Delay(Math.Max(1000, delay), ct);
    Console.WriteLine($"poll {type} status");
    // return input arguments back
    return Tuple.Create(type, delay);
}

private async Task PollAll(CancellationToken ct) {
    var tasks = new []
    {
        Poll("A", 3000, ct),
        Poll("B", 2000, ct),
        Poll("C", 1000, ct)
    };
    while (!ct.IsCancellationRequested) {
        var completed = await Task.WhenAny(tasks);
        var index = Array.IndexOf(tasks, completed);
        // await to throw exceptions if any
        await completed;                                
        // replace with new task with the same arguments
        tasks[index] = Poll(completed.Result.Item1, completed.Result.Item2, ct);
    }
}

Upvotes: 0

Related Questions