/// <summary>
            /// Prepares the specified resource for access by a lock holder.
            /// </summary>
            /// <param name="resource">The resource to prepare.</param>
            /// <param name="cancellationToken">The token whose cancellation signals lost interest in this resource.</param>
            /// <param name="forcePrepareConcurrent">Force preparation of the resource for concurrent access, even if an exclusive lock is currently held.</param>
            /// <returns>A task that is completed when preparation has completed.</returns>
            private Task PrepareResourceAsync(TResource resource, CancellationToken cancellationToken, bool forcePrepareConcurrent = false)
            {
                Requires.NotNull(resource, nameof(resource));
                Assumes.True(Monitor.IsEntered(this.service.SyncObject));

                // We deliberately ignore the cancellation token in the tasks we create and save because the tasks can be shared
                // across requests and we can't have task continuation chains where tasks within the chain get canceled
                // as that can cause premature starting of the next task in the chain.
                bool forConcurrentUse = forcePrepareConcurrent || !this.service.IsWriteLockHeld;

                AsyncReaderWriterResourceLock <TMoniker, TResource> .Helper.ResourceState finalState = forConcurrentUse ? ResourceState.Concurrent : ResourceState.Exclusive;
                object stateObject = forConcurrentUse
                    ? (object)resource
                    : Tuple.Create(resource, this.service.GetAggregateLockFlags());

                if (!this.resourcePreparationTasks.TryGetValue(resource, out ResourcePreparationTaskAndValidity preparationTask))
                {
                    Func <object, Task>?preparationDelegate = forConcurrentUse
                        ? this.prepareResourceConcurrentDelegate
                        : this.prepareResourceExclusiveDelegate;

                    // We kick this off on a new task because we're currently holding a private lock
                    // and don't want to execute arbitrary code.
                    // Let's also hide the ARWL from the delegate if this is a shared lock request.
                    using (forConcurrentUse ? this.service.HideLocks() : default(Suppression))
                    {
                        preparationTask = new ResourcePreparationTaskAndValidity(
                            Task.Factory.StartNew(NullableHelpers.AsNullableArgFunc(preparationDelegate), stateObject, CancellationToken.None, TaskCreationOptions.None, TaskScheduler.Default).Unwrap(),
                            finalState);
                    }
                }
                else if (preparationTask.State != finalState || preparationTask.PreparationTask.IsFaulted)
                {
                    Func <Task, object, Task>?preparationDelegate = forConcurrentUse
                        ? this.prepareResourceConcurrentContinuationDelegate
                        : this.prepareResourceExclusiveContinuationDelegate;

                    // We kick this off on a new task because we're currently holding a private lock
                    // and don't want to execute arbitrary code.
                    // Let's also hide the ARWL from the delegate if this is a shared lock request.
                    using (forConcurrentUse ? this.service.HideLocks() : default(Suppression))
                    {
                        preparationTask = new ResourcePreparationTaskAndValidity(
                            preparationTask.PreparationTask.ContinueWith(preparationDelegate !, stateObject, CancellationToken.None, TaskContinuationOptions.None, TaskScheduler.Default).Unwrap(),
                            finalState);
                    }
                }

                Assumes.NotNull(preparationTask.PreparationTask);
                this.resourcePreparationTasks[resource] = preparationTask;

                // We tack cancellation onto the task that we actually return to the caller.
                // This doesn't cancel resource preparation, but it does allow the caller to return early
                // in the event of their own cancellation token being canceled.
                return(preparationTask.PreparationTask.WithCancellation(cancellationToken));
            }
            /// <summary>
            /// Prepares the specified resource for access by a lock holder.
            /// </summary>
            /// <param name="resource">The resource to prepare.</param>
            /// <param name="cancellationToken">The token whose cancellation signals lost interest in this resource.</param>
            /// <param name="forcePrepareConcurrent">Force preparation of the resource for concurrent access, even if an exclusive lock is currently held.</param>
            /// <returns>A task that is completed when preparation has completed.</returns>
            private Task PrepareResourceAsync(TResource resource, CancellationToken cancellationToken, bool forcePrepareConcurrent = false)
            {
                Requires.NotNull(resource, nameof(resource));
                Assumes.True(Monitor.IsEntered(this.service.SyncObject));

                // We deliberately ignore the cancellation token in the tasks we create and save because the tasks can be shared
                // across requests and we can't have task continuation chains where tasks within the chain get canceled
                // as that can cause premature starting of the next task in the chain.
                bool forConcurrentUse = forcePrepareConcurrent || !this.service.IsWriteLockHeld;

                AsyncReaderWriterResourceLock <TMoniker, TResource> .Helper.ResourceState finalState = forConcurrentUse ? ResourceState.Concurrent : ResourceState.Exclusive;

                Task?preparationTask = null;

                if (!this.resourcePreparationStates.TryGetValue(resource, out ResourcePreparationTaskState? preparationState))
                {
                    Func <object, Task>?preparationDelegate = forConcurrentUse
                        ? this.prepareResourceConcurrentDelegate
                        : this.prepareResourceExclusiveDelegate;

                    // We kick this off on a new task because we're currently holding a private lock
                    // and don't want to execute arbitrary code.
                    // Let's also hide the ARWL from the delegate if this is a shared lock request.
                    using (forConcurrentUse ? this.service.HideLocks() : default(Suppression))
                    {
                        // We can't currently use the caller's cancellation token for this task because
                        // this task may be shared with others or call this method later, and we wouldn't
                        // want their requests to be cancelled as a result of this first caller cancelling.
                        (preparationState, preparationTask) = ResourcePreparationTaskState.Create(
                            combinedCancellationToken => Task.Factory.StartNew(
                                NullableHelpers.AsNullableArgFunc(preparationDelegate),
                                forConcurrentUse ? Tuple.Create(resource, combinedCancellationToken) : Tuple.Create(resource, this.service.GetAggregateLockFlags(), combinedCancellationToken),
                                combinedCancellationToken,
                                TaskCreationOptions.None,
                                TaskScheduler.Default).Unwrap(),
                            finalState,
                            cancellationToken);
                    }
                }
                else
                {
                    Func <Task, object, Task>?preparationDelegate = null;
                    if (preparationState.State != finalState || preparationState.InnerTask.IsFaulted)
                    {
                        preparationDelegate = forConcurrentUse
                            ? this.prepareResourceConcurrentContinuationDelegate
                            : this.prepareResourceExclusiveContinuationDelegate;
                    }
                    else if (!preparationState.TryJoinPrepationTask(out preparationTask, cancellationToken))
                    {
                        preparationDelegate = forConcurrentUse
                            ? this.prepareResourceConcurrentContinuationOnPossibleCancelledTaskDelegate
                            : this.prepareResourceExclusiveContinuationOnPossibleCancelledTaskDelegateDelegate;
                    }

                    if (preparationTask is null)
                    {
                        Assumes.NotNull(preparationDelegate);

                        // We kick this off on a new task because we're currently holding a private lock
                        // and don't want to execute arbitrary code.
                        // Let's also hide the ARWL from the delegate if this is a shared lock request.
                        using (forConcurrentUse ? this.service.HideLocks() : default(Suppression))
                        {
                            (preparationState, preparationTask) = ResourcePreparationTaskState.Create(
                                combinedCancellationToken => preparationState.InnerTask.ContinueWith(
                                    preparationDelegate !,
                                    forConcurrentUse ? Tuple.Create(resource, combinedCancellationToken) : Tuple.Create(resource, this.service.GetAggregateLockFlags(), combinedCancellationToken),
                                    CancellationToken.None,
                                    TaskContinuationOptions.RunContinuationsAsynchronously,
                                    TaskScheduler.Default).Unwrap(),
                                finalState,
                                cancellationToken);
                        }
                    }
                }

                Assumes.NotNull(preparationState);
                this.resourcePreparationStates[resource] = preparationState;

                return(preparationTask);
            }
Exemple #3
0
 /// <summary>
 /// Initializes a new instance of the <see cref="WaiterCompletionSource"/> class.
 /// </summary>
 /// <param name="owner">The event that is initializing this value.</param>
 /// <param name="allowInliningContinuations"><c>true</c> to allow continuations to be inlined upon the completer's callstack.</param>
 /// <param name="cancellationToken">The cancellation token associated with the waiter.</param>
 internal WaiterCompletionSource(AsyncAutoResetEvent owner, bool allowInliningContinuations, CancellationToken cancellationToken)
     : base(allowInliningContinuations)
 {
     this.CancellationToken = cancellationToken;
     this.Registration      = cancellationToken.Register(NullableHelpers.AsNullableArgAction(owner.onCancellationRequestHandler), this);
 }