.NET and me Coding dreams since 1998!

20Sep/120

Task.WhileAll

When Task.WhenAll met the Task.WhenAny…

There are two methods which can be used for awaiting an array of tasks in non blocking manner: Task.WhenAll and Task.WhenAny.

It is quite obvious how they work:

  • WhenAll completes when every task is completed,
  • WhenAny when any of the task is completed.

I needed today something which is a mix of those two and I’ve came up with something which completes when all is awaited but also provides hook for me to respond on awaits of individual tasks.

I’ve called my little extension: Task.WhileAll.

Extension method

Here's the complete implementation

    
public static class TaskExtensions 
{
	public static async Task<IList<T>> WhileAll<T>(this IList<Task<T>> tasks, IProgress<T> progress) 
	{
		var result = new List<T>(tasks.Count);
		var done = new List<Task<T>>(tasks);
		while(done.Count > 0) 
		{
			await Task.WhenAny(tasks);
			var spinning = new List<Task<T>>(done.Count - 1);
			for(int i = 0; i < done.Count; i++) 
			{
				if(done[i].IsCompleted) 
				{
					result.Add(done[i].Result);
					progress.Report(done[i].Result);
				} else {
					spinning.Add(done[i]);
				}
			}

			done = spinning;
		}

		return result;
	}
}

 

The code is quire simple:

  • it is an async extension method extending the IList<Task<T>>
  • method returns on completition IList<T> (result of awaited tass)
  • Method accepts IProgress<T> interface publishing information about the tasks who had just completed to the interested subscribers
  • Inside of the method body we have a loop which is active as long there are tasks with IsCompleted==false.
  • The loop has a “sleep” line where I use await Task.WhenAny to asynchronuslly wait for any task to complete

Unit test

Here’s a simple unit test ilustrating the usage of the extension method

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

    using Microsoft.VisualStudio.TestTools.UnitTesting;

    [TestClass]
    public class UnitTest1 {
        [TestMethod]
        public async Task TestMethod1() {

            var task1 = Task.Run(() => 101);
            var task2 = Task.Run(() => 102);
            var tasks = new List<Task<int>>() { task1, task2 };


            List<int> result = new List<int>();
            var listener = new Progress<int>(
                taskResult => {
                    result.Add(taskResult);
                });

            var actual = await tasks.WhileAll(listener);
            Thread.Sleep(50); // wait a bit for progress reports to complete

            Assert.AreEqual(2, result.Count);
            Assert.IsTrue(result.Contains(101));
            Assert.IsTrue(result.Contains(102));

            Assert.AreEqual(2, actual.Count);
            Assert.IsTrue(actual.Contains(101));
            Assert.IsTrue(actual.Contains(102));
        }
    }

Again, nothing complicated :

  • I create an array of two dumb tasks
  • I define the listner which takes the task result and (for the unit test needs) adds it to the collection of results
  • I await the extension methods on task array with provided listner
  • Wait for 50 ms so the progress report would have time to finish (code is async after all)
  • Check that both tasks were reported to subscriber
  • Check that both tasks are returned as result.

Conclussion

That’s it. Dead simple code which I use A LOT to increase CPU utilization of my web crawlers.

Source code of the extension method and unit test can be found here.

Comments (0) Trackbacks (0)

No comments yet.


Leave a comment

No trackbacks yet.