这个锁定和管理锁定异常的解决方案有什么问题?

6
我的目标是在应用程序中实现线程安全功能和异常处理的规范。我对线程管理/多线程的概念相对较新。我正在使用.NET 3.5。
我编写了以下帮助方法来包装所有锁定操作,阅读了此文章http://blogs.msdn.com/b/ericlippert/archive/2009/03/06/locks-and-exceptions-do-not-mix.aspx后得出这个想法,该文章是作为对此问题Monitor vs lock的回答而提供的链接。
我的想法是,如果我在应用程序中始终使用这个约定,编写线程安全代码和处理线程安全代码中的错误将更容易,而不会破坏状态。
public static class Locking
{

    private static readonly Dictionary<object,bool> CorruptionStateDictionary = new Dictionary<object, bool>(); 
    private static readonly object CorruptionLock = new object();

    public static bool TryLockedAction(object lockObject, Action action, out Exception exception)
    {
        if (IsCorrupt(lockObject))
        {
            exception = new LockingException("Cannot execute locked action on a corrupt object.");
            return false;
        }
        exception = null;
        Monitor.Enter(lockObject);
        try
        {
            action.Invoke();
        }
        catch (Exception ex)
        {
            exception = ex;
        }
        finally
        {
            lock (CorruptionLock)   // I don't want to release the lockObject until its corruption-state is updated.
                                    // As long as the calling class locks the lockObject via TryLockedAction(), this should work
            {
                Monitor.Exit(lockObject);
                if (exception != null)
                {   
                    if (CorruptionStateDictionary.ContainsKey(lockObject))
                    {
                        CorruptionStateDictionary[lockObject] = true;
                    }
                    else
                    {
                        CorruptionStateDictionary.Add(lockObject, true);
                    }
                }
            }
        }
        return exception == null;
    }

    public static void Uncorrupt(object corruptLockObject)
    {
        if (IsCorrupt(corruptLockObject))
        {
            lock (CorruptionLock)
            {
                CorruptionStateDictionary[corruptLockObject] = false;
            }
        }
        else
        {
            if(!CorruptionStateDictionary.ContainsKey(corruptLockObject))
            {
                throw new LockingException("Uncorrupt() is not valid on object that have not been corrupted."); 
            }
            else
            {
                //  The object has previously been uncorrupted.
                //  My thought is to ignore the call.
            }
        }
    }

    public static bool IsCorrupt(object lockObject)
    {
        lock(CorruptionLock)
        {
            return CorruptionStateDictionary.ContainsKey(lockObject) && CorruptionStateDictionary[lockObject];
        }
    }


}

我使用 LockingException 类来方便调试。

    public class LockingException : Exception
    {
        public LockingException(string message) : base(message) { }
    }

这是一个示例用法类,展示我打算如何使用它。
public class ExampleUsage
{
    private readonly object ExampleLock = new object();

    public void ExecuteLockedMethod()
    {
        Exception exception;
        bool valid = Locking.TryLockedAction(ExampleLock, ExecuteMethod, out exception);
        if (!valid)
        {
            bool revalidated = EnsureValidState();
            if (revalidated)
            {
                Locking.Uncorrupt(ExampleLock);
            }
        }
    }

    private void ExecuteMethod()
    {
        //does something, maybe throws an exception

    }

    public bool EnsureValidState()
    {
        // code to make sure the state is valid
        // if there is an exception returns false,

        return true;
    }
}
4个回答

