使用悲观锁将项目添加到Microsoft.ApplicationServer.Caching.DataCache?

3
我正在服务器端使用Azure Shared Caching构建缓存层,以减少对数据库的请求并使运行速度更快(希望能如此)。但我卡在了如何让整个过程线程安全上。我似乎找不到一个可靠和可用的方法来锁定DataCache中的键。我需要一种在存储任何内容之前就可以预先锁定键的方法,以便我能够添加值而无需担心另一个线程同时执行相同操作的风险。
迄今为止,我一直在专注于悲观锁定,因为这是我认为最具有线程安全性的方式,我希望确保我所工作的内容被锁定。
我已经知道,如果我要使用悲观锁定,那么我应该只使用与其相关的方法。混合使用其他方法将破坏整个锁定机制。(来源:http://go4answers.webhost4life.com/Example/datacacheput-unlocking-key-77158.aspx
So basicly I only have access to these methods:
value GetAndLock(key, out DataCacheLockHandle);
void PutAndUnlock(key, value, DataCacheLockHandle);
void Unlock(key, DataCacheLockHandle);

“GetAndLock”在我尝试获取不在缓存中的东西时会抛出异常。同时,我的唯一添加内容到缓存的方法是“PutAndUnlock”,但只有成功执行“GetAndUnlock”后才能使用它。
实际上,不可能向缓存中添加任何新内容,只能替换已经存在的内容(这些内容可能为空)。
所以,在“GetAndLock”抛出没有内容时的异常情况下,我似乎被迫使用乐观的“Put”。根据我阅读的内容,但是,乐观的“Put”会破坏使用“GetAndLock”获得的任何现有锁定,这将破坏线程安全的整个尝试。
Example plan:
1. Try to GetAndLock
2. In case of nothing there exception: 
     - Put a dummy item on the key. 
     - GetAndLock again.
3. We have a lock, do computations, query database etc
4. PutAndUnlock the computed value


One of probably several ways it would screw up:
Thread1: Tries to GetAndLock, gets nothing there exception
Thread2: Tries to GetAndLock, gets nothing there exception
Thread1: Put a dummy item on the key
Thread1: GetAndLock again, lock achieved
Thread2: Put a dummy item on the key (destroying Thread1:s lock)
Thread2: GetAndLock again, lock achieved
Thread1: We think we have a lock, do computations, query database etc
Thread2: We have a lock, do computations, query database etc
Thread1: PutAndUnlock the computed value (will this throw an exception?)
Thread2: PutAndUnlock the computed value

基本上,两个线程可能会同时向同一个键写入不同的内容,而忽略它们都认为拥有的锁。

我唯一的结论是,DataCache的悲观锁定功能不完整且完全无法使用。我是否遗漏了什么?有没有解决这个问题的方法?

我需要的只是在存储任何内容之前就预先锁定键的方法。


GetAndLock方法有一个名为forceLock的参数,MSDN上解释说:如果forceLock为true,则无论缓存中是否存在键值对,都会锁定该键。这难道不能帮助您在状态不存在的情况下锁定键吗? - psulek
1个回答

0

Jonathan,

你考虑过使用这个逻辑将东西添加到缓存中吗(请原谅我的伪代码)?

public bool AddToCache(string key, object value) {

DataCache dc = _factory.GetDefaultCache();  
object currentVal = dc.Get(key);

if (currentVal == null) {
    dc.Put(key, value);
    currentVal = dc.GetAndLock(key);

    if (value == currentVal) {
        //handle this rare occurrence + unlock.
        return false;
    } else {
                   dc.Unlock(key);
            }
} else {
    currentVal = dc.GetAndLock(key);
    dc.PutAndUnlock (key, value);
}

return true;

}


我不确定我完全明白。您的意思是添加一些代码来处理锁被中断的情况吗? - Jonatan Melin
嗯,再读一遍,我猜你的意思不是这个。制作一个Get来检查键是否不存在的问题在于,当您正在工作时,另一个线程可能会执行相同的操作,因此您可能会得到两个并行线程执行Put的结果。这个Put有可能会破坏其他线程已经获取并锁定的GetAndLock。 - Jonatan Melin
这个想法是:
  1. 进行一次 GET 请求来查看它是否存在
  2. 如果不存在,则添加它
  3. 再次检查(使用锁)
  4. 如果新值不等于您刚刚添加的值,则表示在您检查值是否存在并在锁定之前,其他线程已更改该值-->因此,您将不得不添加一些逻辑来处理这种情况。
- SemanticZen
我明白了。我相信这种情况也可能失败,但方式不同。Thread 1:通过Get进行检查 Thread 2:通过Get进行检查 Thread 1:执行Put操作 Thread 3:通过Get进行检查 Thread 1:锁定以检查值是否更改,未更改 Thread 1:解锁 Thread 3:currentVal不为空,执行普通情况锁定 Thread 2:执行Put操作,破坏线程3的锁定。 - Jonatan Melin
话说现在我有一个计划,大部分代码已经写好,但还存在一些bug。当我认为它没有问题时我会发帖介绍的。这个计划的基本思路是在应用程序启动时向缓存添加一个虚拟项,以便我知道可以锁定的东西,然后将其用作整个缓存的金属锁,从而允许进行不被打断的检查和添加操作。 - Jonatan Melin
将该方法设为静态并锁定。 - Sean

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接