How to observe tasks that are not awaited due to failure in another awaited task in C#?

Viewed 169

I have this code:

var task1 = operation1();
var task2 = operation2();


var result1 = await task1;
var result2 = await task2;

I do also handle UnobservedTaskException (by logging it). The problem that I face is that after task1 fails and first await results in exception, task2 completes in an error and then I have a log entry that I do not want to see as I will already log the first exception and at that point all subsequent exceptions are of no interest to me.

So I am wondering if there is a way to do something so that all tasks are "ignored" in a way after I get an exception.

I know I can use await Task.WhenAll, but the downside is that I have to wait for all exceptions to happen, though I know for sure that after first task results in exception, I don't need to wait for the other task to complete as the whole operation already failed.

Another possible solution is to write try/catch and cancel all tasks, but that's a bit cumbersome.

P.S. The example is simplified, I do have multiple tasks running like that. So I am looking for a generic solution that would work for any number of tasks

1 Answers

As an alternative to Task.WhenAll you can use a progressive approach with Task.WhenAny.

When any of the Tasks finishes then you can examine the result and you can decide what to do next. (Please bear in mind that Task.WhenAny does not throw exception even it is awaited) The great thing about this approach is that you can easily add throttling (control the degree of parallelism) to this.

The basic implementation of progressive async for each

static async Task ProgressiveAsyncForEach(int degreeOfParallelism, params Task[] tasks)
{
    var toBeProcessedTasks = new HashSet<Task>();
    var remainingTasksEnumerator = tasks.GetEnumerator();

    void AddNextTask()
    {
        if (!remainingTasksEnumerator.MoveNext()) return;
        var nextTaskToProcess = (Task)remainingTasksEnumerator.Current;
        toBeProcessedTasks.Add(nextTaskToProcess);
    }

    //Initialize
    while (toBeProcessedTasks.Count < degreeOfParallelism)
    {
        AddNextTask();
    }

    while (toBeProcessedTasks.Count > 0)
    {
        var processedTask = await Task.WhenAny(toBeProcessedTasks).ConfigureAwait(false);
        if (!processedTask.IsCompletedSuccessfully)
        {
            Console.WriteLine("One of the task has failed");
            //TODO: log first failed task
            //CONSIDER: cancel all the remaining tasks
            return;
        }

        toBeProcessedTasks.Remove(processedTask);
        AddNextTask();
    }
}

Sample usage

static async Task Main(string[] args)
{
    await ProgressiveAsyncForEach(2, Faulty(), Fast(), Slow());
    Console.WriteLine("Application finished");
}

static async Task Slow()
{
    Console.WriteLine("Slow started");
    await Task.Delay(1000);
    Console.WriteLine("Slow finished");
}

static async Task Fast()
{
    Console.WriteLine("Fast started");
    await Task.Delay(500);
    Console.WriteLine("Fast finished");
}

static async Task Faulty()
{
    Console.WriteLine("Faulty started");
    await Task.Delay(700);
    throw new Exception();
}
Related