/// <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); }
/// <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); }