C# KeyVault GetSecretAsync永远不会返回
在web应用程序中使用KeyVault的示例代码包含以下代码:C# KeyVault GetSecretAsync永远不会返回,c#,.net,azure,async-await,C#,.net,Azure,Async Await,在web应用程序中使用KeyVault的示例代码包含以下代码: public static async Task<string> GetSecret(string secretId) { var secret = await keyVaultClient.GetSecretAsync(secretId); return secret.Value; } 不幸的是,调用永远不会返回,查询将无限挂起 原因可能是什么,因为坦白说我不知道从哪里开始 不幸的是,调用从未返回,查
public static async Task<string> GetSecret(string secretId)
{
var secret = await keyVaultClient.GetSecretAsync(secretId);
return secret.Value;
}
不幸的是,调用永远不会返回,查询将无限挂起
原因可能是什么,因为坦白说我不知道从哪里开始
不幸的是,调用从未返回,查询无限期挂起
你有一个典型的僵局。这就是为什么。在幕后,编译器生成一个状态机并捕获一个称为SynchronizationContext
的东西。当同步阻止调用线程时,尝试将延续发回同一上下文会导致死锁
不要与.Result
同步阻塞,而是使控制器异步并等待从GetSecret
返回的任务:
public async IHttpActionResult FooAsync()
{
var secret = await KeyVaultAccessor.GetSecretAsync("https://superSecretUri");
return Ok();
}
旁注-异步方法应遵循命名约定,并使用Async
进行后期修复这是我所遇到的常见死锁问题。简而言之,async
方法试图在wait
完成后返回ASP.NET请求上下文,但该请求一次只允许一个线程,并且该上下文中已经有一个线程(调用Result
时被阻止的线程)。因此,任务正在等待上下文释放,线程正在阻塞上下文,直到任务完成:死锁
正确的解决方案是使用等待
而不是结果
:
var secret = await KeyVaultAccessor.GetSecret("https://superSecretUri");
我已使用以下代码覆盖同步上下文:
var secret = Task.Run(async () => await KeyVaultAccessor.GetSecretAsync("https://superSecretUri")).Result;
这仍然允许您使用.Result
如果您使用的是非异步方法请使用RESTAPI
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;
}
}
}
公共类AzureKeyVault客户端
{
公共字符串GetSecret(字符串名称、字符串库)
{
var client=new RestClient($“https://{vault}.vault.azure.net/”);
client.Authenticator=新AzureAuthenticator($”https://vault.azure.net");
var请求=新的重新请求($“secrets/{name}?api version=2016-10-01”);
request.Method=Method.GET;
var result=client.Execute(请求);
if(result.StatusCode!=HttpStatusCode.OK)
{
Trace.TraceInformation($“无法使用响应{result.Content}从{vault}检索{name}”);
var exception=GetKeyVaultErrorFromResponse(result.Content);
抛出异常;
}
其他的
{
返回GetValueFromResponse(result.Content);
}
}
公共字符串GetValueFromResponse(字符串内容)
{
var result=content.FromJson();
返回result.value;
}
公共异常GetKeyVaultErrorFromResponse(字符串内容)
{
尝试
{
var result=content.FromJson();
var exception=新异常($“{result.error.code}{result.error.message}”);
if(result.error.innererror!=null)
{
var innerException=新异常($“{result.error.innererror.code}{result.error.innererror.message}”);
}
返回异常;
}
捕获(例外e)
{
返回e;
}
}
类键响应
{
公共字符串值{get;set;}
公共字符串contentType{get;set;}
}
类keyvautlerrorresponse
{
public KeyVault错误{get;set;}
}
类keyvaulterror
{
公共字符串代码{get;set;}
公共字符串消息{get;set;}
public keyvaulterror innererror{get;set;}
}
AzureAuthenticator类:IAAuthenticator
{
私人字符串管理机构;
私有字符串_clientId;
私有字符串_clientSecret;
私有字符串资源;
公共AzureAuthenticator(字符串资源)
{
_authority=WebConfiguration Manager.AppSettings[“azure:authority”];
_clientId=WebConfiguration Manager.AppSettings[“azure:clientId”];
_clientSecret=WebConfiguration Manager.AppSettings[“azure:clientSecret”];
_资源=资源;
}
公共AzureAuthenticator(字符串资源、字符串tennant、字符串clientid、字符串机密)
{
//https://login.microsoftonline.com//oauth2/oken
_权威=权威;
//azure客户端id(web应用程序或本机应用程序)
_clientId=clientId;
//azure客户端机密
_clientSecret=秘密;
//vault.azure.net
_资源=资源;
}
公共无效身份验证(IRestClient客户端、IRestRequest请求)
{
var token=GetS2SAccessTokenForProdMSA().AccessToken;
//WriteLine(String.Format(“从ADAL获得的承载令牌{0}并添加到rest请求中”,token));
AddHeader(“Authorization”,String.Format(“Bearer{0}”,token));
}
公共身份验证结果GetS2SAccessTokenForProdMSA()
{
返回GetS2SAccessToken(_authority,_resource,_clientId,_clientSecret);
}
私有身份验证结果GetS2SAccessToken(字符串权限、字符串资源、字符串clientId、字符串clientSecret)
{
var clientCredential=新的clientCredential(clientId,clientSecret);
AuthenticationContext=新的AuthenticationContext(authority,false);
AuthenticationResult AuthenticationResult=context.AcquireToken(
资源,,
客户凭证);
返回authenticationResult;
}
}
}
此通用方法可用于覆盖死锁问题:
public static T SafeAwaitResult<T>(Func<Task<T>> f)
{
return Task.Run(async () => await f()).Result;
}
因此,如果我有一个不使用异步方法的现有应用程序,我需要进行一些重构才能使用这个库?@GabrielG.Roy:是的,这是最好的方法。有没有一种快速而肮脏的方法可以让我同步地进行这些调用?@GabrielG.Roy:没有。没有
public static T SafeAwaitResult<T>(Func<Task<T>> f)
{
return Task.Run(async () => await f()).Result;
}
SafeAwaitResult(() => foo(param1, param2));