-1

I have to retrieve results from two different methods/stored procedures and return combiled results. Is this the most efficient way to use Async Wait or do I need to also wait for both to complete before combining results?

public async Task<IEnumerable<Reports>> GetReports(int days = 0)
{
    List<Reports> data = new List<Reports>();

    List<Reports> AdHocdata = (List<Reports>) await GetAdHocReports(days);
    List<Reports> Pushdata = (List<Reports>) await GetPushReports(days);

    data.AddRange(AdHocdata);
    data.AddRange(Pushdata);

    return data;
}
  • 2
    You should use task.whenall to let both tasks run in parallel. To get results see her https://stackoverflow.com/questions/25319484/how-do-i-get-a-return-value-from-task-waitall-in-a-console-app | https://stackoverflow.com/questions/17197699/awaiting-multiple-tasks-with-different-results – Rand Random Jul 15 '22 at 00:41
  • That looks fine. When you `await` you can immediately use the result – Poul Bak Jul 15 '22 at 00:42
  • 1
    you might look at Task.WhenAll, a good discussion is on this post: https://stackoverflow.com/questions/12337671/using-async-await-for-multiple-tasks – Geoffrey McGrath Jul 15 '22 at 00:43

3 Answers3

3

What you're doing is fine, although it will wait for one report to finish before even starting the second one. If you'd like to run them in parallel, don't await the task until you actually need the data.

public async Task<IEnumerable<Reports>> GetReports(int days = 0)
{
    List<Reports> data = new List<Reports>();

    var x = GetAdHocReports(days);  //Start task, but don't wait
    var y = GetPushReports(days);   //Start task, but don't wait

    data.AddRange(await x);   //Wait for and get the data
    data.AddRange(await y);   //Wait for and get the data

    return data;
}
John Wu
  • 50,556
  • 8
  • 44
  • 80
1

do I need to also wait for both to complete before combining results?

You already are. Both awaits must complete before the execution reaches the AddRange calls. Note that this also means the awaits run sequentially: the next has not begun until the previous has completed.

If these two reports can be obtained in parallel, then you could use Task.WhenAll to do a single blocking await instead:

public async Task<IEnumerable<Reports>> GetReports(int days = 0)
{
    // The `System.Collections.Concurrent` classes are preferred
    // since you're accessing the object in parallel
    ConcurrentBag<Reports> data = new ConcurrentBag<Reports>();

    await Task.WhenAll(new Task[]
    {
        Task.Run(async () => data.AddRange(await GetAdHocReports(days))),
        Task.Run(async () => data.AddRange(await GetPushReports(days))),
    });

    return data.AsEnumerable();
}
Ved
  • 301
  • 3
  • 6
  • 3
    Note that @Ved is using a `ConcurrentBag` This is very important because it is a thread-safe collection. If it was not, you could get exceptions because multiple threads tried to access the same resource. If you want to read more about what a thread-safe collection is, you can read about it here -> https://learn.microsoft.com/en-us/dotnet/standard/collections/thread-safe/ – Joost00719 Jul 15 '22 at 06:32
  • Why is `Task.Run` needed? – John Wu Jul 15 '22 at 18:18
0

This is what I ended up implementing but it looks like there is at least 3 good answers here. Thanks for all the ideas and feedback. It will definitely help me going forward.

public async Task<IEnumerable<Reports>> GetReports(int days = 0)
{

    Task<IEnumerable<Reports>> AdHocTask = GetAdHocReports(days);
    Task<IEnumerable<Reports>> PushTask = GetPushReports(days);

    Task.WaitAll(AdHocTask, PushTask);

    List<Reports> data = new List<Reports>();
    data.AddRange(AdHocTask.Result);
    data.AddRange(PushTask.Result);
    return data;
}