public void UnlockCompetition(CancellationToken cancellationToken) { CancellationTokenRegistration registration = cancellationToken.Register( state => { ExclusiveCompletionSourceGroup <T> group = state as ExclusiveCompletionSourceGroup <T>; /// There are 2 cases here. /// /// #1: The token is canceled before <see cref="UnlockCompetition(CancellationToken)"/> is called, but after the token is validated higher up the stack. /// Is this is the case, the cancellation callbak will be called synchronously while <see cref="_completedSource"/> is still set to <see cref="State.Locked"/>. /// So the competition will never progress to <see cref="State.Unlocked"/> and we have to check for this explicitly. /// /// #2: We're canceled after the competition has been unlocked. /// If this is the case, we have a simple race against the awaiters to progress from <see cref="State.Unlocked"/> to <see cref="State.Canceled"/>. if (group.TryTransitionToCanceledIfStateIs(State.Locked) || group.TryTransitionToCanceledIfStateIs(State.Unlocked)) { group._realCompetionSource.SetCanceled(); } }, this, useSynchronizationContext: false); // We can't do volatile reads/writes on a custom value type field, so we have to wrap the registration into a holder instance. // But there's no point in allocating the wrapper if the token can never be canceled. if (cancellationToken.CanBeCanceled) { Volatile.Write(ref _cancellationRegistrationHolder, new CancellationRegistrationHolder(registration)); } // If the cancellation was processed synchronously, the state will already be set to Canceled and we must *NOT* unlock the competition. Interlocked.CompareExchange(ref _completedSource, State.Unlocked, State.Locked); }
/// <summary> /// Removes and returns an item from one of the specified collections in an asynchronous manner. /// </summary> public static Task <AnyResult <TakeResult <T> > > TakeFromAnyAsync(AsyncCollection <T>[] collections, CancellationToken cancellationToken) { if (collections == null) { throw new ArgumentNullException(nameof(collections)); } if (collections.Length <= 0 || collections.Length > TakeFromAnyMaxCollections) { throw new ArgumentException( $"The collection array can't contain less than 1 or more than {TakeFromAnyMaxCollections} collections.", nameof(collections)); } if (cancellationToken.IsCancellationRequested) { return(Task.FromCanceled <AnyResult <TakeResult <T> > >(cancellationToken)); } var exclusiveSources = new ExclusiveCompletionSourceGroup <TakeResult <T> >(); // Fast route: we attempt to take from the top-priority queues that have any items. // If the fast route succeeds, we avoid allocating and queueing a bunch of awaiters. for (var i = 0; i < collections.Length; i++) { if (collections[i].Count > 0) { var result = TryTakeFast(exclusiveSources, collections[i], i); if (result.HasValue) { return(Task.FromResult(result.Value)); } } } // No luck during the fast route; just queue the rest of awaiters. for (var i = 0; i < collections.Length; i++) { var result = TryTakeFast(exclusiveSources, collections[i], i); if (result.HasValue) { return(Task.FromResult(result.Value)); } } // None of the collections had any items. The order doesn't matter anymore, it's time to start the competition. exclusiveSources.UnlockCompetition(cancellationToken); return(exclusiveSources.Task); }
private static AnyResult <TakeResult <T> >?TryTakeFast(ExclusiveCompletionSourceGroup <TakeResult <T> > exclusiveSources, AsyncCollection <T> collection, int index) { // This can happen if the awaiter has already been created during the fast route. if (exclusiveSources.IsAwaiterCreated(index)) { return(null); } var collectionTask = collection.TakeAsync(exclusiveSources.CreateAwaiterFactory(index)); // One of the collections already had an item and returned it directly if (collectionTask != null && collectionTask.IsCompleted) { exclusiveSources.MarkAsResolved(); return(new AnyResult <TakeResult <T> >(collectionTask.Result, index)); } return(null); }
public Factory(ExclusiveCompletionSourceGroup <T> group, int index) { _group = group; _index = index; }
public ExclusiveCompletionSource(ExclusiveCompletionSourceGroup <T> group, int id) { _group = group; _id = id; }