当使用Async / Await时,如何在请求属性发生更改时更新所有请求?

时间:2014-08-11 19:09:31

标签: c# .net asynchronous async-await task

我正在使用async / await使用他们的API每秒向服务发出多个请求。我遇到的问题是当我需要刷新令牌时(它每小时都会消失)。令牌过期后,我从服务中收到401未经授权的错误。那是我刷新令牌,然后再次重试失败的请求。令牌刷新得很好,但我发现即使刷新令牌后,仍然会发送许多后续请求和旧令牌。以下是此功能中使用的方法。想知道是否有任何突出的因素是这种意外行为的罪魁祸首。

public void Process(id)
{
    var tasks = items.Select(async item =>
    {
        var response = await SendRequestAsync(() => CreateRequest(item.Url));
        //do something with response
        await Process(item.subId); //recursive call to process sub items.
    }).ToList();

    if (tasks.Any())
        await Task.WhenAll(tasks);      

}

public HttpRequestMessage CreateRequest(string url)
{
    var request = new HttpRequestMessage(HttpMethod.Get, url);
    request.Headers.Add("Authorization", "Bearer " + AppSettings.AccessToken); 
    return request;
}

public async Task<HttpResponseMessage> SendRequestAsync(Func<HttpRequestMessage> funcReq)
{   
    var response = await ExecuteRequestAsync(funcReq());

    while (response.StatusCode == HttpStatusCode.Unauthorized)
    {
        await RefreshTokenAsync();
        return await ExecuteRequestAsync(funcReq());  //assuming func ensures that CreateRequest is called each time, so I'll always have a new request with the updated token.
    }  

    return response;            
}

private async Task<HttpResponseMessage> ExecuteRequestAsync(HttpRequestMessage request)
{
    var client = new HttpClient();
    var response = await client.SendAsync(request);
    return response;    
}

public async Task RefreshTokenAsync()
{
    await semaphoreSlim.WaitAsync();
    try
    {      
        if ((DateTime.Now - refreshTime).TotalMinutes < 60) //tokens last for an hour, so after the refresh is made by the first request that failed, subsequent requests should have the latest token.
            return; 

        Token newToken = GetNewToken();
        AppSettings.AccessToken = newToken.AccessToken //AppSettings is a singleton wrapper class for app.cofig app settings
        refreshTime = DateTime.Now
    }
    finally
    {
        semaphoreSlim.Release();
    }
}

2 个答案:

答案 0 :(得分:1)

这不是答案。只是我没有在哪里发布其中一条评论中讨论的代码。

Prabhu,我认为这样的事情应该有效,以便在获得401之前更新令牌。这只有在您可以对令牌过期的频率做出假设时才有效。

public HttpRequestMessage CreateRequest(string url)
{
    var request = new HttpRequestMessage(HttpMethod.Get, url);
    request.Headers.Add("Authorization", "Bearer " + GetUpToDateAccessToken()); 
    return request;
}

private Token GetUpToDateAccessToken()
{
    _readWriteLockSlim.EnterReadLock();
    try
    {      
        return _latestToken;
    }
    finally
    {
        _readWriteLockSlim.ExitReadLock();
    }  
}

每隔60分钟就可以使用计时器更新令牌。使用读写锁完成同步。这将是Timer的tick处理程序(您可以使用System.Timers.Timer)。

private void UpdateToken()
{  
  _readWriteLockSlim.EnterWriteLock();
  try 
  {
     if ((DateTime.Now - refreshTime).TotalMinutes >= 60) 
     {
         Token newToken = GetNewToken();
         _latestToken = newToken.AccessToken;
          refreshTime = DateTime.Now;
     }
  }
  finally
  {
    _readWriteLockSlim.ExitWriteLock();
  }
}

正如您所提到的,如果60分钟的到期时间无法保证,那么这将无法按预期工作。也许您可以每5分钟左右重新生成一次令牌,以确保您不会使用无效令牌发出请求。

最后,要处理401s因为它们仍然可以发生,你可以将SendRequestAsync中的while循环修改为:

if (response.StatusCode == HttpStatusCode.Unauthorized)
{
   UpdateToken();
   return await ExecuteRequestAsync(funcReq());
}

答案 1 :(得分:1)

我将建议以下工作流程(添加到Marcel N目前建议的)(伪代码):

// Manage the expiration token yourself in the Application or Db
var token = GetTokenFromDbOrApplicationWithExpirationDateTime()
// Expiration on your application can be a little bit less than real, so instead of 60 can be 50 minutes.
if (token.isExpired)
    token = RequestNewToken()
    Db.SaveChanges(token);
}

CallMethod1Async(token)
CallMethod2Async(token)
CallMethod3Async(token)

您还可能想要检查CallMethodAsync是否返回带有无效令牌的响应,如Marcel N所提供的那样。