Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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

like image 510
Ilya Chernomordik Avatar asked Aug 11 '20 08:08

Ilya Chernomordik


People also ask

What happens if you don't await a task?

If you don't await the task or explicitly check for exceptions, the exception is lost. If you await the task, its exception is rethrown. As a best practice, you should always await the call. By default, this message is a warning.

Can you await a completed task?

So, yes, you can await already completed tasks. Note that, if you call await several time, it will return it every time and by reference if it's not a value.

Is Task run blocking?

Run is misused to run IO blocking tasks. Although the code will work just fine (e.g UI not not freeze) but it is still a wrong approach. This is because Task. Run will still block a thread from thread pool the entire time until it finishes the method.

Does async await block main thread C#?

The await operator doesn't block the thread that evaluates the async method. When the await operator suspends the enclosing async method, the control returns to the caller of the method.

Why doesn't the current method wait for the task to complete?

The current method calls an async method that returns a Task or a Task<TResult> and doesn't apply the Await operator to the result. The call to the async method starts an asynchronous task. However, because no Await operator is applied, the program continues without waiting for the task to complete. In most cases, that behavior isn't expected.

What happens if I don't await the task when it returns?

An exception that's raised in a method that returns a Task or Task<TResult> is stored in the returned task. If you don't await the task or explicitly check for exceptions, the exception is lost. If you await the task, its exception is rethrown. As a best practice, you should always await the call. By default, this message is a warning.

What happens if I don't await the task in Visual Basic?

If you don't await the task or explicitly check for exceptions, the exception is lost. If you await the task, its exception is rethrown. As a best practice, you should always await the call. By default, this message is a warning. For more information about hiding warnings or treating warnings as errors, see Configuring Warnings in Visual Basic.

What happens when asynchronous method await a task?

An asynchronous method awaits a Task directly. When an asynchronous method awaits a Task directly, continuation usually occurs in the same thread that created the task, depending on the async context. This behavior can be costly in terms of performance and can result in a deadlock on the UI thread.


Video Answer


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();
}
like image 140
Peter Csala Avatar answered Oct 20 '22 10:10

Peter Csala