KeyVault GetSecretAsync永远不会返回

时间:2015-10-14 20:03:30

标签: c# .net azure async-await

在Web应用程序中使用KeyVault的示例代码中包含以下代码:

public static async Task<string> GetSecret(string secretId)
{
    var secret = await keyVaultClient.GetSecretAsync(secretId);
    return secret.Value;
}

我已将示例中包含的KeyVaultAccessor对象合并到我的应用程序中以进行测试。该调用作为对我的一个web api控制器方法的查询的一部分执行:

var secret = KeyVaultAccessor.GetSecret("https://superSecretUri").Result;

不幸的是,调用永远不会返回,查询会无限期挂起......

可能是什么原因,因为坦白说我不知道​​从哪里开始......?

5 个答案:

答案 0 :(得分:13)

这是describe in full on my blog常见的死锁问题。简而言之,async方法在await完成后尝试返回到ASP.NET请求上下文,但该请求一次只允许一个线程,并且该上下文中已有一个线程(在Result的电话中被阻止的那个)。因此,任务正在等待上下文自由,并且线程阻止上下文直到任务完成:死锁。

正确的解决方案是使用await代替Result

var secret = await KeyVaultAccessor.GetSecret("https://superSecretUri");

答案 1 :(得分:8)

我使用以下代码覆盖同步上下文:

var secret = Task.Run(async () => await KeyVaultAccessor.GetSecretAsync("https://superSecretUri")).Result;

如果您使用的是非异步方法

,这仍然允许您使用.Result

答案 2 :(得分:4)

  

不幸的是,调用永远不会返回,查询无限期地挂起......

你有一个经典的僵局。这就是you shouldn't block on async code的原因。在幕后,编译器生成一个状态机并捕获一个名为SynchronizationContext的东西。当您同步阻止调用线程时,尝试将延续回发到同一个上下文会导致死锁。

不要使用.Result同步阻止,而是让控制器异步并等待Task返回的GetSecret

public async IHttpActionResult FooAsync()
{
    var secret = await KeyVaultAccessor.GetSecretAsync("https://superSecretUri");
    return Ok();
}

旁注 - 异步方法应遵循命名约定,并以Async为后缀。

答案 3 :(得分:-1)

使用其余的api ...

public class AzureKeyVaultClient
{


    public string GetSecret(string name, string vault)
    {
        var client = new RestClient($"https://{vault}.vault.azure.net/");
        client.Authenticator = new AzureAuthenticator($"https://vault.azure.net");
        var request = new RestRequest($"secrets/{name}?api-version=2016-10-01");

        request.Method = Method.GET;


        var result = client.Execute(request);

        if (result.StatusCode != HttpStatusCode.OK)
        {
            Trace.TraceInformation($"Unable to retrieve {name} from {vault} with response {result.Content}");
            var exception =  GetKeyVaultErrorFromResponse(result.Content);
            throw exception;

        }
        else
        {
            return GetValueFromResponse(result.Content);
        }




    }

    public string GetValueFromResponse(string content)
    {

            var result = content.FromJson<keyvaultresponse>();
            return result.value;

    }


    public Exception GetKeyVaultErrorFromResponse(string content)
    {
        try
        {

            var result = content.FromJson<keyvautlerrorresponse>();
            var exception = new Exception($"{result.error.code} {result.error.message}");
            if(result.error.innererror!=null)
            {
                var innerException = new Exception($"{result.error.innererror.code} {result.error.innererror.message}");
            }

            return exception;
        }
        catch(Exception e)
        {
            return e;
        }
    }

    class keyvaultresponse
    {
        public string value { get; set; }
        public string contentType { get; set; }

    }

    class keyvautlerrorresponse
    {
        public keyvaulterror error {get;set;}
    }

    class keyvaulterror
    {
        public string code { get; set; }

        public string message { get; set; }

        public keyvaulterror innererror { get; set; }
    }

    class AzureAuthenticator : IAuthenticator
    {

        private string _authority;
        private string _clientId;
        private string _clientSecret;
        private string _resource;



        public AzureAuthenticator(string resource)
        {
            _authority = WebConfigurationManager.AppSettings["azure:Authority"];
            _clientId = WebConfigurationManager.AppSettings["azure:ClientId"];
            _clientSecret = WebConfigurationManager.AppSettings["azure:ClientSecret"];
            _resource = resource;

        }

        public AzureAuthenticator(string resource, string tennant, string clientid, string secret)
        {
            //https://login.microsoftonline.com/<tennant>/oauth2/oken
            _authority = authority;
            //azure client id (web app or native app
            _clientId = clientid;
            //azure client secret
            _clientSecret = secret;
            //vault.azure.net
            _resource = resource;

        }


        public void Authenticate(IRestClient client, IRestRequest request)
        {

            var token = GetS2SAccessTokenForProdMSA().AccessToken;
            //Trace.WriteLine(String.Format("obtained bearer token {0} from ADAL and adding to rest request",token));
            request.AddHeader("Authorization", String.Format("Bearer {0}", token));


        }

        public AuthenticationResult GetS2SAccessTokenForProdMSA()
        {
            return GetS2SAccessToken(_authority, _resource, _clientId, _clientSecret);
        }

        private AuthenticationResult GetS2SAccessToken(string authority, string resource, string clientId, string clientSecret)
        {
            var clientCredential = new ClientCredential(clientId, clientSecret);
            AuthenticationContext context = new AuthenticationContext(authority, false);
            AuthenticationResult authenticationResult = context.AcquireToken(
                resource,
                clientCredential);
            return authenticationResult;
        }

    }
}

答案 4 :(得分:-2)

此泛型方法可用于覆盖死锁问题:

public static T SafeAwaitResult<T>(Func<Task<T>> f)
{
    return Task.Run(async () => await f()).Result;
}

使用:

SafeAwaitResult(() => foo(param1, param2));