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); }
public Factory(ExclusiveCompletionSourceGroup <T> group, int index) { _group = group; _index = index; }
public ExclusiveCompletionSource(ExclusiveCompletionSourceGroup <T> group, int id) { _group = group; _id = id; }