Convert a method to use async

2019-03-05 05:54发布

问题:

I am converting a authentication process to support async and the VS 2015 IDE is warning me with the following message: The async method lacks 'await' operators and will run synchronously... etc...

Anyway, the code connects to a LDAP store and verifies a user's account and etc... I have tried various things with await, but I am just missing something here. I put the code back to what it was before.. I would appreciate any guidance in getting it to support async correctly...

Here is the code:

public async Task<User> GetAsyncADUser(PrincipalContextParameter param)
    {
        try
        {

            if (UseLDAPForIdentityServer3)
            {
                using (var pc = new PrincipalContext(ContextType.Domain, param.ADDomain, param.ADServerContainer, param.ADServerUser, param.ADServerUserPwd))
                {
                    UserPrincipal userPrincipal = UserPrincipal.FindByIdentity(pc, param.UserNameToValidate);
                    if (userPrincipal != null)
                    {
                        bool isvalid = pc.ValidateCredentials(userPrincipal.DistinguishedName, param.UserPasswordToValidate, ContextOptions.SimpleBind);

                        if (isvalid)
                        {
                            User user = new User { ad_guid = userPrincipal.Guid.ToString(), Username = param.UserNameToValidate, Password = param.UserPasswordToValidate };
                            return user;
                        }
                    }
                }
            }

        }
        catch (Exception ex)
        {
            throw;
        }

        return null;

    }

回答1:

From MSDN:

The following characteristics summarize what makes an async method:

  • The method signature includes an async modifier.
  • The name of an async method, by convention, ends with an "Async" suffix. The return type is one of the following types:

    • Task<TResult> if your method has a return statement in which the operand has type TResult.
    • Task if your method has no return statement or has a return statement with no operand.
    • Void if you're writing an async event handler.
  • The method usually includes at least one await expression, which marks a point where the method can't continue until the awaited asynchronous operation is complete. In the meantime, the method is suspended, and control returns to the method's caller. The next section of this topic illustrates what happens at the suspension point.

You can use return Task.Run(() => { /* your code here */ }) and return a Task<User>. Then you can call this method as :

User user = await GetAsyncADUser();

That way, you don't need to use the async keyword in the method GetAsyncADUser, but you need to mark the method that uses the above line of code with the async keyword.



回答2:

The try catch block can be left.... To run the code async, just put it in an action within Task.Run:

public async Task<User> GetAsyncADUser(PrincipalContextParameter param)
{        
    if (UseLDAPForIdentityServer3)
    {
        return await Task.Run() =>
        {
            using (var pc = new PrincipalContext(ContextType.Domain, param.ADDomain, param.ADServerContainer, param.ADServerUser, param.ADServerUserPwd))
            {
                UserPrincipal userPrincipal = UserPrincipal.FindByIdentity(pc, param.UserNameToValidate);
                if (userPrincipal != null)
                {
                    bool isvalid = pc.ValidateCredentials(userPrincipal.DistinguishedName, param.UserPasswordToValidate, ContextOptions.SimpleBind);

                    if (isvalid)
                    {
                        User user = new User { ad_guid = userPrincipal.Guid.ToString(), Username = param.UserNameToValidate, Password = param.UserPasswordToValidate };
                        return user;
                    }
                }
            }
        }
    }
    return null;
}


回答3:

Just as it says, your code is synchronous. If you wrapped whatever UserPrincipal.FindByIdentity and PrincipalContext.ValidateCredentials do in a Task and return that, then you could have this do its work async.

As Stephen Cleary's comment said, if there is a lower level you can perform the work in async, do it there and pass the async/await up to this level. Hard to say without knowing what those methods look like though.

public Task<UserPrincipal> FindByIdentity (PrincipalContext pc, string username)
    {
        return Task.Run(() =>
        {
            // Do the thing;
        });
    }

That would allow you to put awaits on them and your code would be async.

public async Task<User> GetADUserAsync(PrincipalContextParameter param)
    {
        try
        {
            if (UseLDAPForIdentityServer3)
            {
                using (var pc = new PrincipalContext(ContextType.Domain, param.ADDomain, param.ADServerContainer, param.ADServerUser, param.ADServerUserPwd))
                {
                    UserPrincipal userPrincipal = await UserPrincipal.FindByIdentity(pc, param.UserNameToValidate);
                    if (userPrincipal != null)
                    {
                        bool isvalid = await pc.ValidateCredentials(userPrincipal.DistinguishedName, param.UserPasswordToValidate, ContextOptions.SimpleBind);

                        if (isvalid)
                        {
                            User user = new User { ad_guid = userPrincipal.Guid.ToString(), Username = param.UserNameToValidate, Password = param.UserPasswordToValidate };
                            return user;
                        }
                    }
                }
            }

        }
        catch (Exception ex)
        {
            throw;
        }

        return null;

    }


回答4:

It appears in this code block there is no asynchronous call to be awaited.... By that I mean, no method call that returns a Task.

For example, if the method UserPrincipal.FindByIdentity() was an async method, (returns Task<UserPrincipal>) then it could be awaited like so:

UserPrincipal userPrincipal = await UserPrincipal.FindByIdentity();

But, it is not an async method therefore there is nothing to be awaited. One thing you can do here is wrap the code you want to run asynchronously in a helper method, execute that helper method via Task.Run(() => RunMeAsync()); and await the result of the new task you just started.

var result = await Task.Run(() => RunMeAsync());

where RunMeAsync() is the helper method that you want to run asynchronously.