是否有任何可以在不同线程上退出锁的读/写锁实现

时间:2018-02-02 09:59:02

标签: .net

我想知道我是否不必实现可以在某个线程上进入的读取器/写入器锁,然后进行异步,并在不同的线程上发布。

模式是这样的:

ReaderWriterLockSlim syncLock
        = new ReaderWriterLockSlim(
                LockRecursionPolicy.NoRecursion);
bool gotLock = false;
try {
    gotLock = syncLock.TryEnterReadLock(lockMillisecondsTimeout);
    if (!gotLock)
        throw new TimeoutException();
    await foo()
            .ConfigureAwait(false);
} finally {
    if (gotLock)
        syncLock.ExitReadLock();
}

或者:

using (Disposable_Acquires_And_Releases_Lock) {
    await foo()
            .ConfigureAwait(false);
}

关键是延续回到另一个线程并且无法释放锁定。我发现没有立即实施;也许这是因为它可能有点毛茸茸,确保你实际上在异步分支面前释放锁。

1 个答案:

答案 0 :(得分:0)

编辑:这个类正在通过测试并且运行得很好,所以我添加了一些小的改进。对Volatile和Interlocked的调用将被删除,因为它们不是必需的。这非常简单,并且很容易通过状态下的单个监视器来推理......

虽然AsyncEx看起来非常好,但我自己写了一个更简单的锁。我会在这里发布。

这个锁很简单:只有一个双重检查的Monitor被锁定到设置状态。缺点可能是您需要获取监视器才能进入,并再次退出。它也不是SinWait,因为它需要获取和脉冲监视器来向服务员发出信号。但实施很简单。

using System;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
using Sc.Util.System;


namespace Sc.Threading
{
    /// <summary>
    /// A thred-free reader/writer lock. This lock is NOT recursive.
    /// </summary>
    public sealed class AsyncReaderWriterLock
    {
        private static int waitTime
            => 15;


        private readonly object handle = new object();
        private int readerCount;
        private bool hasWriter;


        private bool enter(int millisecondsTimeout, CancellationToken cancellationToken, bool isWriteLock)
        {
            Stopwatch stopwatch = Stopwatch.StartNew();
            bool IsTimedOut()
            {
                if (cancellationToken.IsCancellationRequested)
                    return true;
                if (millisecondsTimeout < 0)
                    return false;
                stopwatch.Stop();
                if (stopwatch.ElapsedMilliseconds >= millisecondsTimeout)
                    return true;
                millisecondsTimeout -= (int)stopwatch.ElapsedMilliseconds;
                stopwatch.Restart();
                return false;
            }
            while (true) {
                bool gotLock = false;
                try {
                    if (cancellationToken.CanBeCanceled) {
                        Monitor.TryEnter(
                                handle,
                                (millisecondsTimeout < 0)
                                        ? AsyncReaderWriterLock.waitTime
                                        : Math.Min(
                                                AsyncReaderWriterLock.waitTime,
                                                millisecondsTimeout),
                                ref gotLock);
                    } else
                        Monitor.TryEnter(handle, millisecondsTimeout, ref gotLock);
                    if (!gotLock
                            || hasWriter
                            || (isWriteLock
                            && (readerCount > 0))) {
                        if (IsTimedOut())
                            return false;
                        continue;
                    }
                    if (isWriteLock)
                        hasWriter = true;
                    else
                        ++readerCount;
                    return true;
                } finally {
                    if (gotLock) {
                        Monitor.Pulse(handle);
                        Monitor.Exit(handle);
                    }
                }
            }
        }

        private void exit(bool isWriteLock)
        {
            lock (handle) {
                if (isWriteLock)
                    hasWriter = false;
                else
                    --readerCount;
                Monitor.Pulse(handle);
            }
        }

        private Task<T> getExitContinuation<T>(Task<T> task, bool isWriteLock)
            => task.ContinueWith(
                    (argumentTask, exitWriteLock) =>
                    {
                        exit((bool)exitWriteLock);
                        return argumentTask.Result;
                    },
                    isWriteLock,
                    CancellationToken.None,
                    TaskContinuationOptions.ExecuteSynchronously,
                    TaskScheduler.Default);


        /// <summary>
        /// Tries to enter the reader lock, and creates a disposable object that exits the lock
        /// when disposed.
        /// </summary>
        /// <param name="gotLock">Will be false if your attempt to acquire the lock fails: if false
        /// then you do not have the lock.</param>
        /// <param name="millisecondsTimeout">Optional timeout to wait for the lock. Notice that the
        /// defult is not infinite: you may pass <see cref="Timeout.Infinite"/>.</param>
        /// <param name="cancellationToken">Optional token that will cancel the wait for the lock.</param>
        /// <returns>Not null.</returns>
        public IDisposable EnterReaderLock(
                out bool gotLock,
                int millisecondsTimeout = 1000 * 60 * 3,
                CancellationToken cancellationToken = default)
        {
            DelegateDisposable<bool> result
                    = DelegateDisposable.With(
                            () => enter(millisecondsTimeout, cancellationToken, false),
                            isGotLock =>
                            {
                                if (isGotLock)
                                    exit(false);
                            });
            gotLock = result.State;
            return result;
        }

