Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Task.Factory.ContinueWhenAny continue when any task finish without exception

I have 3 tasks in my application that are responsible for getting data from databases.
Till now I had all tasks executed one after one. If first finished and had Result then this was my data, if now I started second task and I checked again.

Recently I found information that I can start multiple tasks and continue when one of them finish using Task.Factory.ContinueWhenAny. This works fine if all my task don't throw any Exceptions, but if any Task fails I can't get results I want.

For example:

var t1 = Task.Factory.StartNew(() =>
{
    Thread.Sleep(5000);
    return 1;
});

var t2 = Task.Factory.StartNew(() =>
{
    Thread.Sleep(2000);
    throw new Exception("My error");
    return 2;
});

var t3 = Task.Factory.StartNew(() =>
{
    Thread.Sleep(4000);
    return 3;
});

Task.Factory.ContinueWhenAny(new[] {t1, t2,t3}, (t) =>
{
    Console.WriteLine(t.Result);
});

This code starts 3 tasks and waits till one of them finish. Because t2 throws exception after 2 seconds it is the one that is available in ContinueWhenAny.

From code above I would like to get 3 in t.Result.
Is there an option to continue only when task finished successfully? Something like Task.Factory.ContinueWhenAnyButSkipFailedTasks

EDIT 1 This is my solution for now based on @Nitram answer:

var t1 = Task.Factory.StartNew(() =>
{
    var rnd = new Random();
    Thread.Sleep(rnd.Next(5,15)*1000);
    throw new Exception("My error");
    return 1;
});

var t2 = Task.Factory.StartNew(() =>
{
    Thread.Sleep(2000);
    throw new Exception("My error");
    return 2;
});

var t3 = Task.Factory.StartNew(() =>
{
    throw new Exception("My error");
    return 3;
});

var tasks = new List<Task<int>> { t1, t2, t3 };

Action<Task<int>> handler = null;

handler = t =>
{
    if (t.IsFaulted)
    {
        tasks.Remove(t);
        if (tasks.Count == 0)
        {
            throw new Exception("No data at all!");
        }
        Task.Factory.ContinueWhenAny(tasks.ToArray(), handler);
    }
    else
    {
        Console.WriteLine(t.Result);
    }
};

Task.Factory.ContinueWhenAny(tasks.ToArray(), handler);

What I need now is how to throw exception when all tasks throw exception?
Maybe this can be changed into single method that would return task - something like child tasks?

like image 235
Misiu Avatar asked Jun 25 '15 10:06

Misiu


1 Answers

There is a overload to the ContinueWhenAny function that does what you want.

Simply set the TaskContinuationOptions to OnlyOnRanToCompletion and the failed tasks will be ignored.

Task.Factory.ContinueWhenAny(new[] {t1, t2,t3}, (t) =>
{
    Console.WriteLine(t.Result);
}, TaskContinuationOptions.OnlyOnRanToCompletion);

So we concluded that this answer is actually wrong.

The removal of the tasks from a list seems to be the only way I can think of. I tried to put this into some lines of code. Here you go:

var tasks = new List<Task> {t1, t2, t3};

Action<Task> handler = null;
handler = (Task t) =>
{
    if (t.IsFauled) {
        tasks.Remove(t);
        Task.Factory.ContinueWhenAny(tasks.ToArray, handler);
    } else {
        Console.WriteLine(t.Result);
    }
};
Task.Factory.ContinueWhenAny(tasks.ToArray, handler);

I am not very firm with C#, but I hope it gives you a idea. What is basically happening is that every time a task that is faulted is handled, this task is removed from the list of known tasks and the function waits for the next one.

Okay and now the entire thing with .NET 4.5 and the async-await pattern. await basically gives you the ability to register what ever is written after the await into a continuation.

So this is nearly the same pattern with async-await.

var tasks = new List<Task> {t1, t2, t3};
while (tasks.Any()) 
{
    var finishedTask = await Task.WhenAny(tasks);
    if (finishedTask.IsFaulted)
    {
        tasks.Remove(finishedTask);
    }
    else
    {
        var result = await finishedTask;
        Console.WriteLine(result);
        return;
    }
}

The only difference is that the outer function needs to be a async function for that one. This means upon encountering the first await the outer function will return the Task that holds the continuation.

You can add a surrounding function that blocks until this function is done. The async-await pattern gives you the ability to write asynchronous non-blocking code that "looks" like simply synchronouse code.

Also I suggest you use the Task.Run function to spawn your tasks instead of the TaskFactory. It will spare from some problems later on. ;-)

like image 56
Nitram Avatar answered Sep 22 '22 13:09

Nitram