一种锁定等待方式的方法,当有多个调用调用该方法时,一次调用一个?

时间:2019-07-10 06:59:25

标签: c# async-await

我有一个api端点供人们调用该服务, 如果缓存过期,我只希望一次运行一个GetCacheToken。

显然我通过getAccessToken同时有3个调用getCacheToken,并且它在等待_client.SendAsync(requestData)时同步处理(不确定原因);它将刷新刷新令牌,而旧的刷新令牌将不再有效。

因此,第一个呼叫通过,第二个和第三个失败,因为它们都使用旧令牌在发送请求中传递了它。

有没有一种方法可以在缓存过期时锁定进程? 由于Lock无法在等待呼叫中使用。 谢谢

认为它将由async await处理,但是没有运气

    [System.Web.Http.HttpGet]
    [System.Web.Http.Route("getAccessToken")]
    public async Task<string> getAccessToken()
    {
        return await cacheToken.GetCacheToken();
    }       


    private static readonly SemaphoreSlim _mutex = new SemaphoreSlim(1,1); 

     public async Task<string> GetCacheToken()
    {
        ObjectCache cache = MemoryCache.Default;
        var cacheToken = cache.Get("refreshToken", null);
        string refreshToken = cacheToken == null ? GetToken() : cacheToken.ToString();

        if (!cache.Contains("apiToken"))
        {
            //it prevent multiple threads call to update refreshtoken at the same time
            await _mutex.WaitAsync();
            try
            {
                //Log.Information("Access token " + cache.Get("apiToken", null));
                var isNull = cache.Get("refreshToken", null) == null ? "Yes " : "No ";
                var logtime = DateTime.Now.ToString("O");
                Log.Information("refresh token is null : " + isNull + DateTime.Now.ToString("O") + " " + logtime);
                Log.Information(
                    "refresh token : " + refreshToken + " " + DateTime.Now.ToString("O") + " " + logtime);

                var httpContent = new StringContent("", Encoding.UTF8, "application/x-www-form-urlencoded");
                var dict = new Dictionary<string, string>();
                dict.Add("grant_type", "refresh_token");
                dict.Add("refresh_token", refreshToken);
                var requestData = new HttpRequestMessage
                {
                    Method = HttpMethod.Post,
                    RequestUri = new Uri("https://oauth2.sky.blackbaud.com/token"),
                    Content = new FormUrlEncodedContent(dict)
                };

                requestData.Headers.Authorization = new AuthenticationHeaderValue("Basic", Settings.BasicAuth);
                var results = await _client.SendAsync(requestData);
                Log.Information("run time " + DateTime.Now);
                var resultResponse = results.Content.ReadAsStringAsync().Result;

                try
                {
                    results.EnsureSuccessStatusCode();
                    var result = _js.Deserialize<TokenModel>(resultResponse);
                    //token expires in one hour from blackbaud
                    var expiration = DateTimeOffset.Now.AddMinutes(55);
                    cache.Set("apiToken", result.access_token, expiration);
                    cache.Set("refreshToken", result.refresh_token, expiration);
                    await UpdateToken(result.access_token, result.refresh_token);
                    Log.Information("refresh token after update : " + cache.Get("refreshToken", null) +
                                    DateTime.Now.ToString("O"));
                }
                catch (Exception e)
                {
                    var exceptionMessage = $"ResultMessage : {resultResponse} Exception: {e}.";
                    Log.Exception(e, exceptionMessage);
                    throw;
                }
            }
            finally
            {
                _mutex.Release();
            }
        }

        return cache.Get("apiToken", null).ToString();
    }

这是日志

12 Jul 2019 12:00:10.847
 ResultMessage : {"error":"invalid_grant"} Exception: System.Net.Http.HttpRequestException: Response status code does not indicate success: 400 (Bad Request). at System.Net.Http.HttpResponseMessage.EnsureSuccessStatusCode() at RaisersEdge.Infrastructure.Cache.<GetCacheToken>d__3.MoveNext().Exception: HttpRequestException {"Message":"Response status code does not indicate success: 400 (Bad Request).","Data":[],…Stack: at System.Net.Http.HttpResponseMessage.EnsureSuccessStatusCode() at RaisersEdge.Infrastructure.Cache.<GetCacheToken>d__3.MoveNext()Category: Exception
12 Jul 2019 12:00:10.841
 run time 12/07/2019 12:00:10 PM
12 Jul 2019 12:00:09.981
 refresh token : 03124379add64a90850961e6a2021d6e 2019-07-12T12:00:09.9813412+12:00 2019-07-12T12:00:09.9813412+12:00
12 Jul 2019 12:00:09.981
 refresh token is null : No 2019-07-12T12:00:09.9813412+12:00 2019-07-12T12:00:09.9813412+12:00
12 Jul 2019 12:00:09.980
 refresh token after update : f0b569cfa2254bbfbd78e9d84ddd66ae2019-07-12T12:00:09.9803523+12:00
12 Jul 2019 12:00:09.951
 run time 12/07/2019 12:00:09 PM
12 Jul 2019 12:00:08.970
 refresh token : 03124379add64a90850961e6a2021d6e 2019-07-12T12:00:08.9212759+12:00 2019-07-12T12:00:08.9192713+12:00
12 Jul 2019 12:00:08.942
 refresh token is null : Yes 2019-07-12T12:00:08.9192713+12:00 2019-07-12T12:00:08.9192713+12:00

1 个答案:

答案 0 :(得分:1)

lock的异步等效项是SemaphoreSlim

如果您想要的代码如下所示:

private readonly object _mutex = new object();

...
lock (_mutex)
{
  ...
  var results = await _client.SendAsync(requestData);
  ...
}

然后您可以使用SemaphoreSlim这样获得所需的行为:

private readonly SemaphoreSlim _mutex = new SemaphoreSlim();

...
await _mutex.WaitAsync();
try
{
  ...
  var results = await _client.SendAsync(requestData);
  ...
}
finally
{
  _mutex.Release();
}