当有多个调用调用该方法时,一种锁定等待调用的方法? [复制]

Posted

技术标签:

【中文标题】当有多个调用调用该方法时,一种锁定等待调用的方法? [复制]【英文标题】:A way to Lock await calls one at a time when there are multiple calls calling the method? [duplicate] 【发布时间】:2019-11-19 17:53:15 【问题描述】:

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

显然我通过getAccessToken同时有3个或更多调用getCacheToken,并且它同步处理(不知道为什么),当它等待_client.SendAsync(requestData);它将刷新刷新令牌,旧的刷新令牌将不再有效。

所以第一次调用通过,第二次和第三次失败,因为他们都使用旧令牌在发送请求中传递它。

有没有办法在缓存过期时锁定进程? 因为 Lock 不能用于 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

【问题讨论】:

你会在这里找到原因和可能的解决方案***.com/questions/7612602/… 【参考方案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();

【讨论】:

信号量不应该是静态的吗? @xxbbcc:在这种情况下,是的。 感谢您的回复,只是想知道以这种方式这样做是一种好习惯吗? 如果你真的必须阻止其他调用,那么可以。 抱歉,它仍然无法正常工作,我已设置为 new SemaphoreSlim(1,1);一次一个,并尝试使用 1,3,但不起作用。2 个呼叫在日志中同时通过,一个在 2019 年 12:00:09 另一个在 2019 年 12:00:08

以上是关于当有多个调用调用该方法时,一种锁定等待调用的方法? [复制]的主要内容,如果未能解决你的问题,请参考以下文章

如何简单粗暴的搞定dubbo调用模块

调用某些查询时,MySQL“超过锁定等待超时”

线程的优先级

java面试题之sleep()和wait()方法的区别

等待多个异步调用完成?

lock与synchronized比较