async samples 7: Combinators

This next set of async samples discusses techniques that enable you to build workflows of the form “do this asynchronously, and then, when this finishes, do that”. The first example shows how to start several tasks in parallel, and continue when all those tasks have finished:

        public async void AsyncWhenAll()
{
Uri[] uris = {
new Uri("http://www.weather.gov"),
new Uri("http://www.weather.gov/climate/"),
new Uri("http://www.weather.gov/rss/") };

string[] pages = await TaskEx.WhenAll(
from uri in uris select new WebClient().DownloadStringTaskAsync(uri));

foreach (string page in pages)
{
WriteLinePageTitle(page);
}
}

The LINQ query starts the three downloads. The await on the same line waits for all three downloads to finish. Then, the titles are printed.

Of course, sometimes, you may want to check against different services for the same answer, and continue execution as soon as you have the first answer.  TaskEx.WhenAny does that for you:

        public async void AsyncWhenAnyRedundancy()
{
string symbol = "ABCXYZ";

var recommendations = new List<Task<bool>>()
{
GetBuyRecommendation1Async(symbol),
GetBuyRecommendation2Async(symbol),
GetBuyRecommendation3Async(symbol)
};
Task<bool> recommendation = await TaskEx.WhenAny(recommendations);
if (await recommendation)
{
Console.WriteLine("Buy stock {0}!", symbol);
}
else
{
Console.WriteLine("Sell stock {0}!", symbol);
}
}


In this sample, the code starts all three requests for a buy/sell recommendations. The first one to finish wins. That answer is printed.

WhenAny has other uses. Suppose you want to start tasks, and when each task finishes you would process the result from that task. Rinse, and repeat for each task. Here’s an example of how you would do that:

        public async void AsyncWhenAnyInterleaving()
{
Uri[] uris = {
new Uri("http://www.weather.gov"),
new Uri("http://www.weather.gov/climate/"),
new Uri("http://www.weather.gov/rss/") };


List<Task<string>> downloadTasks = (
from uri in uris select new WebClient().DownloadStringTaskAsync(uri)).
ToList();

while (downloadTasks.Count > 0)
{
Task<string> downloadTask = await TaskEx.WhenAny(downloadTasks);
downloadTasks.Remove(downloadTask);

string page = await downloadTask;
WriteLinePageTitle(page);
}
}

The new code in his example is in the while loop. It awaits a result from any of the currently executing tasks.  When a result is available, it removes that task from the list of current tasks, and processes its result.

This process of awaiting and processing one result continues until all tasks have been processed.

You can also use WhenAny to limit the number of current tasks running. This sample adds new tasks when the current list of running tasks has fewer elements than some known limit. Tasks are added until the limit is reached. Then, the list of tasks is awaited. When a result is available, it gets processed, and then if the list of running tasks is below the threshold, and there is more work to do, another task is started and added to the list:

        public async void AsyncWhenAnyThrottling()
{
constint CONCURRENCY_LEVEL = 4; // Maximum of 4 requests at a time

Uri[] uris = {
new Uri("http://ecn.t0.tiles.virtualearth.net/tiles/h000.jpeg?g=400"),
new Uri("http://ecn.t0.tiles.virtualearth.net/tiles/h001.jpeg?g=400"),
new Uri("http://ecn.t0.tiles.virtualearth.net/tiles/h002.jpeg?g=400"),
new Uri("http://ecn.t0.tiles.virtualearth.net/tiles/h003.jpeg?g=400"),
new Uri("http://ecn.t0.tiles.virtualearth.net/tiles/h010.jpeg?g=400"),
new Uri("http://ecn.t0.tiles.virtualearth.net/tiles/h011.jpeg?g=400"),
new Uri("http://ecn.t0.tiles.virtualearth.net/tiles/h012.jpeg?g=400"),
new Uri("http://ecn.t0.tiles.virtualearth.net/tiles/h013.jpeg?g=400"),
new Uri("http://ecn.t0.tiles.virtualearth.net/tiles/h020.jpeg?g=400"),
new Uri("http://ecn.t0.tiles.virtualearth.net/tiles/h021.jpeg?g=400"),
new Uri("http://ecn.t0.tiles.virtualearth.net/tiles/h022.jpeg?g=400"),
new Uri("http://ecn.t0.tiles.virtualearth.net/tiles/h023.jpeg?g=400"),
new Uri("http://ecn.t0.tiles.virtualearth.net/tiles/h030.jpeg?g=400"),
new Uri("http://ecn.t0.tiles.virtualearth.net/tiles/h031.jpeg?g=400"),
new Uri("http://ecn.t0.tiles.virtualearth.net/tiles/h032.jpeg?g=400"),
new Uri("http://ecn.t0.tiles.virtualearth.net/tiles/h033.jpeg?g=400"),
};
int nextIndex = 0;
var downloadTasks = new List<Task<string>>();
while (nextIndex < CONCURRENCY_LEVEL && nextIndex < uris.Length)
{
Console.WriteLine("Queuing up initial download #{0}.", nextIndex + 1);
downloadTasks.Add(
new WebClient().DownloadStringTaskAsync(uris[nextIndex]));
nextIndex++;
}

while (downloadTasks.Count > 0)
{
try
{
Task<string> downloadTask = await TaskEx.WhenAny(downloadTasks);
downloadTasks.Remove(downloadTask);

string str = await downloadTask;
int length = str.Length;

Console.WriteLine("* Downloaded {0}-byte image.", length);
}
catch (Exception ex) { Console.WriteLine(ex.ToString()); }

if (nextIndex < uris.Length)
{
Console.WriteLine("New download slot available. Queuing up download #{0}.",
nextIndex + 1);
downloadTasks.Add(new WebClient().DownloadStringTaskAsync(uris[nextIndex]));
nextIndex++;
}
}

}

Once again, the while loop toward the end is the interesting code. Await a result. Process the result. Then, if there is more work, start the next task.
 
Finally, there is a delay API should you need to wait for a prescribed amount of time, while keeping the UI responsive:
 
        public async void AsyncDelay()
{
Console.WriteLine("Before the delay.");
await TaskEx.Delay(3000);
Console.WriteLine("After the delay.");
}

Created: 3/29/2011 1:00:28 PM

Current Projects

I create content for .NET Core. My work appears in the .NET Core documentation site. I'm primarily responsible for the section that will help you learn C#.

All of these projects are Open Source (using the Creative Commons license for content, and the MIT license for code). If you would like to contribute, visit our GitHub Repository. Or, if you have questions, comments, or ideas for improvement, please create an issue for us.

I'm also the president of Humanitarian Toolbox. We build Open Source software that supports Humanitarian Disaster Relief efforts. We'd appreciate any help you can give to our projects. Look at our GitHub home page to see a list of our current projects. See what interests you, and dive in.

Or, if you have a group of volunteers, talk to us about hosting a codeathon event.