正在使用线程安全更新 属性 个单例

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。万一你看到性能问题,那么你可以考虑更难正确的替代实现。