        /// <summary>
        /// Tries to enter the reader lock, and creates a Task continuation that exits the lock when
        /// your Task is complete.
        /// </summary>
        /// <param name="task">Not null. This will only be invoked if the lock is acquired; and if
        /// so, then the returned task is a continuation of this one that invokes and returns
        /// this Tasks's result after exiting the lock.</param>
        /// <param name="millisecondsTimeout">Optional timeout to wait for the lock. Notice that the
        /// defult is not infinite: you may pass <see cref="Timeout.Infinite"/>.</param>
        /// <param name="cancellationToken">Optional token that will cancel the wait for the lock.
        /// Notice that this token is not used on the Tasks.</param>
        /// <returns>You must test the result. The bool will be false if your attempt to acquire the lock
        /// fails: if false then you do not have the lock; and the returned task will not be null, and is
        /// canceled. If true then the returned task must be awaited for the lock exit and the completion
        /// of your task.</returns>
        public (bool gotLock, Task<T> task) WithReaderLock<T>(
                Func<Task<T>> task,
                int millisecondsTimeout = 1000 * 60 * 3,
                CancellationToken cancellationToken = default)
            => enter(millisecondsTimeout, cancellationToken, false)
                    ? (true, getExitContinuation(task(), false))
                    : (false, Task.FromCanceled<T>(default));

        /// <summary>
        /// Tries to enter the write lock, and creates a disposable object that exits the lock
        /// when disposed.
        /// </summary>
        /// <param name="gotLock">Will be false if your attempt to acquire the lock fails: if false
        /// then you do not have the lock.</param>
        /// <param name="millisecondsTimeout">Optional timeout to wait for the lock. Notice that the
        /// defult is not infinite: you may pass <see cref="Timeout.Infinite"/>.</param>
        /// <param name="cancellationToken">Optional token that will cancel the wait for the lock.</param>
        /// <returns>Not null.</returns>
        public IDisposable EnterWriteLock(
                out bool gotLock,
                int millisecondsTimeout = 1000 * 60 * 3,
                CancellationToken cancellationToken = default)
        {
            DelegateDisposable<bool> result
                    = DelegateDisposable.With(
                            () => enter(millisecondsTimeout, cancellationToken, true),
                            isGotLock =>
                            {
                                if (isGotLock)
                                    exit(true);
                            });
            gotLock = result.State;
            return result;
        }

        /// <summary>
        /// Tries to enter the write lock, and creates a Task continuation that exits the lock when
        /// your Task is complete.
        /// </summary>
        /// <param name="task">Not null. This will only be invoked if the lock is acquired; and if
        /// so, then the returned task is a continuation of this one that invokes and returns
        /// this Tasks's result after exiting the lock.</param>
        /// <param name="millisecondsTimeout">Optional timeout to wait for the lock. Notice that the
        /// defult is not infinite: you may pass <see cref="Timeout.Infinite"/>.</param>
        /// <param name="cancellationToken">Optional token that will cancel the wait for the lock.
        /// Notice that this token is not used on the Tasks.</param>
        /// <returns>You must test the result. The bool will be false if your attempt to acquire the lock
        /// fails: if false then you do not have the lock; and the returned task will not be null, and is
        /// canceled. If true then the returned task must be awaited for the lock exit and the completion
        /// of your task.</returns>
        public (bool gotLock, Task<T> task) WithWriteLock<T>(
                Func<Task<T>> task,
                int millisecondsTimeout = 1000 * 60 * 3,
                CancellationToken cancellationToken = default)
            => enter(millisecondsTimeout, cancellationToken, true)
                    ? (true, getExitContinuation(task(), true))
                    : (false, Task.FromCanceled<T>(default));
    }
}

...而且包含在Dispose中调用的Action的DelegateDisposable ---是这样的:

using System;
using System.Threading;
using System.Threading.Tasks;


