I don't really understand why await
and async
don't improve the performance of my code here like they're supposed to.
Though skeptical, I thought the compiler was supposed to rewrite my method so that the downloads were done in parallel... but it seems like that's not actually happening.
(I do realize that await
and async
do not create separate threads; however, the OS should be doing the downloads in parallal, and calling back my code in the original thread -- shouldn't it?)
Am I using async
and await
improperly? What is the proper way to use them?
Code:
using System;
using System.Net;
using System.Threading;
using System.Threading.Tasks;
static class Program
{
static int SumPageSizesSync(string[] uris)
{
int total = 0;
var wc = new WebClient();
foreach (var uri in uris)
{
total += wc.DownloadData(uri).Length;
Console.WriteLine("Received synchronized data...");
}
return total;
}
static async Task<int> SumPageSizesAsync(string[] uris)
{
int total = 0;
var wc = new WebClient();
foreach (var uri in uris)
{
var data = await wc.DownloadDataTaskAsync(uri);
Console.WriteLine("Received async'd CTP data...");
total += data.Length;
}
return total;
}
static int SumPageSizesManual(string[] uris)
{
int total = 0;
int remaining = 0;
foreach (var uri in uris)
{
Interlocked.Increment(ref remaining);
var wc = new WebClient();
wc.DownloadDataCompleted += (s, e) =>
{
Console.WriteLine("Received manually async data...");
Interlocked.Add(ref total, e.Result.Length);
Interlocked.Decrement(ref remaining);
};
wc.DownloadDataAsync(new Uri(uri));
}
while (remaining > 0) { Thread.Sleep(25); }
return total;
}
static void Main(string[] args)
{
var uris = new string[]
{
// Just found a slow site, to demonstrate the problem :)
"http://www.europeanchamber.com.cn/view/home",
"http://www.europeanchamber.com.cn/view/home",
"http://www.europeanchamber.com.cn/view/home",
"http://www.europeanchamber.com.cn/view/home",
"http://www.europeanchamber.com.cn/view/home",
};
{
var start = Environment.TickCount;
SumPageSizesSync(uris);
Console.WriteLine("Synchronous: {0} milliseconds", Environment.TickCount - start);
}
{
var start = Environment.TickCount;
SumPageSizesManual(uris);
Console.WriteLine("Manual: {0} milliseconds", Environment.TickCount - start);
}
{
var start = Environment.TickCount;
SumPageSizesAsync(uris).Wait();
Console.WriteLine("Async CTP: {0} milliseconds", Environment.TickCount - start);
}
}
}
Output:
Received synchronized data...
Received synchronized data...
Received synchronized data...
Received synchronized data...
Received synchronized data...
Synchronous: 14336 milliseconds
Received manually async data...
Received manually async data...
Received manually async data...
Received manually async data...
Received manually async data...
Manual: 8627 milliseconds // Almost twice as fast...
Received async'd CTP data...
Received async'd CTP data...
Received async'd CTP data...
Received async'd CTP data...
Received async'd CTP data...
Async CTP: 13073 milliseconds // Why so slow??
Chris' answer is almost correct, but introduces a race condition and synchronously blocks on all the tasks.
As a general rule, it's best to not use task continuations if you have await
/async
available. Also, do not use WaitAny
/ WaitAll
- the async
equivalents are WhenAny
and WhenAll
.
I would write it like this:
static async Task<int> SumPageSizesAsync(IEnumerable<string> uris)
{
// Start one Task<byte[]> for each download.
var tasks = uris.Select(uri => new WebClient().DownloadDataTaskAsync(uri));
// Asynchronously wait for them all to complete.
var results = await TaskEx.WhenAll(tasks);
// Calculate the sum.
return results.Sum(result => result.Length);
}
I may be misreading your code, but it looks like you are launching a background thread to do the async reading and then immediately blocking, waiting for it to complete. Nothing about the 'async' portion of your code is actually async. Try this:
static async Task<int> SumPageSizesAsync(string[] uris)
{
int total = 0;
var wc = new WebClient();
var tasks = new List<Task<byte[]>>();
foreach (var uri in uris)
{
tasks
.Add(wc.DownloadDataTaskAsync(uri).ContinueWith(() => { total += data.Length;
}));
}
Task.WaitAll(tasks);
return total;
}
And use it thus:
{
var start = Environment.TickCount;
await SumPageSizesAsync(uris);
Console.WriteLine("Async CTP: {0} milliseconds", Environment.TickCount - start);
}
I could be wrong- the async stuff is new and I'm not 100% familiar with it- but the similar timing to the sync version seems to bear me out.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With