How to use async within a lambda which returns a collection

12,710
  • Convert your collection of Things into a collection of Task<Thing>s.
  • Then join all those tasks using Task.WhenAll and await it.
  • Awaiting the joint task will give you a Thing[]


public async Task<JsonResult>  GetLotsOfStuff()
{
    IEnumerable<Task<ThingDetail>> tasks = collection.Select(q => GetDetailAboutTheThing(q.Id));

    Task<int[]> jointTask = Task.WhenAll(tasks);

    IEnumerable<ThingDetail> things = await jointTask;

    return Json(things, JsonRequestBehavior.AllowGet);
}

Or, succinctly and using type inference:

public async Task<JsonResult>  GetLotsOfStuff()
{
    var tasks = collection.Select(q => GetDetailAboutTheThing(q.Id));
    var things = await Task.WhenAll(tasks);

    return Json(things, JsonRequestBehavior.AllowGet);
}

Fiddle: https://dotnetfiddle.net/78ApTI

Note: since GetDetailAboutTheThing seems to return a Task<Thing>, the convention is to append Async to its name - GetDetailAboutTheThingAsync.

Share:
12,710

Related videos on Youtube

Program.X
Author by

Program.X

Full-stack developer, working on Microsoft SQL Server, .NET and through to JavaScript. Worked on web sites for NHS requiring high levels of accessibility, e-Commerce for leading media vendors, CRM and business solutions and FinTech.

Updated on June 19, 2022

Comments

  • Program.X
    Program.X almost 2 years

    I have a method which is Async "upstream". I'm trying to follow best practice and go all-in qith async all the way up the stack.

    Within a Controller action within MVC I predictably hit the deadlock issue If I rely on .Result().

    Changing the Controller action to async seems to be the way to go, though the issue is that the async method is called multiple times within a lambda.

    How can I await on a lamda that returns multiple results?

    public async Task<JsonResult>  GetLotsOfStuff()
    {
        IEnumerable<ThingDetail> things=  previouslyInitialisedCollection
                                          .Select(async q => await GetDetailAboutTheThing(q.Id)));
        return Json(result, JsonRequestBehavior.AllowGet);
    
    }
    

    You can see I have tried making the lambda async, but this just gives a compiler exception:

    Cannot convert source type

    System.Collections.Generic.IEnumerable<System.Threading.Tasks.Task<ThingDetail> to target type System.Collections.Generic.IEnumerable<ThingDetail>

    Where am I going wrong here?

    • Grundy
      Grundy about 9 years
      now your select return enumeration with task, but you try assign it to enumeration with ThingDetail, as vatiant you can use var keyword instead declare type directly like: var things=... instead of IEnumerable<ThingDetail> things = ...
    • M.Babcock
      M.Babcock over 6 years
      result isn't defined in the code block. Presumably, you meant it to be return Json(things, JsonRequestBehavior.AllowGet)?
  • Program.X
    Program.X about 9 years
    "That's the way to do it". Excellent, thanks. I had tried the WhenAll method but obviously had missed an interim step. I take your point about the Async method naming convention and I do agree. I'll implement this.
  • M.Babcock
    M.Babcock over 6 years
    Shouldn't the last line in both code blocks be return Json(things, JsonRequestBehavior.AllowGet);? result doesn't appear to be defined in either code block.
  • dcastro
    dcastro over 6 years
    @M.Babcock Correct, it's fixed now. Thanks