【问题标题】:KeyVault GetSecretAsync never returnsKeyVault GetSecretAsync 永远不会返回
【发布时间】:2015-10-14 20:03:29
【问题描述】:

在 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;

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

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

【问题讨论】:

    标签: c# .net azure async-await


    【解决方案1】:

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

    正确的解决方案是使用await 而不是Result

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

    【讨论】:

    • 那么,如果我有一个不使用异步方法的现有应用程序,我需要进行一些重构才能使用这个库吗?
    • @GabrielG.Roy:是的,这是最好的方法。
    • 是否有一种快速而肮脏的方法可以让我同步进行这些调用?
    • @GabrielG.Roy:不。有a variety of hacks,但它们都不能完美地工作或在所有情况下都工作。
    • 我使用了您博客中描述的另一种方式,即使用ConfigureAwait(false)。谢谢!
    【解决方案2】:

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

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

    如果您使用非异步方法,这仍然可以让您使用 .Result

    【讨论】:

    • 这应该是正确的答案,因为它适用于一系列场景。
    【解决方案3】:

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

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

    不要与.Result 同步阻塞,而是让您的控制器异步并等待从GetSecret 返回的Task

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

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

    【讨论】:

      【解决方案4】:

      使用其余的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;
              }
      
          }
      }
      

      【讨论】:

        【解决方案5】:

        这个通用方法可以用来覆盖死锁问题:

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

        用途:

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

        【讨论】:

          猜你喜欢
          • 2019-11-15
          • 2011-07-25
          • 1970-01-01
          • 2012-10-02
          • 2013-02-18
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多