我们的设置是:Asp.NET + MVC5使用AutoFac进行DI。
我们有一个类(它是一个单例),它管理各种服务的访问令牌。时不时地,这些令牌过于接近到期(不到10分钟),我们请求新令牌,刷新它们。我目前的实现如下:
// member int used for interlocking
int m_inter = 0;
private string Token { get; set; }
private DateTimeOffset TokenExpiry { get; set; }
public SingletonClassConstructor()
{
// Make sure the Token has some value.
RefreshToken();
}
public string GetCredentials()
{
if ((TokenExpiry - DateTimeOffset.UTCNow).TotalMinutes < 10)
{
if (Interlocked.CompareExchange(ref m_inter, 1, 0) == 0)
{
RefreshToken();
m_inter = 0;
}
}
return Token;
}
private void RefreshToken()
{
// Call some stuff
Token = X.Result().Token;
TokenExpiry = X.Result().Expiry;
}
正如您所看到的,Interlocked确保只有一个线程通过,其余线程获得旧令牌。我想知道的是 - 我们能否最终处于一种奇怪的情况,即当令牌被覆盖时,另一个线程试图读取而不是旧令牌,会得到部分搞砸的结果?这个实现有什么问题吗?
谢谢!
答案 0 :(得分:3)
对我而言,此实施的最大问题是您可以在一个有效期内刷新令牌两次或更多次。如果线程在检查到期条件之后但在CompareExchange()
之前被挂起,那么另一个线程可以在第一个线程恢复之前完成整个刷新操作,包括重置m_inter
。从理论上讲,这可能发生在任意多个线程上。
您的代码的其余部分并不具体,无法发表评论。没有Token
类型的声明,因此不清楚这是struct
还是class
。并且您的GetCredentials()
方法被声明为返回Credentials
值,而是返回Token
值,因此代码显然不是真正的代码。
如果Token
类型是class
,那么其余的实现可能就好了。引用类型变量可以原子方式分配,即使在x64平台上也是如此,因此检索Token
属性值的代码将看到旧令牌或新令牌,而不是某些损坏的中间状态。 (当然,我假设Token
对象本身是线程安全的,最好是因为它是不可变的。)
就个人而言,我不会打扰CompareExchange()
。只需使用完整的C#lock
语句并完成它。包含synchronized块中的整个操作:检查到期时间,必要时替换令牌,并返回令牌值,全部来自lock
。
根据您展示的代码,我认为将整个事物封装在属性本身并制作public
会更有意义。但是,无论哪种方式,只要代码检索令牌值只能 通过这部分同步代码获取它,最简单,最可靠的方法来证明代码是正确的是使用{{ 1}}。万一你看到性能问题,那么你可以考虑更难做到的替代实现。