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