in src/Microsoft.VisualStudio.Threading/CancellableJoinComputation.cs [147:254]
internal bool TryJoinComputation(bool isInitialTask, [NotNullWhen(true)] out Task? task, CancellationToken cancellationToken)
{
if (!this.isCancellationAllowed)
{
task = this.JoinNotCancellableTaskAsync(isInitialTask, cancellationToken);
return true;
}
if (cancellationToken.IsCancellationRequested)
{
if (isInitialTask)
{
// It is a corner case the cancellation token is triggered right after the first task starts. It may need cancel the inner task.
CancellationTokenSource? cancellationTokenSource = null;
lock (this.syncObject)
{
if (this.isCancellationAllowed && this.outstandingWaitingCount == 0 && this.combinedCancellationTokenSource is not null)
{
this.isCancellationRequested = true;
cancellationTokenSource = this.combinedCancellationTokenSource;
this.combinedCancellationTokenSource = null;
}
}
if (cancellationTokenSource is not null)
{
cancellationTokenSource.Cancel();
cancellationTokenSource.Dispose();
}
task = this.InnerTask;
return true;
}
else
{
task = Task.FromCanceled(cancellationToken);
return true;
}
}
// if the inner task is joined by a new uncancellable task, we will abandone the cancellation token source because we will never use it anymore.
// we do it outside of our lock.
CancellationTokenSource? combinedCancellationTokenSourceToDispose = null;
try
{
lock (this.syncObject)
{
if (this.isCancellationRequested)
{
// If the earlier computation is aborted, we cannot join it anymore.
task = null;
return false;
}
if (this.InnerTask.IsCompleted)
{
task = this.InnerTask;
return true;
}
if (!cancellationToken.CanBeCanceled)
{
// A single joined client which doesn't allow cancellation would turn the entire computation not cancellable.
combinedCancellationTokenSourceToDispose = this.combinedCancellationTokenSource;
this.combinedCancellationTokenSource = null;
this.isCancellationAllowed = false;
task = this.JoinNotCancellableTaskAsync(isInitialTask, CancellationToken.None);
}
else if (!this.isCancellationAllowed)
{
task = this.JoinNotCancellableTaskAsync(isInitialTask, cancellationToken);
}
else
{
Assumes.NotNull(this.joinedWaitingList);
WaitingCancellationStatus status;
// we need increase the outstanding count before creating WiatingCancellationStatus.
// Under a rare race condition the cancellation token can be trigger with this time frame, and lead OnWaitingTaskCancelled to be called recursively
// within this lock. It would be critical to make sure the outstandingWaitingCount to increase before decreasing there.
this.outstandingWaitingCount++;
try
{
status = new WaitingCancellationStatus(this, cancellationToken);
}
catch
{
this.outstandingWaitingCount--;
throw;
}
this.joinedWaitingList.Add(status);
task = status.Task;
}
}
}
finally
{
combinedCancellationTokenSourceToDispose?.Dispose();
}
return true;
}