namespace Sc.Util.System
{
    /// <summary>
    /// Simple thread-safe <see cref="IDisposable"/> class that invokes an
    /// <see cref="Action"/> in <see cref="IDisposable.Dispose"/>. Will not invoke the
    /// delegate twice.
    /// </summary>
    public sealed class DelegateDisposable
            : IDisposable
    {
        /// <summary>
        /// As with <see cref="DelegateDisposable"/>; but this class implements a finalizer.
        /// </summary>
        public sealed class Finalizing
                : IDisposable
        {
            private Action<bool> disposeDelegate;


            /// <summary>
            /// Cnstructor.
            /// </summary>
            /// <param name="dispose">Not null.</param>
            public Finalizing(Action<bool> dispose)
                => disposeDelegate = dispose ?? throw new ArgumentNullException(nameof(dispose));


            private void dispose(bool isDisposing)
                => Interlocked.Exchange(ref disposeDelegate, null)
                        ?.Invoke(isDisposing);

            public void Dispose()
            {
                GC.SuppressFinalize(this);
                dispose(true);
            }

            ~Finalizing()
                => dispose(false);
        }


        /// <summary>
        /// Static constructor method. Invokes your <see cref="Action"/> in
        /// <see cref="IDisposable.Dispose"/>. Will not invoke the delegate twice.
        /// </summary>
        /// <param name="dispose">Not null.</param>
        /// <returns>Not null.</returns>
        public static IDisposable With(Action dispose)
            => new DelegateDisposable(dispose);

        /// <summary>
        /// Static constructor method for an instance that implements a finalizer. Invokes your
        /// <see cref="Action"/> in <see cref="IDisposable.Dispose"/> or the finalizer. Will not
        /// invoke the delegate twice.
        /// </summary>
        /// <param name="dispose">Not null.</param>
        /// <returns>Not null.</returns>
        public static IDisposable With(Action<bool> dispose)
            => new Finalizing(dispose);


        /// <summary>
        /// Static constructor method. Creates a new <see cref="Task"/> that will complete when the
        /// returned object is <see cref="IDisposable.Dispose"/>. Also will cancel the task if you
        /// provide a token; and allows a Task result.
        /// </summary>
        /// <param name="task">The task that will complete when the result is disposed.</param>
        /// <param name="taskResult">Optional. Will set the result of the completed <c>task</c>.</param>
        /// <param name="cancellationToken">Optional token that can cancel the returned <c>task</c>.</param>
        /// <returns>Not null.</returns>
        public static IDisposable When<T>(
                out Task<T> task,
                Func<T> taskResult = default,
                CancellationToken cancellationToken = default)
        {
            DelegateDisposable<(TaskCompletionSource<T> tcs, Func<T> taskResult, Action<object> tokenCallback)> result
                    = DelegateDisposable.With
                            <(TaskCompletionSource<T> tcs, Func<T> taskResult, Action<object> tokenCallback),
                            Func<T>>(
                            taskResultDelegate =>
                            {
                                TaskCompletionSource<T> taskCompletionSource = new TaskCompletionSource<T>();
                                if (!cancellationToken.CanBeCanceled)
                                    return (taskCompletionSource, taskResultDelegate, null);
                                // ReSharper disable once ConvertToLocalFunction
                                Action<object> tokenCallback = tcs => ((TaskCompletionSource<T>)tcs).TrySetCanceled();
                                cancellationToken.Register(tokenCallback, taskCompletionSource);
                                if (cancellationToken.IsCancellationRequested)
                                    taskCompletionSource.TrySetCanceled();
                                return (taskCompletionSource, taskResultDelegate, tokenCallback);
                            },
                            taskResult ?? (() => default),
                            tuple =>
                            {
                                if (!cancellationToken.CanBeCanceled
                                        || !cancellationToken.IsCancellationRequested)
                                    tuple.tcs.TrySetResult(tuple.taskResult());
                            });
            task = result.State.tcs.Task;
            return result;
        }


        /// <summary>
        /// Static constructor method. This generic class adds a <see cref="DelegateDisposable{T}.State"/>
        /// object that you can provide from a delegate when this object is constructed. Invokes your
        /// <see cref="Action"/> in <see cref="IDisposable.Dispose"/>. Will not invoke the delegate twice.
        /// </summary>
        /// <param name="constructor">Not null.</param>
        /// <param name="dispose">Not null.</param>
        /// <returns>Not null.</returns>
        public static DelegateDisposable<TState> With<TState>(Func<TState> constructor, Action<TState> dispose)
            => new DelegateDisposable<TState>(constructor, dispose);

        /// <summary>
        /// Static constructor method. This this generic class adds a <see cref="DelegateDisposable{T}.State"/>
        /// object that you can provide from a delegate when this object is constructed; and this method
        /// allows your constructor delegate to provide itself an argument. Invokes your <see cref="Action"/>
        /// in <see cref="IDisposable.Dispose"/>. Will not invoke the delegate twice.
        /// </summary>
        /// <param name="constructor">Not null.</param>
        /// <param name="constructorArg">An arbitrary argument for your own <c>constructor</c>.</param>
        /// <param name="dispose">Not null.</param>
        /// <returns>Not null.</returns>
        public static DelegateDisposable<TState> With<TState, TArg>(
                Func<TArg, TState> constructor,
                TArg constructorArg,
                Action<TState> dispose)
            => new DelegateDisposable<TState>(() => constructor(constructorArg), dispose);


        /// <summary>
        /// Static constructor method for an instance that implements a finalizer.This generic class adds a
        /// <see cref="DelegateDisposable{T}.State"/> object that you can provide from a delegate when this
        /// object is constructed. Invokes your <see cref="Action"/> in <see cref="IDisposable.Dispose"/>
        /// or the finalizer. Will not invoke the delegate twice.
        /// </summary>
        /// <param name="constructor">Not null.</param>
        /// <param name="dispose">Not null.</param>
        /// <returns>Not null.</returns>
        public static DelegateDisposable<TState>.Finalizing With<TState>(
                Func<TState> constructor,
                Action<TState, bool> dispose)
            => new DelegateDisposable<TState>.Finalizing(constructor, dispose);

        /// <summary>
        /// Static constructor method for an instance that implements a finalizer. This this generic class
        /// adds a <see cref="DelegateDisposable{T}.State"/> object that you can provide from a delegate when
        /// this object is constructed; and this method allows your constructor delegate to provide itself
        /// an argument. Invokes your <see cref="Action"/> in <see cref="IDisposable.Dispose"/> or the
        /// finalizer. Will not invoke the delegate twice.
        /// </summary>
        /// <param name="constructor">Not null.</param>
        /// <param name="constructorArg">An arbitrary argument for your own <c>constructor</c>.</param>
        /// <param name="dispose">Not null.</param>
        /// <returns>Not null.</returns>
        public static DelegateDisposable<TState>.Finalizing With<TState, TArg>(
                Func<TArg, TState> constructor,
                TArg constructorArg,
                Action<TState, bool> dispose)
            => new DelegateDisposable<TState>.Finalizing(() => constructor(constructorArg), dispose);


        private Action disposeDelegate;


        /// <summary>
        /// Cnstructor.
        /// </summary>
        /// <param name="dispose">Not null.</param>
        public DelegateDisposable(Action dispose)
            => disposeDelegate = dispose ?? throw new ArgumentNullException(nameof(dispose));


        public void Dispose()
            => Interlocked.Exchange(ref disposeDelegate, null)
                    ?.Invoke();
    }


