3

We have different long procedures with success and non-success results. We want to run this tasks in parallel, and return to user first success result. Stopping of other tasks would be great too.

I've tried Task.WhenAny and Parallel.ForEach approaches. The first one can't really check success. The second one doing a lot of extra work.

My snippets are here:

public static class Program
{
    public static void Main(string[] args)
    {

        //FirstTry().Wait();

        SecondTry().Wait();
    }

    public static async Task FirstTry()
    {
        var variants = Enumerable.Range(1, 20).ToList();

        // first try
        var tasks = variants.ConvertAll(async v => await LongProcedureTrueIfSuccess(v));

        var completed = await Task.WhenAny(tasks);

        var result = (await completed) ? "Good" : "Bad";

        Console.WriteLine("Result is " + result);
    }

    public static async Task SecondTry()
    {
        var variants = Enumerable.Range(1, 20).ToList();

        CancellationTokenSource cts = new CancellationTokenSource();
        ConcurrentBag<bool> bag = new ConcurrentBag<bool>();

        Parallel.ForEach(variants,  (v,state) =>
        {
            var r =  LongProcedureTrueIfSuccess(v).Result;
            if (r)
            {
                bag.Add(r);
                state.Stop();
            }
        });

        var result = (bag.FirstOrDefault()) ? "Good" : "Bad";

        Console.WriteLine("Result is " + result);
    }

    public static async Task<bool> LongProcedureTrueIfSuccess(int i)
    {
        await Task.Delay(1000); //Processing
        Console.WriteLine("We know result");
        if (i == 5)
            return true;
        return false;
    }
}

https://dotnetfiddle.net/Imrrto

The best expected result is:

We know result
Result is Good

But we have

We know result
Result is Bad
We know result
We know result
We know result
We know result
We know result
We know result
We know result
We know result
We know result
We know result
We know result
We know result
We know result
We know result
We know result
We know result
We know result
We know result
We know result

in first case and

We know result
We know result
We know result
We know result
We know result
We know result
We know result
We know result
We know result
We know result
We know result
We know result
We know result
We know result
We know result
We know result
We know result
Result is Good

in second

Maximilian Ast
  • 3,369
  • 12
  • 36
  • 47

1 Answers1

8

You can keep a list of uncompleted tasks, and use Task.WhenAny() to wait for one of the tasks to complete. Then you can check the task's result to see if it was successful.

If it wasn't successful, remove it from the list of tasks and try again. Keep going until a task is successful or all tasks are unsuccessful.

For example (with tasks that just return a bool to indicate success):

using System;
using System.Collections.Generic;
using System.Threading.Tasks;

namespace Demo
{
    class Program
    {
        static async Task Main()
        {
            // Make your array of tasks.

            var tasks = new List<Task<bool>>
            {
                someTask(1000, false),
                someTask(2000, false),
                someTask(3000, true),
                someTask(4000, false),
            };

            while (tasks.Count > 0)
            {
                var completed = await Task.WhenAny(tasks);

                if (completed.Result) // Successful?
                {
                    Console.WriteLine("A task completed successfully");
                    return;
                }

                Console.WriteLine("A task completed unsuccessfully");
                tasks.Remove(completed);
            }

            Console.WriteLine("No tasks completed successfully");

        }

        static async Task<bool> someTask(int delay, bool result)
        {
            await Task.Delay(delay);
            return result;
        }
    }
}

Or with some cancellation support:

using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;

namespace Demo
{
    class Program
    {
        static async Task Main()
        {
            // Make your array of tasks.

            var factory = new CancellationTokenSource();

            var tasks = new List<Task<bool>>
            {
                someTask(1000, false, factory.Token),
                someTask(2000, false, factory.Token),
                someTask(3000, true,  factory.Token),
                someTask(4000, false, factory.Token),
            };

            while (tasks.Count > 0)
            {
                var completed = await Task.WhenAny(tasks);

                if (completed.Result) // Successful?
                {
                    Console.WriteLine("A task completed successfully");
                    factory.Cancel();
                    return;
                }

                Console.WriteLine("A task completed unsuccessfully");
                tasks.Remove(completed);
            }

            Console.WriteLine("No tasks completed successfully");
        }

        static async Task<bool> someTask(int delay, bool result, CancellationToken cancellation)
        {
            await Task.Delay(delay, cancellation);
            return result;
        }
    }
}
Matthew Watson
  • 104,400
  • 10
  • 158
  • 276