Performance is often one of the key focus points when building enterprise software. Many of the systems that we build rely heavily on communications with other systems. When these external communications become slow, then our software becomes slow. Unfortunately, we often have no control over the response time of the services that we depend on. However, we can optimize the way that we communicate with those services in order to ensure maximum performance. Running in parallel One of the best ways to optimize this type of software in C#, is by utilizing the (aka TPL). If you’re not familiar, this library provides a number of APIs that allow you to execute your code in parallel. This becomes especially valuable in the case mentioned previously, where we have some process that relies on multiple external communications. Task Parallel Library Managing Complexity When working with a large number of tasks that return different types, using the TPL can get complicated rather quickly. A common case can be seen as follows. Let’s say we have the following few methods that do some potentially long-running communications: { Task.Delay( ); } { stopwatch = Stopwatch.StartNew(); Task.Delay( ); stopwatch.ElapsedMilliseconds; } { response = httpClient.GetAsync(url);\ response.StatusCode; } Task ( ) static async SimulateWorkAsync await 1000 Task< > ( ) static async long SimulateLongWorkAsync var await 5000 return Task<HttpStatusCode> ( ) static async CheckHttpStatusAsync url string var await return One simple way to execute these methods in parallel is by using : Task.WhenAll await .WhenAll( SimulateWorkAsync(), SimulateLongWorkAsync(), CheckHttpStatusAsync( ), CheckHttpStatusAsync( ), CheckHttpStatusAsync( ) ); Task "http://www.google.com" "http://www.facebook.com" "http://slowwly.robertomurray.co.uk/delay/3000/url/http://www.microsoft.com" Side note: As seen used above, I’ve found to be a great website to simulate slow HTTP requests. It allows you to specify a delayed response directly in the URL. Slowwly While this does accomplish our goal of running the methods in parallel, what if we need to use the return value of these methods? One common way of achieving this is by keeping each in it’s own variable, so that the can be accessed later: Task .Result workTask = SimulateWorkAsync(); longWorkTask = SimulateLongWorkAsync(); googleTask = CheckHttpStatusAsync( ); facebookTask = CheckHttpStatusAsync( ); microsoftTask = CheckHttpStatusAsync( ); Task.WhenAll( workTask, longWorkTask, googleTask, facebookTask, microsoftTask ); var var var "http://www.google.com" var "http://www.facebook.com" var "http://slowwly.robertomurray.co.uk/delay/3000/url/http://www.microsoft.com" await As you can see, this approach can easily get complex if you start to add more methods. One thing we can do is use to handle the results of the tasks as continuations: Task.ContinueWith await Task.WhenAll( SimulateWorkAsync(), SimulateLongWorkAsync().ContinueWith( .WriteLine($ )), CheckHttpStatusAsync( ).ContinueWith( .WriteLine($ )), CheckHttpStatusAsync( ).ContinueWith( .WriteLine($ )), CheckHttpStatusAsync( ).ContinueWith( .WriteLine($ )) ); => cont Console "{nameof(SimulateLongWorkAsync)} took {cont.Result} ms." "http://www.google.com" => cont Console "http://www.google.com returned {cont.Result}" "http://www.facebook.com" => cont Console "http://www.facebook.com returned {cont.Result}" "http://slowwly.robertomurray.co.uk/delay/3000/url/http://www.microsoft.com" => cont Console "http://slowwly.robertomurray.co.uk/delay/3000/url/http://www.microsoft.com returned {cont.Result}" With this, we can see that the calls to are essentially the same (aside from the URL that’s passed in). We should be able to consolidate this even further by using LINQ to execute that method against a collection of URLs: CheckHttpStatusAsync urls = [] { , , }; await Task.WhenAll( SimulateWorkAsync(), SimulateLongWorkAsync().ContinueWith( .WriteLine($ )), urls.Select( CheckHttpStatusAsync(url).ContinueWith( .WriteLine($ ))) ); var new "http://www.google.com" "http://www.facebook.com" "http://slowwly.robertomurray.co.uk/delay/3000/url/http://www.microsoft.com" => cont Console "{nameof(SimulateLongWorkAsync)} took {cont.Result} ms." => url => cont Console "{url} returned {cont.Result}" However, this doesn’t work since we’re now passing different types ( and ) into . We can solve this problem by using the static methods to build a single that we can pass into : Task IEnumerable<Task> Task.WhenAll Enumerable IEnumerable<Task> Task.WhenAll urls = [] { , , }; await Task.WhenAll(Enumerable.Empty<Task>() .Append(SimulateWorkAsync()) .Append(SimulateLongWorkAsync().ContinueWith( .WriteLine($ ))) .Concat(urls.Select( CheckHttpStatusAsync(url).ContinueWith( .WriteLine($ )))) ); var new "http://www.google.com" "http://www.facebook.com" "http://slowwly.robertomurray.co.uk/delay/3000/url/http://www.microsoft.com" => cont Console "{nameof(SimulateLongWorkAsync)} took {cont.Result} ms." => url => cont Console "{url} returned {cont.Result}" Conclusion We’ve managed to find a way to execute a variety of tasks in parallel with a single statement. This approach is neat and concise, but more importantly it achieves our original goal of optimizing our software to handle the poor performance of others as best as we can.