8
你的解决方案似乎只是因为在TryLockedAction中存在竞争而增加了复杂性:
如果(lockObject)已经损坏,那么在锁定的对象上执行锁定操作就会出错。当我们正在等待Monitor.Enter时,lockObject可能会变得“损坏”,所以没有保护。
我不确定你想要实现什么行为,但分离锁定和状态管理可能有帮助:
```class StateManager { public bool IsCorrupted { get; set; }
public void Execute(Action body, Func fixState) { if (this.IsCorrupted) { // 在这里使用一些派生自Exception的类。 throw new Exception("不能对已损坏的对象执行操作。"); }
try { body(); } catch (Exception) { this.IsCorrupted = true; if (fixState()) { this.IsCorrupted = false; }
throw; } } } public class ExampleUsage { private readonly object ExampleLock = new object(); private readonly StateManager stateManager = new StateManager();
public void ExecuteLockedMethod() { lock (ExampleLock) { stateManager.Execute(ExecuteMethod, EnsureValidState); } }
private void ExecuteMethod() { //做某件事,可能会抛出异常 }
public bool EnsureValidState() { //代码确保状态有效 //如果有异常则返回false return true; } } ```
此外,据我了解,文章的重点是在并发存在的情况下更难管理状态。但是,这仍然只是您对象状态正确性问题的正交性,并且可能需要使用完全不同的方法来确保正确性。例如,而不是在锁定的代码区域内更改某些复杂状态,请创建一个新状态,如果成功,则仅进行单个简单引用赋值以切换到新状态:
```public class ExampleUsage { private ExampleUsageState state = new ExampleUsageState();
public void ExecuteLockedMethod() { var newState = this.state.ExecuteMethod(); this.state = newState; } } public class ExampleUsageState { public ExampleUsageState ExecuteMethod() { //做某件事,可能会抛出异常 } } ```
个人而言,我总是倾向于认为手动锁定足够困难,以至于需要单独处理每种需要它的情况(因此没有太多需要通用状态管理解决方案),并且低级别工具足够节省使用它。

1

虽然看起来很可靠,但我有三个顾虑:

1)在每个锁定操作上调用Invoke()的性能成本可能会很高。 2)如果该操作(方法)需要参数怎么办?需要更复杂的解决方案。 3)CorruptionStateDictionary是否会无限增长?我认为uncorrupt()方法应该删除对象而不是将数据设置为false。


1 - 为什么? 2 - 我的意图类似于:()=>{ CallMethod(localVariableAsParameter1,localVariableAsParameter2); } 3 - 好的调用 - smartcaveman
请查看此文章中大约在中途位置的条形图:http://msdn.microsoft.com/en-us/magazine/cc163759.aspx#S3 - Elroy Flynn
显然有性能差异。这篇帖子的答案似乎表明性能成本可以忽略不计:https://dev59.com/T3I95IYBdhLWcg3w8iz1。你会建议什么替代方案? - smartcaveman
也许这样做没问题。我唯一能建议的替代方案是将其分为两部分:锁定和解锁,调用者应该负责调用这两部分,并使用try/finally。另外,你是否担心调用者无法看到异常的详细信息?也许你的方法应该返回Exception对象(或null)而不是bool。 - Elroy Flynn
顺便说一下,SO(Stack Overflow)正在搞砸我的文本。这并不是完全是我的错。<s> - Elroy Flynn

0
  1. 将IsCorrupt测试和Monitor.Enter移动到Try块内部
  2. 将损坏设置处理从finally块移出,并移到Catch块中(只有在抛出异常时才执行)
  3. 在设置损坏标志后再释放主锁(保留在finally块中)
  4. 不要限制异常只在调用线程中抛出;可以重新抛出异常或通过自定义异常替换布尔值,并将其添加到损坏字典中,然后与IsCorrupt检查一起返回
  5. 对于不损坏的情况,只需移除该项
  6. 锁定顺序存在一些问题(见下文)

