11

I'm reading up more about async here: http://msdn.microsoft.com/en-us/library/hh873173(v=vs.110).aspx

Going through this example:

Task<bool> [] recommendations = …;
while(recommendations.Count > 0)
{ 
    Task<bool> recommendation = await Task.WhenAny(recommendations);    
    try
    {
        if (await recommendation) BuyStock(symbol);
        break;
    }
    catch(WebException exc)
    {
        recommendations.Remove(recommendation);
    }
}

I wonder, if I'm already performing await on Task.WhenAny why do I need to await again inside of the try block?

If I already did this: Task<bool> recommendation = await Task.WhenAny(recommendations); Why do this: if (await recommendation) BuyStock(symbol);

i3arnon
  • 113,022
  • 33
  • 324
  • 344
amhed
  • 3,649
  • 2
  • 31
  • 56

6 Answers6

10

The first await exists to asynchronously wait for the first task to complete (i.e. recommendation). The second await is only there to extract the actual result out of the already completed task, and throw exceptions stored in the task. (it's important to remember that awaiting a completed task is optimized and will execute synchronously).

A different option to get the result would be using Task<T>.Result, however it differs in the way it handles exceptions. await would throw the actual exception (e.g WebException) while Task<T>.Result would throw an AggregateException containing the actual exception inside.

Task<bool> [] recommendations = …;
while(recommendations.Count > 0)
{ 
    Task<bool> recommendation = await Task.WhenAny(recommendations);    
    try
    {
        if (recommendation.Result) 
        {
            BuyStock(symbol);
        }
        break;
    }
    catch(AggregateException exc)
    {
        exc = exc.Flatten();
        if (exc.InnerExceptions[0] is WebException)
        {
            recommendations.Remove(recommendation);
        }
        else
        {
            throw;
        }
    }
}

Clearly awaiting the task is simpler and so it's the recommended way of retrieving a result out of a task.

i3arnon
  • 113,022
  • 33
  • 324
  • 344
  • 1
    This would throw an `AggregateException`, not a `WebException`, if the `Task` is faulted as a result of a `WebException`. – Servy Sep 02 '14 at 15:00
  • @Servy clarified that. – i3arnon Sep 02 '14 at 15:27
  • 2
    You've now mentioned that there is a difference, but you have still broken the code as a result of your change, because you have changed the error handling semantics in a way the code doesn't support. The crux of your answer, that you can just change the code to use `Result`, is inherently wrong, because by making that change you broke the code. There's a *very good reason* that the author of this code used `await` here. – Servy Sep 02 '14 at 15:27
  • @Servy I wasn't recommending using that code, it was part of the answer to "why use `await`?". I clarified that as well. – i3arnon Sep 02 '14 at 15:44
  • You asserted it gave the exact same behavior, but it didn't, because you broke the code. Now all that's left of your answer is the remnants of your incorrect answer, which is, by your own admission, clearly not useful at all. – Servy Sep 02 '14 at 15:47
  • @Servy The assertion was removed. That was the clarification. And I disagree it wasn't useful. The question was "Why is that code using `await`?" and the answer always explained why. – i3arnon Sep 02 '14 at 15:54
  • No, it didn't, because the whole point of using `await` here is that it *doesn't* just get the result out. It gets the result out *with a particular set of error handling semantics* that are vital to the function of the method. And breaking the code in such a wait that it won't work, but it won't work in subtle and hard to debug ways is about as un-useful as an answer can get. At this point pretty much the only useful content in your answer is the stuff that I told you needed to be in your answer, with most of the rest being *actively harmful*. – Servy Sep 02 '14 at 15:58
  • @Servy The question is basically why is there an extra `await` on top of the `await Task.WhenAny(...)` and the answer is to get out a result. It's true that it matters in what way that is done, and I'm glad you pointed that bug out. But even without the comparison with `Task.Result` this post answers the OP's question. – i3arnon Sep 02 '14 at 16:10
5

The use of await here creates the desired error handling semantics. If he used Result instead of await then the AggregateException would be rethrown directly; when using await the first exception within the AggregateException is pulled out an that exception is re-thrown. Clear the author of this code wanted the WebException to be thrown, rather than an AggregateException that he would need to manually unwrap.

Could he have used another approach, sure. This was simply the approach that the author of the code preferred, as it allows him to write the code more like traditional synchronous code rather than radically changing the style of the code.

Servy
  • 202,030
  • 26
  • 332
  • 449
4

You're right. It is not necessary. You could replace it with

if (recommendation.Result) 
    BuyStock(symbol);

Also note that await will not await(will not set continuation) when completed task is given. It will just execute synchronously in that case as a optimization. I guess author leverages that optimization.

If you ask why author wrote that way, May be consistency? only he knows!.

Sriram Sakthivel
  • 72,067
  • 7
  • 111
  • 189
1

If I already did this: Task recommendation = await Task.WhenAny(recommendations); Why do this: if (await recommendation) BuyStock(symbol);

Because Task.WhenAny returns a Task<Task<bool>> and you want to unwrap the outter Task to retrieve the resulting bool. You could do the same by accessing the Task.Result property of the returned Task

Yuval Itzchakov
  • 146,575
  • 32
  • 257
  • 321
1

Other answers have pointed out that you have to await the task returned by await Task.WhenAll to unwrap the return value (alternatively, you can use the Result property).

However, you can also get rid of your try/catch (and it's a good thing to avoid catching unnecessary exception)

Task<bool> recommendation = await Task.WhenAny(recommendations);    
if(!recommendation.IsFaulted)
{
    if (await recommendation) BuyStock(symbol);
    break;
}
else
{
    if(recommendation.Exception.InnerExceptions[0] is WebException)
    {
        recommendations.Remove(recommendation);
    }
    else
    {
        throw recommendation.Exception.InnerExceptions[0];
    }
}
Falanwe
  • 4,636
  • 22
  • 37
  • Are you sure that when task is faulted `WhenAny` doesn't throw exception? – Sriram Sakthivel Sep 02 '14 at 13:37
  • @SriramSakthivel : perfectly sure. `WhenAny`returns a Task whose result is the firs Task that completed. A Task is completed if either it is successful or it is faulted. If a Task faults, `WhenAny`succeeds ans it gives you the first completed Task (i.e. the faulted one). – Falanwe Sep 02 '14 at 13:43
  • Wouldn't `recommendation.Exception` contain an `AggregateException`? – i3arnon Sep 02 '14 at 14:17
  • @I3arnon : you're right. I have updated mys answer accordingly. – Falanwe Sep 02 '14 at 14:57
0

Because Task.WhenAny<TResult>(IEnumerable<Task<TResult>> tasks) returns a Task<Task<TResult>>. The outer task (the one created by the Task.WhenAny call) will complete when any of the tasks passed to it completes with the completed task as a result.

khellang
  • 17,550
  • 6
  • 64
  • 84