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

时间:2011-06-01 01:57:10

标签: c# oauth thread-safety

我正在调用使用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

1 个答案:

答案 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)

这将阻止一个线程启动此进程,而另一个线程正在执行它。