| File: Utils\CancellationSeries.cs | Web Access |
| Project: src\src\Aspire.Dashboard\Aspire.Dashboard.csproj (Aspire.Dashboard) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. namespace Aspire.Dashboard.Utils; /// <summary> /// Produces a series of <see cref="CancellationToken"/>s that are used to coordinate /// cancellation of non-overlapping operations in a concurrent environment. /// </summary> /// <remarks> /// The class produces a stream of cancellation tokens, where each represents the lifetime of an operation that /// started when <see cref="NextAsync"/> was called. Each call of that method cancels the last token it issued, then returns a new one. /// In that way, you can set up operations with cancellation and no overlap. /// The effectiveness of this approach depends upon good support for cancellation in operations under that token. /// It's important that any await can be promptly cancelled. /// </remarks> internal sealed class CancellationSeries { private CancellationTokenSource? _cts; /// <summary> /// Produces the next token, cancelling the one before. /// </summary> /// <remarks> /// The returned token represents the lifetime of an operation, in a concurrent environment where operations will not overlap. /// This method cancels token it returned the last time it was called. In that way, you can set up operations with cancellation and no overlap. /// The effectiveness of this approach depends upon good support for cancellation in operations under that token. /// It's important that any await can be promptly cancelled. /// </remarks> /// <returns>A cancellation token that manages the lifetime of a non-overlapping operation in a concurrent environment.</returns> public async Task<CancellationToken> NextAsync() { var nextCts = new CancellationTokenSource(); // Obtain the token before exchange, as otherwise the CTS may be cancelled before // we request the Token, which will result in an ObjectDisposedException. // This way we would return a cancelled token, which is reasonable. var nextToken = nextCts.Token; await Next(nextCts).ConfigureAwait(false); return nextToken; } /// <summary> /// Cancels the current <see cref="CancellationToken"/> but doesn't create a new one. /// </summary> /// <remarks> /// Multiple calls to this method will be no-ops until <see cref="NextAsync"/> is called, /// at which point there's something to cancel. /// </remarks> /// <returns></returns> public Task ClearAsync() { return Next(null); } private async Task Next(CancellationTokenSource? next) { using var priorCts = Interlocked.Exchange(ref _cts, next); if (priorCts is not null) { await priorCts.CancelAsync().ConfigureAwait(false); } } }