正在使用线程安全更新 属性 个单例
Updating property of a singleton with Thread Safety
我们的设置是:Asp.NET + MVC5 使用 AutoFac 进行 DI。
我们有一个 class(这是一个单例),用于管理各种服务的访问令牌。时不时地,这些令牌太接近到期(不到 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 确保只有一个线程通过,其余线程获得旧令牌。我想知道的是 - 我们是否会遇到一种奇怪的情况,即当令牌被覆盖时,另一个线程尝试读取而不是旧令牌,得到部分搞砸的结果?此实现有任何问题吗?
谢谢!
对我来说,此实现的最大问题是您可能会在单个有效期内刷新令牌两次或多次。如果一个线程在检查过期条件之后但在 CompareExchange()
之前被挂起,那么另一个线程可以在第一个线程恢复之前完成刷新操作,包括重置 m_inter
。这在理论上可以发生在任意多个线程上。
您的代码的其余部分不够具体,无法评论。没有 Token
类型的声明,因此不清楚它是 struct
还是 class
。并且您的 GetCredentials()
方法被声明为 returning 一个 Credentials
值,而是 returns 一个 Token
值,因此该代码显然甚至不是真正的代码.
如果 Token
类型是 class
,那么其余的实现可能没问题。引用类型变量可以原子分配,即使在 x64 平台上也是如此,因此检索 Token
属性 值的代码将看到旧标记或新标记,而不是某些损坏的中间状态。 (当然,我假设 Token
对象本身是线程安全的,最好是不可变的。)
就我个人而言,我不会为 CompareExchange()
烦恼。只需使用成熟的 C# lock
语句即可完成。将整个操作包含在同步块中:检查过期时间,必要时更换令牌,以及 return 令牌值,全部来自 lock
.
根据您展示的代码,我认为将整个内容封装在 属性 本身并使其成为 public
更有意义。但是无论如何,只要通过这段同步代码只能获取令牌值的代码,证明代码正确的最简单和最可靠的方法是使用 lock
。万一你看到性能问题,那么你可以考虑更难正确的替代实现。
我们的设置是:Asp.NET + MVC5 使用 AutoFac 进行 DI。
我们有一个 class(这是一个单例),用于管理各种服务的访问令牌。时不时地,这些令牌太接近到期(不到 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 确保只有一个线程通过,其余线程获得旧令牌。我想知道的是 - 我们是否会遇到一种奇怪的情况,即当令牌被覆盖时,另一个线程尝试读取而不是旧令牌,得到部分搞砸的结果?此实现有任何问题吗?
谢谢!
对我来说,此实现的最大问题是您可能会在单个有效期内刷新令牌两次或多次。如果一个线程在检查过期条件之后但在 CompareExchange()
之前被挂起,那么另一个线程可以在第一个线程恢复之前完成刷新操作,包括重置 m_inter
。这在理论上可以发生在任意多个线程上。
您的代码的其余部分不够具体,无法评论。没有 Token
类型的声明,因此不清楚它是 struct
还是 class
。并且您的 GetCredentials()
方法被声明为 returning 一个 Credentials
值,而是 returns 一个 Token
值,因此该代码显然甚至不是真正的代码.
如果 Token
类型是 class
,那么其余的实现可能没问题。引用类型变量可以原子分配,即使在 x64 平台上也是如此,因此检索 Token
属性 值的代码将看到旧标记或新标记,而不是某些损坏的中间状态。 (当然,我假设 Token
对象本身是线程安全的,最好是不可变的。)
就我个人而言,我不会为 CompareExchange()
烦恼。只需使用成熟的 C# lock
语句即可完成。将整个操作包含在同步块中:检查过期时间,必要时更换令牌,以及 return 令牌值,全部来自 lock
.
根据您展示的代码,我认为将整个内容封装在 属性 本身并使其成为 public
更有意义。但是无论如何,只要通过这段同步代码只能获取令牌值的代码,证明代码正确的最简单和最可靠的方法是使用 lock
。万一你看到性能问题,那么你可以考虑更难正确的替代实现。