Garry Xiao
Posted on March 30, 2020
Today, I need to complete an interesting test to demonstrate my knowledge of asynchronous programming by creating a method that downloads three resources and aggregates the content length of all 3 responses.
The first idea ran to my mind is Task.WhenAll:
static async Task<long> FetchContentLengthWaitAllAsync(Uri[] urls, CancellationToken cancellationToken)
{
// Start a Stopwatch to evaluate performance
var sw = new Stopwatch();
sw.Start();
// Current thread id
Console.WriteLine("Current Thread Id: " + Thread.CurrentThread.ManagedThreadId);
// Request
var contentLength = (await Task.WhenAll(urls.Select(url =>
{
// Web client for the request
using (var wc = new WebClient())
{
// Cancellation on the token will cancel the request
cancellationToken.Register(() =>
{
wc.CancelAsync();
Console.WriteLine($"Request cancelled!");
});
wc.DownloadDataCompleted += (s, e) =>
{
Console.WriteLine("FetchContentLengthWaitAllAsync Thread Id: " + Thread.CurrentThread.ManagedThreadId);
};
// 返回
return wc.DownloadDataTaskAsync(url);
}
}))).Sum(bytes => bytes.Length);
// Output
sw.Stop();
Console.WriteLine("FetchContentLengthDirectAsync Miliseconds: " + sw.ElapsedMilliseconds);
// Return
return contentLength;
}
I studied further and learned several posts like 'Concurrency vs. Parallel vs. Async in .NET' (https://dev.to/scotthannen/concurrency-vs-parallel-vs-async-in-net-3812) and 'Parallel Foreach async in C#' (https://medium.com/@alex.puiu/parallel-foreach-async-in-c-36756f8ebe62). They put me in vague than before. Just hands-on:
static async Task<long> FetchContentLengthAsync(Uri[] urls, CancellationToken cancellationToken)
{
// Start a Stopwatch to evaluate performance
var sw = new Stopwatch();
sw.Start();
// Request
var resultCollection = new ConcurrentBag<long>();
await Task.Run(() =>
{
Parallel.ForEach(urls, (url) =>
{
// Web client for the request
using (var wc = new WebClient())
{
// Cancellation on the token will cancel the request
cancellationToken.Register(() =>
{
wc.CancelAsync();
Console.WriteLine($"Request cancelled!");
});
// 返回
resultCollection.Add(wc.DownloadData(url).Length);
Console.WriteLine("FetchContentLengthAsync Thread Id: " + Thread.CurrentThread.ManagedThreadId);
}
});
});
// Output
sw.Stop();
Console.WriteLine("FetchContentLengthAsync Miliseconds: " + sw.ElapsedMilliseconds);
// Return
return resultCollection.Sum();
}
What are the differences? I implemented a test:
static async Task Main(string[] args)
{
// Requested URLs
var urls = new Uri[] { new Uri("https://dev.to/garryxiao/react-typescript-electron-all-steps-to-start-lcc"), new Uri("https://dev.to/bipinrajbhar/the-beginner-s-guide-to-react-introduction-50i0"), new Uri("https://stackoverflow.com/questions/22024233/read-response-header-from-webclient-in-c-sharp") };
// Cancellation token source
using (var cts = new CancellationTokenSource())
{
// WaitAll way
var contentLengthWaitAll = await FetchContentLengthWaitAllAsync(urls, cts.Token);
// Common
var contentLengthCommon = await FetchContentLengthAsync(urls, cts.Token);
}
}
Conclusions:
- Parallel.ForEach is quicker than Task.WhenAll. Parallel itself is synchronous.
- Parallel.ForEach is multiple threads solution while Task.WhenAll will probably share threads. If tasks share the same thread, they are just pieces of the thread and will need more time to complete the tasks.
- Because they are both concurrencies, so keep an eye on thread-safe issues.
- async/await are kinds of syntactic sugar that enables concurrency potential but not necessarily multithreading.
For more details: https://www.codemag.com/Article/1211071/Tasks-and-Parallelism-The-New-Wave-of-Multithreading
Posted on March 30, 2020
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.
Related
November 26, 2024