从非异步方法运行异步代码

问题描述:

我想从Azure AD获取访问令牌,并且异步调用永远不会结束。我不确定自己做错了什么,也许有人可以指引我走向正确的方向。这是代码:从非异步方法运行异步代码

private static void GetAccessTokenNonAsync() 
    { 
     Func<System.Threading.Tasks.Task> task = async() => { await GetAccessToken().ConfigureAwait(false); }; 
     task().Wait(); 
    } 
    private static async Task<AuthenticationResult> GetAccessToken() 
    { 
     string aadInstance = ConfigurationManager.AppSettings["ida:AADInstance"]; 
     string clientId = ConfigurationManager.AppSettings["ida:ClientId"]; 
     string clientSecret = ConfigurationManager.AppSettings["ida:ClientSecret"]; 
     string source = ConfigurationManager.AppSettings["ExchangeOnlineId"]; 

     var authContext = new AuthenticationContext(aadInstance, false); 
     var credentials = new ClientCredential(clientId, clientSecret); 
     var appRedirectUrl = HttpContext.Current.GetOwinContext().Request.Scheme + "://" + HttpContext.Current.GetOwinContext().Request.Host + HttpContext.Current.GetOwinContext().Request.PathBase + "/"; 
     var res = 
      await 
       authContext.AcquireTokenByAuthorizationCodeAsync(
        ((ClaimsIdentity)HttpContext.Current.User.Identity).FindFirst("AuthenticationCode").Value, 
        new Uri(appRedirectUrl), credentials, source).ConfigureAwait(false); 
     Current.AccessToken = res.AccessToken; 
     return res; 

    } 
    private static string AccessToken 
    { 
     get 
     { 
      return HttpContext.Current.Session["AccessToken"].ToString(); 
     } 
     set { HttpContext.Current.Session["AccessToken"] = value; } 
    } 

而在我的主要方法我称之为GetAccessTokenNonAsync()

+2

的可能的复制[我将如何运行的异步任务方法同步?] (http://stackoverflow.com/questions/5095183/how-would-i-run-an-async-taskt-method-synchronously) – Igor

+1

@Igor:在这个问题上接受的答案有严重的重入问题。请不要推荐它。 –

您使用async关键字不当。像这样的东西应该工作:

public static void GetAccessTokenNonAsync() 
{ 
    // Call the async method and get the resulting task. 
    Task<AuthenticationResult> accessTokenTask = GetAccessToken(); 
    // Wait for the task to finish. 
    accessTokenTask.Wait(); 
} 

private async static Task<AuthenticationResult> GetAccessToken() 
{ 
    return await Task.Factory.StartNew<AuthenticationResult> 
    (
     () => 
     { 
      // Insert code here. 
      return new AuthenticationResult(); 
     } 
    ); 
} 

由于您的异步代码返回的东西(使用任务签名),尝试,如果你的目标最新的.NET Framework这样做:

private static void GetAccessTokenNonAsync() 
{ 
    var result = GetAccessToken().ConfigureAwait(false).Result; 
} 

您正在运行到deadlock scenario because you're blocking on async code 。我在我的博客上描述了细节。

理想情况下,您不应该在任何新代码中使用HttpContext.Current。它被认为是很差的练习,并且已经在ASP.NET Core中完全删除(并且不会回来)。

它看起来像你想要做的是将令牌存储在会话状态,并(如果有必要,(异步))创建它。适当的方式做,这是使访问异步:

public static async Task<string> GetAccessTokenAsync() 
{ 
    var result = HttpContext.Current.Session["AccessToken"] as string; 
    if (result != null) 
    return result; 

    ... 

    var res = await authContext.AcquireTokenByAuthorizationCodeAsync(...); 
    result = res.AccessToken.ToString(); 
    HttpContext.Current.Session["AccessToken"] = result; 
    return result; 
} 
+0

我不认为这解决了我的问题。我需要在用户需要使用它时重新生成访问令牌,所以我需要它同步。我还在所有的异步调用上使用ConfigureAwait(false),但它仍然锁定用户界面... –

+0

@OanaMarina:这不是问题;你只是异步而非同步地重新生成它。 'ConfigureAwait(false)'只在整个调用堆栈中使用时才有效; 'AcquireTokenByAuthorizationCodeAsync'可能没有使用它。 –

+0

如果我异步重新生成它,我不能确定它会在我使用它的下一行代码中可用。我需要等待它生成,然后使用它... –

终于工作的代码是这样的:

private static void GetAccessTokenNonAsync() 
    { 
     var userId = ((ClaimsIdentity)HttpContext.Current.User.Identity).FindFirst("UserId").Value; 

     var task = System.Threading.Tasks.Task.Run(async() => 
     { 
      return await GetAccessToken(userId); 
     }); 
     task.Wait(); 
     Current.AccessToken = task.Result.AccessToken; 
     Current.AccessTokenExpiresOn = task.Result.ExpiresOn.ToString(); 
    } 

    private static async Task<AuthenticationResult> GetAccessToken(string userId) 
    { 

     return await System.Threading.Tasks.Task.Factory.StartNew<AuthenticationResult> 
     (
      () => 
      { 
       string aadInstance = ConfigurationManager.AppSettings["ida:AADInstance"]; 
       string clientId = ConfigurationManager.AppSettings["ida:ClientId"]; 
       string clientSecret = ConfigurationManager.AppSettings["ida:ClientSecret"]; 
       string source = ConfigurationManager.AppSettings["ExchangeOnlineId"]; 

       var authContext = new AuthenticationContext(aadInstance, false); 
       var credentials = new ClientCredential(clientId, clientSecret); 
       var res = 
        authContext.AcquireTokenSilentAsync(source, credentials, 
         new UserIdentifier(userId, UserIdentifierType.UniqueId)).Result; 
       return res; 
      } 
     ); 
    }