2015-10-14 156 views
1

示例代碼與KeyVault工作的web應用程序內中有下面的代碼:KeyVault GetSecretAsync永遠不會返回

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

我已經納入包括在我的應用程序樣本中的KeyVaultAccessor對象,以測試它。該呼叫作爲查詢的一部分執行的我的網頁API控制方法之一:

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

不幸的是,調用不會返回與查詢indefintely掛...

可能是什麼原因,是因爲坦率地說,我不知道從哪裏開始......?

回答

7

這是我常見的死鎖問題,我describe in full on my blog。簡而言之,在await完成後,async方法嘗試返回到ASP.NET請求上下文,但該請求一次只允許一個線程,並且該上下文中已經有一個線程(被阻止在該線程上Result)。所以任務正在等待上下文空閒,並且線程正在阻塞上下文直到任務完成:死鎖。

正確的解決方案是使用await而不是Result

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

所以,如果我有一個不使用異步方法,任何現有的應用程序,我需要做一些重構才能使用這個庫? –

+0

@ GabrielG.Roy:是的,那是最好的方法。 –

+0

有沒有一個快速和骯髒的方法,這將允許我同時進行這些調用? –

3

不幸的是,調用不會返回與查詢無限期掛...

你有一個經典的僵局。這就是爲什麼you shouldn't block on async code。在幕後,編譯器生成一個狀態機並捕獲一些名爲SynchronizationContext的東西。當您同步阻止調用線程時,嘗試將延續發回到同一上下文會導致死鎖。

,而不是與.Result同步阻塞,使你的控制器的異步和等待在TaskGetSecret返回:

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

旁註 - 異步方法應該遵循命名約定,並與Async的後綴。

5

我用下面的代碼覆蓋同步上下文:

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

這仍然讓你使用.Result如果你'在一個非異步的方法

+0

這應該是正確的答案,因爲它適用於一系列場景。 – fernacolo

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

    } 
} 
-2

這個通用的方法可以用於覆蓋僵局問題:

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

用途:

SafeAwaitResult(() => foo(param1, param2)); 
相關問題