24

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?

2
  • 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 = ... Commented Apr 23, 2015 at 10:55
  • result isn't defined in the code block. Presumably, you meant it to be return Json(things, JsonRequestBehavior.AllowGet)? Commented Jan 24, 2018 at 18:40

1 Answer 1

35
  • 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.

Sign up to request clarification or add additional context in comments.

4 Comments

"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.
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.
@M.Babcock Correct, it's fixed now. Thanks
This method is an anti pattern. The reason in that you don't know how much items in a collection. It can overload ThreadPool. In this case you should use TPL or split items on small blocks and WaitAll in a loop limited number of tasks.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.