i could not understand why two awaits are used.

rajesh yadav 291 Reputation points
2025-08-06T10:46:40.8166667+00:00

q1) follosing code is from the followoing link

https://learn.microsoft.com/en-us/dotnet/csharp/asynchronous-programming/start-multiple-async-tasks-and-process-them-as-they-complete

while (downloadTasks.Any())
    {
        Task<int> finishedTask = await Task.WhenAny(downloadTasks);
        downloadTasks.Remove(finishedTask);
        total += await finishedTask;
    }

Developer technologies | ASP.NET | ASP.NET Core
{count} votes

2 answers

Sort by: Most helpful
  1. Bruce (SqlWork.com) 79,101 Reputation points Volunteer Moderator
    2025-08-06T15:52:03.65+00:00

    finishedTask is still an awaitable task (even though it is in a completed status). the await is the most efficient method to unwrap an awaitable task result. the other option is:

    total += finishedTask.GetAwaiter().GetResult();

    note: while you can use finishedTask.Result, this is a legacy method for compatibility from before async/await, and is not recommended. For compatibility it has some odd processing rules and its use can lead to deadlocks.

    1 person found this answer helpful.
    0 comments No comments

  2. Jack Dang (WICLOUD CORPORATION) 1,020 Reputation points Microsoft External Staff
    2025-08-07T03:50:23.62+00:00

    Hi @rajesh yadav ,

    Thanks for reaching out with your question about the C# code from the Microsoft Learn documentation. I understand the two await keywords in the while loop can be puzzling, especially when you're new to async programming. The code snippet you’re referring to is:

    while (downloadTasks.Any())
    {
        Task<int> finishedTask = await Task.WhenAny(downloadTasks);
        downloadTasks.Remove(finishedTask);
        total += await finishedTask;
    }
    

    Let’s break this down:

    This program is downloading data from a bunch of websites at once, like ordering food from multiple restaurants and wanting to check each order as it arrives. Each website download is a “task” that returns the size of the downloaded data (in bytes). The SumPageSizesAsync method starts all these downloads and processes them as they finish, adding up the sizes to get a total. The while loop with the two awaits is where it handles each completed download.

    Let’s walk through the loop step-by-step, using a simple analogy to make it click:

    1. First await (waiting for the first delivery):
         Task<int> finishedTask = await Task.WhenAny(downloadTasks);
      
      • What’s happening? The downloadTasks list contains tasks, each one downloading a website’s data. Task.WhenAny watches all these tasks and waits for the first one to finish, like waiting at your door for the first food delivery to show up.
      • Why await? The await pauses the program until one task completes. When it does, Task.WhenAny gives you a Task<int> called finishedTask. This is like the delivery person handing you a takeout bag—it’s got the website’s size inside, but you haven’t opened it yet.
      • Technical detail: Task.WhenAny returns a Task<Task<int>>, which resolves to the first completed Task<int>. The await unwraps this to give you the Task<int>—the specific task that finished.
    2. Remove the Task:
         downloadTasks.Remove(finishedTask);
      
      • This removes the finished task from the list, so you don’t process it again. It’s like checking off the delivery from your order list once the food arrives.
    3. Second await (checking inside the bag):
         total += await finishedTask;
      
      • What’s happening? The finishedTask is a Task<int>, like a takeout bag with the website’s size (a number) inside. The second await opens the bag to get that number and adds it to total.
      • Why await? Even though the task is done (the bag’s at your door), the number is still wrapped in the Task<int>. You need await to safely extract the number. It’s like opening the bag to see how much food is inside.
      • Technical detail: A Task<int> is a container for an integer result. await retrieves the int (the website’s size). If the task failed (e.g., the website didn’t load), await throws a specific error like HttpRequestException, which is easier to handle than other methods.

    You might wonder, “If finishedTask is done, why not use finishedTask.Result or finishedTask.GetAwaiter().GetResult()?” Here’s why:

    • .Result: This older method can cause deadlocks (freezing your program) in contexts like UI apps or ASP.NET and wraps errors in a complex AggregateException, making debugging harder.
    • .GetAwaiter().GetResult(): This also retrieves the result synchronously but shares the same deadlock risks as .Result. It’s not common in modern async code.
    • Why await is better: await is the safe, standard way to get the result, avoiding deadlocks and handling errors cleanly (e.g., throwing HttpRequestException for failed downloads).

    I can see why this part tripped you up—async programming can feel like a puzzle at first! Here are a couple of reasons why the two awaits might have been confusing:

    1. Thinking Task.WhenAny Gives the Number Directly: You might’ve expected Task.WhenAny to hand you the website’s size right away. Instead, it just tells you which task finished, giving you a Task<int> that you still need to “open” with the second await.
    2. New to Tasks: If you’re still getting used to how Task<int> works, it’s not obvious that it’s like a box holding a number. The await keyword is how you open that box, and the docs don’t always make that super clear for beginners.
    3. Technical Jargon: The documentation uses terms like Task<TResult> and AggregateException, which can sound intimidating. It doesn’t explicitly say, “Hey, you need two awaits because the first one gives you the task, and the second one gets the number out of it.”

    A Quick Analogy to Seal the Deal

    Imagine you’re waiting for food deliveries:

    • First await: You’re watching for the first delivery person to arrive. Task.WhenAny tells you who got there first and hands you their takeout bag (finishedTask).
    • Second await: You open the bag to check how much food is inside (the website’s size) so you can add it to your total.

    You need both steps: one to know who arrived, and another to see what they brought.


    Key Takeaways

    • The first await (await Task.WhenAny) finds the first completed download and gives you the task (the takeout bag).
    • The second await (await finishedTask) opens the task to get the website’s size (the number inside).
    • Using await is the safe, professional way to handle tasks, avoiding issues that .Result can cause.
    • This setup lets the program process downloads as they finish, which is efficient when tasks take different amounts of time.

    I hope this clears things up, Rajesh! If you agree with my answer, feel free to interact with the system accordingly!


Your answer

Answers can be marked as Accepted Answers by the question author, which helps users to know the answer solved the author's problem.