/// <summary> /// Handles one waiting task can be cancelled. /// </summary> /// <param name="status">The status of the waiting task being cancelled.</param> private void OnWaitingTaskCancelled(WaitingCancellationStatus status) { CancellationTokenSource?overallCancellationSource = null; lock (this.syncObject) { if (--this.outstandingWaitingCount != 0 || !this.isCancellationAllowed) { // when overall cancellation is not allowed, we cancel this single waiting task. status.TrySetCanceled(status.CancellationToken); } else { // otherwise, we cancel the overall computation, when it is done, it will cancel the current waiting task. overallCancellationSource = this.combinedCancellationTokenSource; if (overallCancellationSource is not null) { this.combinedCancellationTokenSource = null; this.isCancellationRequested = true; } } } if (overallCancellationSource is not null) { overallCancellationSource.Cancel(); overallCancellationSource.Dispose(); } }
/// <summary> /// Initializes a new instance of the <see cref="CancellableJoinComputation"/> class. /// </summary> /// <param name="taskFactory">A callback to create the task.</param> /// <param name="allowCancelled">Whether the inner task can be cancelled.</param> internal CancellableJoinComputation(Func <CancellationToken, Task> taskFactory, bool allowCancelled) { Requires.NotNull(taskFactory, nameof(taskFactory)); if (allowCancelled) { this.isCancellationAllowed = true; this.combinedCancellationTokenSource = new CancellationTokenSource(); this.joinedWaitingList = new List <WaitingCancellationStatus>(capacity: 2); } this.InnerTask = taskFactory(this.combinedCancellationTokenSource?.Token ?? CancellationToken.None); if (allowCancelled) { // Note: this continuation is chained asynchronously to prevent being inlined when we trigger the combined cancellation token. this.InnerTask.ContinueWith( (t, s) => { var me = (CancellableJoinComputation)s !; List <WaitingCancellationStatus> allWaitingTasks; CancellationTokenSource?combinedCancellationTokenSource; lock (me.syncObject) { Assumes.NotNull(me.joinedWaitingList); allWaitingTasks = me.joinedWaitingList; combinedCancellationTokenSource = me.combinedCancellationTokenSource; me.joinedWaitingList = null; me.combinedCancellationTokenSource = null; } combinedCancellationTokenSource?.Dispose(); if (t.IsCanceled) { for (int i = 0; i < allWaitingTasks.Count; i++) { WaitingCancellationStatus status = allWaitingTasks[i]; if (status.CancellationToken.IsCancellationRequested) { status.TrySetCanceled(status.CancellationToken); } else { status.TrySetCanceled(); } status.Dispose(); } } else if (t.IsFaulted) { System.Collections.ObjectModel.ReadOnlyCollection <Exception> exceptions = t.Exception !.InnerExceptions; for (int i = 0; i < allWaitingTasks.Count; i++) { WaitingCancellationStatus status = allWaitingTasks[i]; status.TrySetException(exceptions); status.Dispose(); } } else { for (int i = 0; i < allWaitingTasks.Count; i++) { WaitingCancellationStatus status = allWaitingTasks[i]; status.TrySetResult(true); status.Dispose(); } } }, this, CancellationToken.None, TaskContinuationOptions.RunContinuationsAsynchronously, TaskScheduler.Default).Forget(); } }