以上应该涵盖所有情况

  public static class Locking
{
    private static readonly Dictionary<object, Exception> CorruptionStateDictionary = new Dictionary<object, Exception>();
    private static readonly object CorruptionLock = new object();
    public static bool TryLockedAction(object lockObject, Action action, out Exception exception)
    {
        var lockTaken = false;
        exception = null;
        try
        {
            Monitor.Enter(lockObject, ref lockTaken);

            if (IsCorrupt(lockObject))
            {
                exception = new LockingException("Cannot execute locked action on a corrupt object.");
                return false;
            }

            action.Invoke();
        }
        catch (Exception ex)
        {
            var corruptionLockTaken = false;
            exception = ex;
            try
            {
                Monitor.Enter(CorruptionLock, ref corruptionLockTaken);

                if (CorruptionStateDictionary.ContainsKey(lockObject))
                {
                    CorruptionStateDictionary[lockObject] = ex;
                }
                else
                {
                    CorruptionStateDictionary.Add(lockObject, ex);
                }
            }

            finally
            {
                if (corruptionLockTaken)
                {
                    Monitor.Exit(CorruptionLock);
                }
            }
        }
        finally
        {
            if (lockTaken)
            {
                Monitor.Exit(lockObject);
            }
        }
        return exception == null;
    }
    public static void Uncorrupt(object corruptLockObject)
    {
        var lockTaken = false;
        try
        {
            Monitor.Enter(CorruptionLock, ref lockTaken);
            if (IsCorrupt(corruptLockObject))
            {
                { CorruptionStateDictionary.Remove(corruptLockObject); }
            }
        }
        finally
        {
            if (lockTaken)
            {
                Monitor.Exit(CorruptionLock);
            }
        }

    }
    public static bool IsCorrupt(object lockObject)
    {
        Exception ex = null;
        return IsCorrupt(lockObject, out ex);
    }
    public static bool IsCorrupt(object lockObject, out Exception ex)
    {
        var lockTaken = false;
        ex = null;
        try
        {
            Monitor.Enter(CorruptionLock, ref lockTaken);
            if (CorruptionStateDictionary.ContainsKey(lockObject))
            {
                ex = CorruptionStateDictionary[lockObject];
            }
            return CorruptionStateDictionary.ContainsKey(lockObject);
        }
        finally
        {
            if (lockTaken)
            {
                Monitor.Exit(CorruptionLock);
            }
        }           
    }
}

将 Monitor.Enter 移动到 Try 内部会导致对象在抛出异常时仍然被锁定。 - smartcaveman
在C# 4.0中,我们已经改变了lock关键字,使其生成的代码类似于bool lockWasTaken = false; var temp = obj; try { Monitor.Enter(temp, ref lockWasTaken); { body } } finally { if (lockWasTaken) Monitor.Exit(temp); }。 - CCondron
抱歉,我应该更清楚。Monitor.Enter必须放在Try块内以确保在finally内部执行退出。这是3.5代码中存在的问题,但在4.0代码中通过内部的try得到了解决。然后Eric提出了更大的腐败问题。 - CCondron
你打算将这个降级吗? - CCondron

0
我建议的方法是使用一个锁状态管理器对象,其中包含一个“inDangerState”字段。需要访问受保护资源的应用程序首先使用锁管理器对象来获取锁定;管理器将代表应用程序获取锁定并检查inDangerState标志。如果设置了该标志,则管理器将抛出异常并在展开堆栈时释放锁定。否则,管理器将向应用程序返回一个IDisposable,该IDisposable将在Dispose时释放锁定,但也可以操作危险状态标志。在将锁定资源置于错误状态之前,应调用IDisposable上的一个方法,该方法将设置inDangerState并返回一个令牌,该令牌可用于在将锁定资源恢复到安全状态后重新清除它。如果在清除inDangerState标志之前Dispose IDisposable,则资源将处于“危险”状态。
可以恢复锁定资源到安全状态的异常处理程序应在返回或传播异常之前使用令牌清除inDangerState标志。如果异常处理程序无法将锁定资源恢复到安全状态,则应在设置inDangerState的情况下传播异常。
这种模式似乎比您建议的更简单,但似乎比假设所有异常都会破坏锁定资源或不会破坏锁定资源要好得多。

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