如何对第三方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

如何对第三方API(OAuth)进行线程安全调用

编辑

假设这是一个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);
}

这将阻止一个线程在另一个线程执行该进程时启动该进程。