如何对第三方API(OAuth)进行线程安全调用
本文关键字:线程 安全 调用 OAuth 第三方 API | 更新日期: 2023-09-27 17:58:10
我正在调用一个使用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
编辑
假设这是一个ASP.NET应用程序,那么简单的路由(使用lock { }
块的Monitor
锁)是不合适的。为了解决这个问题,您需要使用一个命名的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令牌的会话提供了某种持久标识符(这个过程不会更改),那么您可能会使用该令牌作为互斥体名称,而不是像上面所说的"OAuth"
。这将使同步特定于给定的令牌,因此您不必担心操作必须等待不相关的令牌被续订。这应该可以抵消互斥锁相对于Monitor
锁的成本增加。
为了帮助其他可能发现这个问题的人,我在下面留下了我的原始答案:
原始答案
你只需要一把简单的锁就可以了。
创建object
:类型的实例(或静态,如果这些函数是静态的)变量
private object tokenLock = new object();
在代码中,将需要原子化的步骤包含在lock(tokenLock)
块中:
lock(tokenLock)
{
var token = _tokenService.GetCurrentToken();
var newToken = oauth.RenewAccessToken(token);
_tokenService.UpdateCurrentToken(newToken);
}
这将阻止一个线程在另一个线程执行该进程时启动该进程。