2

以下 C# 类用于多线程环境。我删除了很多实际代码。几乎同时调用 MethodA 和 MethodB 时会出现问题。IsDepleted 属性中的锁顺序并不能解决问题。从 IsDepleted 属性中删除 lock(WaitingQueue) 可以解决死锁,但是当另一个线程在 WaitingQueue.Count == 0 和 Processing.Count == 0 语句之间从 WaitingQueue 添加/删除项目时,此解决方案会导致问题。

using System.Collections.Generic;

class Example
{
    bool IsDepleted
    {
        get
        {
            lock (Processing)
            {
                lock (WaitingQueue)
        {
                    return WaitingQueue.Count == 0
             && Processing.Count == 0;
        }
            }
        }
    }

    private readonly List<object> Processing = new List<object>();
    private readonly Queue<object> WaitingQueue = new Queue<object>();

    public void MethodA(object item)
    {
        lock (WaitingQueue)
        {
            if (WaitingQueue.Count > 0)
            {
                if (StartItem(WaitingQueue.Peek()))
                {
                    WaitingQueue.Dequeue();
                }
            }
        }
    }

    public void MethodB(object identifier)
    {
        lock (Processing)
        {
            Processing.Remove(identifier);
            if (!IsDepleted)
            {
                return;
            }
        }
    //Do something...
    }

    bool StartItem(object item)
    {
        //Do something and return a value
    }
}
4

3 回答 3

4

这取决于您是想要快速修复还是严格修复。

一个快速的解决方法是在所有情况下都使用一个锁对象。

例如private readonly object _lock = new object();

然后锁定它。但是,根据您的情况,这可能对性能的影响超出您的接受程度。

即你的代码会变成这样:

using System.Collections.Generic;

class Example
{
    private readonly object _lock = new object();

    bool IsDepleted
    {
        get
        {
            lock (_lock)
            {
                return WaitingQueue.Count == 0
                 && Processing.Count == 0;
            }
        }
    }

    private readonly List<object> Processing = new List<object>();
    private readonly Queue<object> WaitingQueue = new Queue<object>();

    public void MethodA(object item)
    {
        lock (_lock)
        {
            if (WaitingQueue.Count > 0)
            {
                if (StartItem(WaitingQueue.Peek()))
                {
                    WaitingQueue.Dequeue();
                }
            }
        }
    }

    public void MethodB(object identifier)
    {
        lock (_lock)
        {
            Processing.Remove(identifier);
            if (!IsDepleted)
            {
                return;
            }
        }
        //Do something...
    }

    bool StartItem(object item)
    {
        //Do something and return a value
    }
}
于 2009-01-14T18:52:28.957 回答
3

取方法 A 中的 Processing 锁和方法 B 中的 WaitingQueue 锁(换句话说,让它看起来像第一个代码块)。这样,您总是以相同的顺序获取锁,并且您永远不会死锁。

于 2009-01-14T18:50:19.820 回答
2

简化您的代码并仅使用一个对象来锁定。您还可以将锁替换为:

Monitor.TryEnter(处理中,1000)

这会给你 1 秒的超时时间。所以本质上:

        if (Monitor.TryEnter(Processing, 1000))
        {
            try
            {
                //do x
            }
            finally
            {
                Monitor.Exit(Processing);
            }
        }

现在你不会停止死锁,但你可以处理没有锁的情况。

于 2009-01-14T19:00:03.903 回答