锁定语句似乎无法正常工作

5

我有这个方法:

public bool Remove(EntityKeyType key)
{
    lock (syncroot)
    {
        //wait if we need to
        waitForContextMRE.Wait();

        //if the item is not local, assume it is not remote.
        if (!localCache.ContainsKey(key)) return false;

        //build an expression tree
        Expression<Func<EntityType, bool>> keyComparitorExpression = GenerateKeyComparitorExpression(key);

        var itemToDelete = TableProperty.Single(keyComparitorExpression);

        //delete from db
        TableProperty.DeleteOnSubmit(itemToDelete);
        DataContext.SubmitChanges();

        //get the removed item for OnCollectionChanged
        EntityType itemToRemove = localCache[key];
        itemToRemove.PropertyChanged -= item_PropertyChanged;

        //remove from the list
        Debug.Assert(localCache.Remove(key));

        //call the notification
        OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, itemToRemove));
        return true;
    }
}

我从多个线程中调用它(调用同一实例),但是TableProperty.Single(序列不包含任何元素)会抛出异常。通过调试代码,我发现在一个不同的线程检查缓存中是否存在该项后,该项正在从数据库中删除。除非在锁定语句内有多个线程(同步对象跨线程绝对是相同的实例),否则不可能发生这种情况。

不可能?我有证据: Impossible situation

锁定语句中有三个线程!怎么回事?

注意:

  1. MRE已设置(未阻止)。
  2. 这不是引发异常的情况,只是显示锁定部分内的多个线程。更新:我将图像更改为intellitrace异常事件。旧图像在此处
  3. 同步对象不是静态的,因为我只想要对同一实例的调用进行同步。

更新

这是同步对象的声明:

private object syncroot = new object();

还有一些其他的声明:

private ManualResetEventSlim waitForContextMRE = new ManualResetEventSlim(true);
private DataContextType _dataContext;
private System.Data.Linq.Table<EntityType> _tableProperty;
//DataContextType and EntityType are generic type parameters

我无法将syncroot设置为静态,因为我有多个类实例在运行,重要的是它们不会互相阻塞。但这并不重要-使它静态并不能解决问题。ManualResetEvent(waitForContextMRE)不用于同步-它用于在执行某些操作后(例如启动时)阻止数据库操作一段时间。它大部分时间都被设置。将其从锁定块中删除也无法解决问题。

1
A: 我们能看到你在哪里初始化 syncroot 吗? B: 对象上下文会持续多久? C: 你确定它们是同一个实例吗?从暂停状态很难判断... - Marc Gravell
1
我建议添加跟踪消息,使用System.Diagnostics.Debug.WriteLine。在锁定块的开头和结尾处编写线程ID和syncroot哈希代码。然后,您可以轻松地查看输出窗口中是否有两个线程与相同的syncroot重叠,这绝对不应该发生。 - Kevin Gosse
1
@Bob 可能意图更细粒度化,而不是每个应用程序一个锁;在许多应用程序中,静态锁非常需要避免。 - Marc Gravell
1
@Bob 绝对没错;假设这是一个高使用率区域,它会使您的多核服务器变得相当无用 - 只允许单个调用者。如果数据(如 OP 所打算)是分开的,则没有必要阻塞彼此。如果仅限于本地访问,那么这是不可取的,但可以原谅(至少直到分析表明它很重要)- 但是当添加网络访问(在本例中为 DB)时,它可能代表一个显著瓶颈。 - Marc Gravell
@Marc 谢谢。我没有想到这个角度。通过使用静态锁,每次只有一个线程可以访问该代码。因此你的多核/无用的评论。 - Bob Horn
显示剩余6条评论
4个回答

3
我唯一的解释是waitForContextMRE.Wait(); 调用此方法使线程解除 syncroot 阻塞!这样其他线程就可以进入锁定部分。尝试将 waitForContextMRE.Wait(); 移动到 lock(...) 之前。

我认为我们无法看到足够的关于waitForContextMRE的信息来得出任何结论;Monitor.Wait(syncroot)是唯一会释放锁的东西 - 对我来说这并不明显。 - Marc Gravell
如果您已经在锁内部并且假设没有其他进程使用该线程,为什么要调用wait()呢?//如果需要,等待 waitForContextMRE.Wait(); - Travis J

3
我认为你正在调用不同的对象。从你的截图上看不出你是否从不同的线程中获取值。 此外,使用非静态的syncroot并不是一个好主意,因为可能会导致像你这样的情况。你真的有很充分的理由不将其设为静态吗?

@Elastep 我同意没有足够的证据表明它们是同一个对象,因此我请求更多信息作为(未回答的)评论... - Marc Gravell
它需要是非静态的,因为会有多个对象实例,这些实例不能相互阻塞。此代码正在从单元测试中调用,在这种情况下只有一个实例。 - Cillié Malan

0

我建议锁定TableProperty或DataContext


5
出于好奇...基于什么?线程问题非常复杂,因此应避免任何莽撞的反应,并且任何建议都应该是:a:基于证据,b:包括推理,但不改变原意。 - Marc Gravell
我无法锁定TableProperty或DataContext,因为存在这样的情况:它们被更改(处置并重新创建),在这种情况下,它还会锁定syncroot对象。 - Cillié Malan

0

我已经调试了一段时间,虽然我还没有解决它,但很明显锁正在工作。我猜问题出在DataContext上(在多线程情况下众所周知它们很棘手)。


我找到了问题 —— Debug.Assert(localCache.Remove(key)) —— 就像在 C++ 中,assert 内部的内容不会在 release 模式下运行,导致项目从数据库而非缓存中被删除。 - Cillié Malan

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