Search code examples
c#async-awaittasktask-parallel-librarycancellationtokensource

Task.WaitAny() — Checking for results


I have a series of Tasks in an array. If a Task is "Good" it returns a string. If it's "Bad": it return a null.

I want to be able to run all the Tasks in parallel, and once the first one comes back that is "Good", then cancel the others and get the "Good" result.

I am doing this now, but the problem is that all the tasks need to run, then I loop through them looking for the first good result.

List<Task<string>> tasks = new List<Task<string>>();
Task.WaitAll(tasks.ToArray());

Solution

  • I want to be able to run all the Tasks in parallel, and once the first one comes back that is "Good", then cancel the others and get the "Good" result.

    This is misunderstanding, since Cancellation in TPL is co-operative, so once the Task is started, there's no way to Cancel it. CancellationToken can work before Task is started or later to throw an exception, if Cancellation is requested, which is meant to initiate and take necessary action, like throw custom exception from the logic

    Check the following query, it has many interesting answers listed, but none of them Cancel. Following is also a possible option:

    public static class TaskExtension<T>
    {
      public static async Task<T> FirstSuccess(IEnumerable<Task<T>> tasks, T goodResult)
    
        {
            // Create a List<Task<T>>
            var taskList = new List<Task<T>>(tasks);
            // Placeholder for the First Completed Task
            Task<T> firstCompleted = default(Task<T>);
            // Looping till the Tasks are available in the List
            while (taskList.Count > 0)
            {
                // Fetch first completed Task
                var currentCompleted = await Task.WhenAny(taskList);
    
                // Compare Condition
                if (currentCompleted.Status == TaskStatus.RanToCompletion
                    && currentCompleted.Result.Equals(goodResult))
                {
                    // Assign Task and Clear List
                    firstCompleted = currentCompleted;
                    break;
                }
                else
                   // Remove the Current Task
                   taskList.Remove(currentCompleted);
            }
            return (firstCompleted != default(Task<T>)) ? firstCompleted.Result : default(T);
        }
    }
    

    Usage:

    var t1 = new Task<string>(()=>"bad");
    
    var t2 = new Task<string>(()=>"bad");
    
    var t3 = new Task<string>(()=>"good");
    
    var t4 = new Task<string>(()=>"good");
    
    var taskArray = new []{t1,t2,t3,t4};
    
    foreach(var tt in taskArray)
      tt.Start();
    
    var finalTask = TaskExtension<string>.FirstSuccess(taskArray,"good");
    
    Console.WriteLine(finalTask.Result);
    

    You may even return Task<Task<T>>, instead of Task<T> for necessary logical processing