Warning: file_get_contents(/data/phpspider/zhask/data//catemap/0/asp.net-mvc/14.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# 使用线程安全性更新singleton的属性_C#_Asp.net Mvc_Multithreading_Autofac_Interlocked - Fatal编程技术网

C# 使用线程安全性更新singleton的属性

C# 使用线程安全性更新singleton的属性,c#,asp.net-mvc,multithreading,autofac,interlocked,C#,Asp.net Mvc,Multithreading,Autofac,Interlocked,我们的设置是:Asp.NET+MVC5使用AutoFac for DI 我们有一个类(它是一个单例),用于管理各种服务的访问令牌。有时,这些代币太接近到期(少于10分钟),我们请求新的代币,刷新它们。我当前的实现如下所示: // member int used for interlocking int m_inter = 0; private string Token { get; set; } private DateTimeOffset TokenExpiry { get; set; }

我们的设置是:Asp.NET+MVC5使用AutoFac for 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;
}
//用于联锁的成员int
int m_inter=0;
私有字符串标记{get;set;}
专用DateTimeOffset令牌到期{get;set;}
公共单例类构造函数()
{
//确保令牌具有某些值。
刷新令牌();
}
公共字符串GetCredentials()
{
if((令牌到期-DateTimeOffset.UTCNow).TotalMinutes<10)
{
if(联锁比较交换(参考m_inter,1,0)==0)
{
刷新令牌();
m_inter=0;
}
}
返回令牌;
}
私有无效刷新令牌()
{
//打电话来
Token=X.Result().Token;
TokenExpiry=X.Result().Expiry;
}
如您所见,Interlocated确保只有一个线程通过,其余线程获得旧令牌。我想知道的是-我们是否会在一个奇怪的情况下结束,当标记被覆盖时,另一个线程尝试读取,而不是旧的标记,得到一个部分出错的结果?这个实现有什么问题吗


谢谢

对我来说,这个实现的最大问题是,您可能会在一个到期期间刷新令牌两次或更多次。如果一个线程在检查过期条件之后但在
compareeexchange()
之前挂起,那么另一个线程可以在第一个线程恢复之前完成刷新操作,包括重置
m_inter
。理论上,这可能发生在任意多个线程上

代码的其余部分不够具体,无法对其进行评论。没有
标记
类型的声明,因此不清楚这是
结构
还是
。您的
GetCredentials()
方法声明为返回
Credentials
值,但返回的是
令牌
值,因此代码显然不是真正的代码

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

就个人而言,我不会为
CompareExchange()
而烦恼。只要使用一个完整的C#
lock
语句,就可以完成它。在同步块中包含整个操作:检查过期时间,必要时更换令牌,并返回令牌值,所有操作都在
锁中进行


根据您展示的代码,我认为将整个内容封装在属性本身中并使其公开更合理。但无论如何,只要检索令牌值的代码只能通过这段同步代码获取,证明代码正确性的最简单、最可靠的方法就是使用
lock
。在不太可能的情况下,你会看到性能问题,然后你可以考虑其他的实现方式,这是一个很好的读物。彼得。为了更好地理解这些内容,我实际上已经更新了代码示例,您是对的,它不是真正的代码。标记的类型为字符串。我想问你两个后续问题。1) 您提到线程被挂起。鉴于这是Asp.Net,它真的会在那个阶段暂停吗?没有io呼叫等。。如果我要添加反模式-双重检查-那么就在RefreshToken之前添加另一个If语句,我真的不明白为什么线程会挂起(理论上说,你是绝对正确的2),“如果这是Asp.Net,它真的会挂起吗?”--Windows定期挂起所有线程以进行多任务处理。即使是拥有大量内核(例如32、64等)的服务器,在操作系统下运行的线程仍然比内核多,因此每个线程都必须定期挂起。这是不可避免的。“如果我要添加反模式-双重检查-那么就在RefreshToken之前添加另一个If语句?”--是的,您可以在调用
RefreshToken()
之前再次检查过期时间,这样可以避免多次刷新问题。查看更新的代码示例,现在,我对实施缺乏信心。您似乎将过期时间与令牌分开。首先,在您的代码示例中,我看不到这在哪里更新。其次,假设它在
refreshttoken()
方法中更新,那么现在确实存在一个明显的原子性问题。您不仅不能使用联锁操作以原子方式更新
Token
Minutes
,而且
DateTimeOffset
类型是一个结构,并且不享受原语类型和引用类型引用的原子性保证。就分钟数而言,您使用相同的RefreshToken方法是正确的。更新是否需要是原子的?它不能一个接一个地更新令牌和分钟吗?只要它们处于联锁状态?我能想到的缺点是分钟数不会反映当前令牌的到期时间,或者当前令牌不会是属于分钟数的令牌-在很短的时间内-但只要两者都更新了,它就需要原子吗?为了清楚起见,我将更新代码以使用锁定。在这个阶段,我会问,因为我在这里学到了很多:)为什么你不只是使用锁?