在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;
不幸的是,调用永远不会返回,查询会无限期挂起......
可能是什么原因,因为坦白说我不知道从哪里开始......?
答案 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));