10

I understand that calling task.Result in an async method can lead to deadlocks. I have a different twist on the question, though...

I find myself doing this pattern a lot. I have several tasks that return the same types of results, so I can await on them all at once. I want to process the results separately, though:

Task<int> t1 = m1Async();
Task<int> t2 = m2Async();
await Task.WhenAll(t1, t2);

Is it ok to call Result here, since I know the tasks are now completed?

int result1 = t1.Result;
int result2 = t2.Result;

Or, should I use await still...it just seems redundant and can be a bit uglier depending on how I need to process the results:

int result1 = await t1;
int result2 = await t2;

Update: Someone marked my question as a duplicate of this one: Awaiting multiple Tasks with different results. The question is different, which is why I didn't find it in my searches, though one of the detailed answers there does answer may question, also.

Community
  • 1
  • 1
Cary
  • 363
  • 4
  • 10
  • 1
    Yes, it is okay to call Result when you know. However, if you call .Result after WhenAll and t1 or t2 threw an exception, then .Result be problematic. – Travis J Oct 21 '16 at 22:46
  • I've seen some posts referring to different behaviors with exceptions, but I need to dig into that some more to understand it all. Thank you for your quick response! – Cary Oct 21 '16 at 22:50
  • One thing you should know. There is 0 performance overhead of calling `await` on a task already in the completed state. The state machine that gets generated checks for that condition and just skips right to basically calling `.Result` internally (It actually calls `.GetAwaiter().GetResult()`, but it has the same overhead as just calling `.Result`). So to me there is no compelling reason to ever use `.Result` inside a method that is already marked `async`. – Scott Chamberlain Oct 21 '16 at 23:04

2 Answers2

6

There's nothing inherently wrong or bad about using t1.Result after you've already done an await, but you may be opening yourself up to future issues. What if someone changes the code at the beginning of your method so you can no longer be positive the Tasks have completed successfully? And what if they don't see your code further down that makes this assumption?

Seems to me that it might be better to use the returned value from your first await.

Task<int> t1 = m1Async();
Task<int> t2 = m2Async();
var results = await Task.WhenAll(t1, t2);

int result1 = results[0];
int result2 = results[1];

That way, if someone messes with the first await, there's a natural connection for them to follow to know that your code later is dependent on its result.

You may also want to consider whether Task.WhenAll() is really giving you any value here. Unless you're hoping to tell the difference between one task failing and both failing, it might just be simple to await the tasks individually.

Task<int> t1 = m1Async();
Task<int> t2 = m2Async();

int result1 = await t1;
int result2 = await t2;
StriplingWarrior
  • 151,543
  • 27
  • 246
  • 315
  • Good point with using the return of WhenAll. It can get hairy to line up results in more complicated scenarios, though. And, if I await the tasks individually, aren't I serializing the calls, then? – Cary Oct 21 '16 at 23:02
  • 2
    @Cary: You're only serializing the calls if you await the first one before calling the other. If you call into `m2Async()` before you `await` the results from `m1Async()` then the second call will be underway before your execution moves out of scope. – StriplingWarrior Oct 21 '16 at 23:07
2

The doc says that Task.Result it is equivalent to calling the Wait method.

And when Wait is called

“What does Task.Wait do?”

... If the Task ran to completion, Wait will return successfully.

(from https://blogs.msdn.microsoft.com/pfxteam/2009/10/15/task-wait-and-inlining/)

So you can assume that when you call Task.Result, it will return successfully not leading to the deadlocks you mentioned.

Community
  • 1
  • 1
Felipe Sabino
  • 17,825
  • 6
  • 78
  • 112