使用Thread Safety更新单例的属性

时间:2017-02-28 17:43:27

标签: c# asp.net-mvc multithreading autofac interlocked

我们的设置是: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确保只有一个线程通过,其余线程获得旧令牌。我想知道的是 - 我们能否最终处于一种奇怪的情况,即当令牌被覆盖时,另一个线程试图读取而不是旧令牌,会得到部分搞砸的结果?这个实现有什么问题吗?

谢谢!

1 个答案:

答案 0 :(得分:3)

对我而言,此实施的最大问题是您可以在一个有效期内刷新令牌两次或更多次。如果线程在检查到期条件之后但在CompareExchange()之前被挂起,那么另一个线程可以在第一个线程恢复之前完成整个刷新操作,包括重置m_inter。从理论上讲,这可能发生在任意多个线程上。

您的代码的其余部分并不具体,无法发表评论。没有Token类型的声明,因此不清楚这是struct还是class。并且您的GetCredentials()方法被声明为返回Credentials值,而是返回Token值,因此代码显然不是真正的代码。

如果Token类型是class,那么其余的实现可能就好了。引用类型变量可以原子方式分配,即使在x64平台上也是如此,因此检索Token属性值的代码将看到旧令牌或新令牌,而不是某些损坏的中间状态。 (当然,我假设Token对象本身是线程安全的,最好是因为它是不可变的。)

就个人而言,我不会打扰CompareExchange()。只需使用完整的C#lock语句并完成它。包含synchronized块中的整个操作:检查到期时间,必要时替换令牌,并返回令牌值,全部来自lock

根据您展示的代码,我认为将整个事物封装在属性本身并制作public会更有意义。但是,无论哪种方式,只要代码检索令牌值只能 通过这部分同步代码获取它,最简单,最可靠的方法来证明代码是正确的是使用{{ 1}}。万一你看到性能问题,那么你可以考虑更难做到的替代实现。