我正在调用使用OAuth进行身份验证的第三方API,我想知道如何使这个线程安全:
var token = _tokenService.GetCurrentToken(); // eg token could be "ABCDEF"
var newToken = oauth.RenewAccessToken(token); // eg newToken could be "123456"
_tokenService.UpdateCurrentToken(newToken); // save newToken to the database
这样做是每次调用RenewAccessToken()
时使用前一个标记。但如果两个用户同时启动它(两个不同的线程将同时运行代码),则会出现问题,我们最终会按此顺序执行该代码:
[Thread 1] var token = _tokenService.GetCurrentToken(); // returns "ABCDEF"
[Thread 2] var token = _tokenService.GetCurrentToken(); // returns "ABCDEF"
[Thread 1] var newToken = oauth.RenewAccessToken("ABCDEF"); // returns "123456"
[Thread 2] var newToken = oauth.RenewAccessToken("ABCDEF");
// throws an invalid token exception
在线程2中,实际上应该调用oauth.RenewAccessToken("123456");
(因为这是最新的令牌值。但是最新的令牌甚至还没有保存到数据库中,所以线程2总是有当前令牌的错误值。
我该怎么做才能解决这个问题?
编辑:有人建议使用这样的锁:
private object tokenLock = new object();
lock(tokenLock)
{
var token = _tokenService.GetCurrentToken();
var newToken = oauth.RenewAccessToken(token);
_tokenService.UpdateCurrentToken(newToken);
}
编辑2:无论如何锁实际上都没有用,这来自我的日志:
[43 22:38:26:9963] Renewing now using token JHCBTW1ZI96FF
[36 22:38:26:9963] Renewing now using token JHCBTW1ZI96FF
[36 22:38:29:1790] OAuthException exception
第一个数字是线程ID,第二个是时间戳。两个线程在完全相同的时间执行到毫秒。我不知道为什么锁直到螺纹43完成后才能停止螺纹36。
编辑3:再次,这次将object tokenLock
更改为类变量而不是局部变量后,锁定无效。
[25 10:53:58:3870] Renewing now using token N95984XVORY
[9 10:53:58:3948] Renewing now using token N95984XVORY
[9 10:54:55:7981] OAuthException exception
答案 0 :(得分:4)
编辑
鉴于这是一个ASP.NET应用程序,简单的路由(使用Monitor
块的lock { }
锁定)不合适。您需要使用命名的Mutex才能解决此问题。
鉴于您的示例代码,这些内容可以起作用:
using(var m = new Mutex("OAuthToken"))
{
m.WaitOne();
try
{
var token = _tokenService.GetCurrentToken();
var newToken = oauth.RenewAccessToken(token);
_tokenService.UpdateCurrentToken(newToken);
}
finally
{
m.ReleaseMutex();
}
}
注意finally
子句;释放互斥锁非常重要。因为它是一个系统范围的对象,它的状态将持续超出您的应用程序。如果您在上面的OAuth代码中遇到异常,则在重新启动系统之前,您将无法重新输入代码。
此外,如果您对使用相同OAuth令牌的会话具有某种持久标识符(由于此过程而不会更改的内容),您可能会将该令牌用作互斥锁名称而不是{ {1}}正如我上面所说的那样。这将使同步特定于给定令牌,因此您不必担心必须等待更新无关令牌的操作。这应该抵消互斥锁成本超过"OAuth"
锁定的增加。
为了帮助那些可能会发现这个问题的人,我在下面留下了原来的答案:
原始答案
你需要一个简单的锁定操作。
创建一个实例(或静态,如果这些函数是静态的)Monitor
类型的变量:
object
在您的代码中,将private object tokenLock = new object();
块中需要原子的步骤括起来:
lock(tokenLock)
这将阻止一个线程启动此进程,而另一个线程正在执行它。