问题描述
我正在使用一个异步 Web API,它需要在每个 API 调用中将 AccessToken
(一个不可变结构)作为参数传递.这个AccessToken
本身是通过调用同一个Web API的异步Authenticate
方法获得的.
I am consuming an asynchronous Web API that requires an AccessToken
(an immutable struct) to be passed as an argument on every API call. This AccessToken
is itself obtained by calling an asynchronous Authenticate
method of the same Web API.
class WebApi
{
public Task<AccessToken> Authenticate(string username, string password);
public Task PurchaseItem(AccessToken token, int itemId, int quantity);
// More methods having an AccessToken parameter
}
出于性能原因,我不想在调用 API 的所有其他方法之前调用 Authenticate
方法.我想调用一次,然后对多个 API 调用重复使用相同的 AccessToken
.我的问题是 AccessToken
每 15 分钟过期一次,调用任何具有过期 AccessToken
的 API 方法都会导致 AccessTokenExpiredException
.我可以捕获此异常,然后在获取新的 AccessToken
后重试错误调用,但出于性能原因,我更愿意在 AccessToken
过期之前抢先刷新它.我的应用程序是多线程的,因此多个线程可能会尝试同时使用/刷新相同的 AccessToken
值,事情很快就会变得非常混乱.
I don't want to call the Authenticate
method before calling every other method of the API, for performance reasons. I want to call it once, and then reuse the same AccessToken
for multiple API calls. My problem is that the AccessToken
is expiring every 15 minutes, and calling any API method with an expired AccessToken
results to an AccessTokenExpiredException
. I could catch this exception and then retry the faulted call, after acquiring a new AccessToken
, but I would prefer to preemptively refresh the AccessToken
before it has expired, again for performance reasons. My application is multithreaded, so multiple threads might try to use/refresh the same AccessToken
value concurrently, and things quickly start to become very messy.
要求是:
Authenticate
方法的调用频率不应超过每 15 分钟一次,即使多个线程尝试同时调用 Web API 的方法.- 如果
Authenticate
调用失败,下次需要AccessToken
时应重复调用.此要求优先于先前的要求.将出现故障的Task
缓存和重用 15 分钟是不可接受的. Authenticate
方法仅在实际需要AccessToken
时才应调用.使用Timer
每 15 分钟调用一次是不可接受的.AccessToken
只能在创建后的 15 分钟内使用.- 过期机制不应依赖于系统时钟.系统时钟调整不应影响(延长或缩短)有效期.
- The
Authenticate
method should not be called more frequently than once every 15 minutes, even if multiple threads attempt to invoke methods of the Web API concurrently. - In case an
Authenticate
call fails, it should be repeated the next time anAccessToken
is needed. This requirement takes precedence over the previous requirement. Caching and reusing a faultedTask<AccessToken>
for 15 minutes is not acceptable. - The
Authenticate
method should be called only when anAccessToken
is actually needed. Invoking it every 15 minutes with aTimer
is not acceptable. - An
AccessToken
should only be used during the next 15 minutes after its creation. - The expiration mechanism should not be dependent on the system clock. A system-wise clock adjustment should not affect (elongate or shorten) the expiration period.
我的问题是:怎么可能我以一种满足要求的方式抽象了获取、监控到期和刷新 AccessToken
的功能,同时保持应用程序的其余部分不受所有这些复杂性的影响?我在想类似于我在这个问题中找到的 AsyncLazy
类型的东西:强制调用一次异步方法,但增强具有过期功能.以下是使用此类型的假设示例(使用 TimeSpan
参数增强):
My question is: how couldI abstract the functionality of acquiring, monitoring the expiration, and refreshing the AccessToken
, in a way that satisfies the requirements, while keeping the rest of my application clean from all this complexity? I am thinking of something similar to the AsyncLazy<T>
type that I found in this question:Enforce an async method to be called once, but enhanced with expiration functionality. Here is a hypothetical example of using this type (enhanced with a TimeSpan
parameter):
private readonly WebApi _webApi = new WebApi();
private readonly AsyncLazy<AccessToken> _accessToken = new AsyncLazy<AccessToken>(
() => _webApi.Authenticate("xxx", "yyy"), TimeSpan.FromMinutes(15));
async Task Purchase(int itemId, int quantity)
{
await _webApi.PurchaseItem(await _accessToken, itemId, quantity);
}
顺便说一句,这个问题的灵感来自 一个最近的问题,其中 OP 试图以不同的方式解决类似的问题.上面给出的例子是人为的.我的目的是自我回答这个问题,但欢迎任何贡献作为答案.
Btw this question was inspired by a recent question, where the OP was trying to solve a similar problem in a different way. The example presented above is contrived. My intention is to self-answer this question, but any contribution as an answer is welcome.
我想避免在评论中发布答案.如果需要澄清,请使用评论要求对此问题进行澄清.
I would like to ask to avoid posting answers in the comments. Please use the comments to ask for clarifications about this question, in case clarifications are needed.
推荐答案
A resettable"AsyncLazy
是 等效于单个项目异步缓存.在这种情况下,使用基于时间的到期,相似性更加惊人.
A "resettable" AsyncLazy<T>
is equivalent to a single-item asynchronous cache. In this case, with a time-based expiration, the similarity is even more striking.
我建议使用实际的AsyncCache
;我有 一个我正在研究,目前正在使用一个非常低负载的产品-类似环境,但还没有在实际生产环境中得到很好的测试.
I recommend using an actual AsyncCache<T>
; I have one I'm working on and am currently using in a very low-load prod-like environment, but it hasn't been well tested in a real production environment.
这篇关于强制异步方法按需延迟调用,并在前一个结果过期时再次调用的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!