2

My MVC app ocasionally results in a deadlock. I think it is likely due to a faulty way I am collecting data from completed async tasks.

I have two independent async methods.

var task1 = GetNamesFromSource1Async(); // a database call, may throw an exception
var task2 = GetNamesFromSource2Async(); // a database call, may throw an exception

var total = new List<string>();

await Task.WhenAll(task1, taks2).ConfigureAwait(false);

Question part 1: What is the safest recommended way and the best practice to collect results from these tasks:

// here I already know that both tasks are completed and 
// I am using (or abusing?) await to get task results
List<string> names1 = await task1.ConfigureAwait(false);
List<string> names2 = await task2.ConfigureAwait(false);

if (names1 != null) total.AddRange(names1);
if (names2 != null) total.AddRange(names2);

or

total.AddRange(task1.IsFaulted ? new List<string> : task1.Result);
total.AddRange(task2.IsFaulted ? new List<string> : task2.Result);

?

Question part 2: in addition if I want to transform data from the first source, is it safe to use ContinueWith (when I say safe I mean from the standpoint of deadlocks)

var task1 = GetNamesFromSource1Async().ContinueWith(t => 
 {
   if ( !t.IsFaulted && t.Result != null)
   {
      return t.Result.Take(1).ToList();
   }
 });

Remark: here I am trying control for exceptions within each of the tasks by checking IsFaulted flag.

A recommendation on the best practice to solve this problem would be highly appreciated. I am using .NET 4.5

Nkosi
  • 235,767
  • 35
  • 427
  • 472
AstroSharp
  • 1,872
  • 2
  • 22
  • 31

1 Answers1

4

.Result is a blocking call and can lead to deadlock when mixed with async/await

var task1 = GetNamesFromSource1Async(); // a database call, may throw an exception
var task2 = GetNamesFromSource2Async(); // a database call, may throw an exception

var total = new List<string>();

var results = await Task.WhenAll(task1, task2);

total.AddRange(results.Where(s => s != null && s.Count > 0).SelectMany(s => s));

Update

The above assumed the return types were all the same.

However from your comment...

How would you modify the last line if I still need to collect results but task1 and task2 are based on different types?

and referencing this answer

Awaiting multiple Tasks with different results

Then it would be modified as

var task1 = GetNamesFromSource1Async(); // a database call, may throw an exception
var task2 = GetNamesFromSource2Async(); // a database call, may throw an exception

var total = new List<string>();

await Task.WhenAll(task1, task2);

List<String> names1 = await task1;
List<int> names2 = await task2;

//...process results
Community
  • 1
  • 1
Nkosi
  • 235,767
  • 35
  • 427
  • 472
  • 1
    great answer, thank you. How would you modify the last line if I still need to collect results but task1 and task2 are based on different types? Say task1 is Task> but task2 is Task> ? – AstroSharp Feb 24 '17 at 17:33
  • Do a projection in the SelectMany that returns a common type. – Nkosi Feb 24 '17 at 17:35
  • Found answer actually : http://stackoverflow.com/questions/17197699/awaiting-multiple-tasks-with-different-results identical what I have been doing with await – AstroSharp Feb 24 '17 at 17:36
  • The question only ever used `Result` on an already completed `Task`, and as such wouldn't result in a blocking operation. – Servy Feb 24 '17 at 18:34