    /// <summary>
    /// Simple thread-safe <see cref="IDisposable"/> class that invokes an
    /// <see cref="Action"/> in <see cref="IDisposable.Dispose"/>. Will not invoke the
    /// delegate twice. This generic class adds a <see cref="State"/> object that you
    /// can provide from a delegate when this object is constructed.
    /// </summary>
    /// <typeparam name="TState">Your state type.</typeparam>
    public sealed class DelegateDisposable<TState>
            : IDisposable
    {
        /// <summary>
        /// As with <see cref="DelegateDisposable{T}"/>; but this class implements a finalizer.
        /// </summary>
        public sealed class Finalizing
                : IDisposable
        {
            private Action<TState, bool> disposeDelegate;


            /// <summary>
            /// Cnstructor.
            /// </summary>
            /// <param name="constructor">Not null.</param>
            /// <param name="dispose">Not null.</param>
            public Finalizing(Func<TState> constructor, Action<TState, bool> dispose)
            {
                if (constructor == null)
                    throw new ArgumentNullException(nameof(constructor));
                // ReSharper disable once JoinNullCheckWithUsage
                if (dispose == null)
                    throw new ArgumentNullException(nameof(dispose));
                State = constructor();
                disposeDelegate = dispose;
            }


            /// <summary>
            /// Your arbitrary state. Set in the constructor; and set to default in dispose.
            /// </summary>
            public TState State { get; private set; }


            private void dispose(bool isDisposing)
            {
                Interlocked.Exchange(ref disposeDelegate, null)
                        ?.Invoke(State, isDisposing);
                State = default;
            }

            public void Dispose()
            {
                GC.SuppressFinalize(this);
                dispose(true);
            }

            ~Finalizing()
                => dispose(false);
        }


        private Action<TState> disposeDelegate;


        /// <summary>
        /// Cnstructor.
        /// </summary>
        /// <param name="constructor">Not null.</param>
        /// <param name="dispose">Not null.</param>
        public DelegateDisposable(Func<TState> constructor, Action<TState> dispose)
        {
            if (constructor == null)
                throw new ArgumentNullException(nameof(constructor));
            // ReSharper disable once JoinNullCheckWithUsage
            if (dispose == null)
                throw new ArgumentNullException(nameof(dispose));
            State = constructor();
            disposeDelegate = dispose;
        }


        /// <summary>
        /// Your arbitrary state. Set in the constructor; and set to default in dispose.
        /// </summary>
        public TState State { get; private set; }


        public void Dispose()
        {
            Interlocked.Exchange(ref disposeDelegate, null)
                    ?.Invoke(State);
            State = default;
        }
    }
}