/// <summary> /// Creates a task to prepare the source and returns it with <see cref="ResourcePreparationTaskState"/>. /// </summary> /// <param name="taskCreation">A callback method to create the preparation task.</param> /// <param name="finalState">The final resource state when the preparation is done.</param> /// <param name="cancellationToken">A cancellation token to abort the preparation task.</param> /// <returns>The preparation task and its status to be used to join more waiting tasks later.</returns> internal static (ResourcePreparationTaskState PreparationState, Task InitialTask) Create(Func <CancellationToken, Task> taskCreation, ResourceState finalState, CancellationToken cancellationToken) { var preparationState = new ResourcePreparationTaskState(taskCreation, finalState, cancellationToken.CanBeCanceled); Assumes.True(preparationState.TryJoinComputation(isInitialTask: true, out Task? initialTask, cancellationToken)); return(preparationState, initialTask); }
/// <summary> /// Sets the specified resource to be considered in an unknown state. Any subsequent access (exclusive or concurrent) will prepare the resource. /// </summary> private void SetUnknownResourceState(TResource resource) { Requires.NotNull(resource, nameof(resource)); lock (this.service.SyncObject) { this.resourcePreparationStates.TryGetValue(resource, out ResourcePreparationTaskState? previousState); this.resourcePreparationStates[resource] = ResourcePreparationTaskState.Create( _ => previousState?.InnerTask ?? Task.CompletedTask, ResourceState.Unknown, CancellationToken.None).PreparationState; } }
/// <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); }