What is wrong with this solution to locking and managing locked exceptions?

前端 未结 4 346
佛祖请我去吃肉
佛祖请我去吃肉 2021-01-01 02:08

My objective is a convention for thread-safe functionality and exception handling within my application. I\'m relatively new to the concept of thread management/multithread

相关标签:
4条回答
  • 2021-01-01 02:35

    The approach I would suggest would be to have a lock-state-manager object, with an "inDangerState" field. An application that needs to access a protected resource starts by using the lock-manager-object to acquire the lock; the manager will acquire the lock on behalf of the application and check the inDangerState flag. If it's set, the manager will throw an exception and release the lock while unwinding the stack. Otherwise the manager will return an IDisposable to the application which will release the lock on Dispose, but which can also manipulate the danger state flag. Before putting the locked resource into a bad state, one should call a method on the IDisposable which will set inDangerState and return a token that can be used to re-clear it once the locked resource is restored to a safe state. If the IDisposable is Dispose'd before the inDangerState flag is re-cleared, the resource will be 'stuck' in 'danger' state.

    An exception handler which can restore the locked resource to a safe state should use the token to clear the inDangerState flag before returning or propagating the exception. If the exception handler cannot restore the locked resource to a safe state, it should propagate the exception while inDangerState is set.

    That pattern seems simpler than what you suggest, but seems much better than assuming either that all exceptions will corrupt the locked resource, or that none will.

    0 讨论(0)
  • 2021-01-01 02:45
    1. Move the IsCorrupt test and the Monitor.Enter inside the Try
    2. Move the corruption set handling out of finally and into the Catch block (this should only execute if an exception has been thrown)
    3. Don't release the primary lock until after the corruption flag has been set (leave it in the finaly block)
    4. Don't restrict the execption to the calling thread; either rethow it or add it to the coruption dictionary by replacing the bool with the custom execption, and return it with the IsCorrupt Check
    5. For Uncorrupt simply remove the item
    6. There are some issues with the locking sequencing (see below)

    That should cover all the bases

      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);
                }
            }           
        }
    }
    
    0 讨论(0)
  • 2021-01-01 02:48

    Your solution seems to add nothing but complexity due to a race in the TryLockedAction:

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

    The lockObject might become "corrupted" while we are still waiting on the Monitor.Enter, so there is no protection.

    I'm not sure what behaviour you'd like to achieve, but probably it would help to separate locking and state managing:

    
    class StateManager
    {
        public bool IsCorrupted
        {
            get;
            set;
        }
    
        public void Execute(Action body, Func fixState)
        {
            if (this.IsCorrupted)
            {
                // use some Exception-derived class here.
                throw new Exception("Cannot execute action on a corrupted object.");
            }
    
            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()
        {
            //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;
        }
    }
    
    

    Also, as far as I understand, the point of the article is that state management is harder in presence of concurrency. However, it's still just your object state correctness issue which is orthogonal to the locking and probably you need to use completely different approach to ensuring correctness. E.g. instead of changing some complex state withing locked code region, create a new one and if it succeeded, just switch to the new state in a single and simple reference assignment:

    
    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()
        {
            //does something, maybe throws an exception
        }
    }
    
    

    Personally, I always tend to think that manual locking is hard-enough to treat each case when you need it individually (so there is no much need in generic state-management solutions) and low-lelvel-enough tool to use it really sparingly.

    0 讨论(0)
  • 2021-01-01 02:53

    Though it looks reliable, I have three concerns:

    1) The performance cost of Invoke() on every locked action could be severe. 2) What if the action (the method) requires parameters? A more complex solution will be necessary. 3) Does the CorruptionStateDictionary grow endlessly? I think the uncorrupt() method should problem remove the object rather than set the data false.

    0 讨论(0)
提交回复
热议问题