3

I'm using ASP.Net MVC 5 and am creating a controller to allow a manager to add a user to roles. This is what a non-async version of the controller would look like:

public JsonResult UerRoles(string userid, string[] roles)
{
   foreach(var role in roles)
   {
      if(!UserManager.IsInRole(userid, role))
      {
         UserManager.AddToRole(userid, role);
      }
    }

    ...
 }

I want to use the UserManager.IsInRoleAsync and UserManager.AddToRoleAsync methods and would like to run these in parallel and then halt execution of the thread until everything is completed. I've not done asynchronous threading before, but it appears that I could do something like this:

public async Task<JsonResult> UserRoles(string userid, string[] roles)
{
   IList<Task<IdentityResult>> tasks = new List<Task<bool>>();
   foreach(var role in roles)
   {
      tasks.Add(UserManager.AddToRoleAsync(userid, role));
   }

   await Task.WhenAll(tasks);

   ...
 }

However, I somehow need to account for the conditional logic of checking if the user is already in the role -- i.e. UserManager.IsInRoleAsycn(userid, role). I'm not sure how to check for that and conditionally add Users to roles all in parallel and asynchronously.

I've seen the ContinueWith method mentioned and it seems like maybe that somehow applies but I can't figure out how that would be implemented. Something like:

UserManager.IsInRoleAsync(userid, role).ContinueWith(t => if(t.Result) { UserManager.AddToRole(userid, role)} ;);

Is there a way to accomplish this?

trailmax
  • 34,305
  • 22
  • 140
  • 234
RHarris
  • 10,641
  • 13
  • 59
  • 103
  • 1
    I don't think you need to use `Tasks` at all. Just await the `Async` methods – Jonesopolis Aug 15 '14 at 14:46
  • 1
    `WhenAll` doesn't run the tasks in parallel. For that you would have to actually spawn a new thread for each task, which defeats the purpose of async in the first place. – Chris Pratt Aug 15 '14 at 14:59

1 Answers1

2

Assumption: You are using standard EF implementation of AspNet Identity

You will have to work with this code:

foreach(var role in roles)
{
   var isInRole = await UserManager.IsInRoleAsync(userid, role)
   if(!isInRole)
   {
      await UserManager.AddToRoleAsync(userid, role);
   }
}

The reason for this underlying ORM (Entity Framework) does not support execution of sql queries in parallel threads. If you try that, you will get exception from EF saying something like this:

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.

This topic has a good example of what happens.

Also, I doubt that adding user to a role is a very popular operation in your system. Why go out of your way to optimise something that will not be hit that often?

Update

You don't need to do checking for IsUserInRole. Identity framework does it anyway for you. This is a part of decompiled Identity framework:

public virtual async Task<IdentityResult> AddToRoleAsync(TKey userId, string role)
{
    // sanity checks... 

    IList<string> userRoles = await userRoleStore.GetRolesAsync(user).ConfigureAwait(false);
    IdentityResult identityResult;
    if (userRoles.Contains(role))
    {
      identityResult = new IdentityResult(new string[1]
      {
        Resources.UserAlreadyInRole
      });
    }
    else
    {
    // actually add user to role
    }
    return identityResult;
}
Community
  • 1
  • 1
trailmax
  • 34,305
  • 22
  • 140
  • 234
  • Thanks a lot. Never considered the EF implications. – RHarris Aug 15 '14 at 15:00
  • Note that if you choose NOT to test for IsInRole, then you will have to add an extra test for Success. If the decompiled AddToRoleAsync (and also AddToRole) code, indicates that Identity result is NOT SUCCESSFUL. IdentityResult.Succeeded will be FALSE. and you will have to test for it, or test the Errors for "Already added to Role" and ignore it. IMO - it would be more robust to stick to using using `UserManager.IsInRoleAsync(userid, role)` – OzBob Dec 16 '14 at 07:54