3

How can I use the async/await keywords correctly in a lambda expression ? here is the code :

public async Task<IHttpActionResult> GetUsers() {

        var query = await _db.Users.ToListAsync();

        var users = query.Select(async u =>  new
        {
            FirstName = u.FirstName,
            LastName = u.LastName,
            IsGeek = await _userManager.IsInRoleAsync(u.Id, "Geek")
        });

        return Ok(users);
}

As you can see this code is running inside a webapi controller, it compiles without any error, the problem is it needs an extra await somewhere because this action never retuns.

Notice that _db and _usermanager are the DbContext and the UserManagerfor the application.

Thanks.

Update :

This equivalent code never fails (but it's not ellegant :( ):

var query = await _db.Users.ToListAsync();
var users = new List<object>();
foreach (var u in query)
{
     bool IsGeek = await _userManager.IsInRoleAsync(u.Id, "IsGeek");
     users.Add( new {
         FirstName = u.FirstName,
         LastName = u.LastName,
         IsGeek= IsGeek
      });
 }
return Ok(users);
BornToCode
  • 9,495
  • 9
  • 66
  • 83
dafriskymonkey
  • 2,189
  • 6
  • 25
  • 48
  • 1
    This probably never returns because you are synchronously waiting somewhere. The classic ASP.NET deadlock. Maybe in the user manager? – usr Nov 29 '14 at 16:23
  • @usr I dont think so, the `_usermanager ` inherits from the AspNet.Identity `UserManager`. – dafriskymonkey Nov 29 '14 at 16:54
  • "it needs an extra await somewhere" -- what do you mean by that? Why would adding an `await` improve things? As far as the "never returns", it's not clear how you call this, but if the caller is `await`ing, the only two places in the method where it could set stuck are the call to `ToListAsync()`, and the call to `Ok()`. Your first step here is to watch the execution in a debugger and see which it is, and then track down exactly where the code is at that point (i.e. what is the call that didn't return actually waiting on). – Peter Duniho Nov 29 '14 at 16:58
  • OK, it's probably correct then. How do you know this action never returns? When you pause the debugger, is there a thread waiting somewhere with a meaningful callstack? When you step through with the debugger where does it hang? – usr Nov 29 '14 at 16:59
  • @usr I was trying it with an ajax call and fiddler, both never came back. – dafriskymonkey Nov 29 '14 at 17:08
  • @dafriskymonkey that answer you wrote should be added as an answer to your question and marked as the right answer. The one answer below "looks" like it must be the right answer to the unassuming reader (myself, earlier today) since it's the only one posted and has upvotes, but it doesn't actually solve your problem or meet your needs. – pbristow Oct 14 '20 at 21:10

1 Answers1

12

Think about your types.

var query = await _db.Users.ToListAsync();

query is a list of users.

var users = query.Select(async u =>  new
{
    FirstName = u.FirstName,
    LastName = u.LastName,
    IsGeek = await _userManager.IsInRoleAsync(u.Id, "Geek")
});

When you Select with an async lambda, the result is a sequence of tasks. So, to (asynchronously) wait for all those tasks to complete, use Task.WhenAll:

var result = await Task.WhenAll(users);
return Ok(result);
Stephen Cleary
  • 437,863
  • 77
  • 675
  • 810
  • But why does the request hang? – usr Nov 29 '14 at 16:59
  • 1
    Internal server error :) "A second operation started on this context before a previous asynchronous operation completed. Use 'await' to ensure that any asynchronous operations have completed before calling another method on this context. Any instance members are not guaranteed to be thread safe." – dafriskymonkey Nov 29 '14 at 17:05
  • 1
    I still think we need an extra `await` somewhere. – dafriskymonkey Nov 29 '14 at 17:07
  • @dafriskymonkey: Other code is likely causing that exception. Please post a minimal, reproducible example. – Stephen Cleary Nov 29 '14 at 17:08
  • @StephenCleary This is the minimal reproducible example. The inner exception says : "The connection does not support MultipleActiveResultSets" !!! – dafriskymonkey Nov 29 '14 at 17:14
  • 4
    That's your answer then, you cannot parallelize this task, because the underlying data that you are accessing requires you to only do one query at a time – Ana Betts Nov 29 '14 at 17:23
  • I'm confused. Doesn't this solution obviously not work if it is a standard SQL connection, as the connection doesn't allow parallel calls via the IsInRoleAsync call? @StephenCleary do you have an answer that would work given reasonable assumptions about the tech stack / use case (i.e. using SQL server and ASP.NET Identity)? – pbristow Oct 14 '20 at 13:48
  • @pbarranis: I never use my own database for user/role permissions; I use an OAuth API that does allow parallel calls. If you are using a MSSQL database, then yes, this would not work. In that case, you'd need multiple connections, and I'm not sure how to do that with `UserManager`. – Stephen Cleary Oct 14 '20 at 17:19