public bool SignalAndWait(int millisecondsTimeout, CancellationToken cancellationToken)
{
ThrowIfDisposed();
cancellationToken.ThrowIfCancellationRequested();
if (millisecondsTimeout < -1)
{
throw new System.ArgumentOutOfRangeException(nameof(millisecondsTimeout), millisecondsTimeout,
SR.Barrier_SignalAndWait_ArgumentOutOfRange);
}
// in case of this is called from the PHA
if (_actionCallerID != 0 && Environment.CurrentManagedThreadId == _actionCallerID)
{
throw new InvalidOperationException(SR.Barrier_InvalidOperation_CalledFromPHA);
}
// local variables to extract the basic barrier variable and update them
// The are declared here instead of inside the loop body because the will be used outside the loop
bool sense; // The sense of the barrier *before* the phase associated with this SignalAndWait call completes
int total;
int current;
int currentTotal;
long phase;
SpinWait spinner = new SpinWait();
while (true)
{
currentTotal = _currentTotalCount;
GetCurrentTotal(currentTotal, out current, out total, out sense);
phase = CurrentPhaseNumber;
// throw if zero participants
if (total == 0)
{
throw new InvalidOperationException(SR.Barrier_SignalAndWait_InvalidOperation_ZeroTotal);
}
// Try to detect if the number of threads for this phase exceeded the total number of participants or not
// This can be detected if the current is zero which means all participants for that phase has arrived and the phase number is not changed yet
if (current == 0 && sense != (CurrentPhaseNumber % 2 == 0))
{
throw new InvalidOperationException(SR.Barrier_SignalAndWait_InvalidOperation_ThreadsExceeded);
}
//This is the last thread, finish the phase
if (current + 1 == total)
{
if (SetCurrentTotal(currentTotal, 0, total, !sense))
{
if (CdsSyncEtwBCLProvider.Log.IsEnabled())
{
CdsSyncEtwBCLProvider.Log.Barrier_PhaseFinished(sense, CurrentPhaseNumber);
}
FinishPhase(sense);
return true;
}
}
else if (SetCurrentTotal(currentTotal, current + 1, total, sense))
{
break;
}
spinner.SpinOnce();
}
// ** Perform the real wait **
// select the correct event to wait on, based on the current sense.
ManualResetEventSlim eventToWaitOn = (sense) ? _evenEvent : _oddEvent;
bool waitWasCanceled = false;
bool waitResult = false;
try
{
waitResult = DiscontinuousWait(eventToWaitOn, millisecondsTimeout, cancellationToken, phase);
}
catch (OperationCanceledException)
{
waitWasCanceled = true;
}
catch (ObjectDisposedException)// in case a race happen where one of the thread returned from SignalAndWait and the current thread calls Wait on a disposed event
{
// make sure the current phase for this thread is already finished, otherwise propagate the exception
if (phase < CurrentPhaseNumber)
waitResult = true;
else
throw;
}
if (!waitResult)
{
//reset the spinLock to prepare it for the next loop
spinner.Reset();
//If the wait timeout expired and all other thread didn't reach the barrier yet, update the current count back
while (true)
{
bool newSense;
currentTotal = _currentTotalCount;
GetCurrentTotal(currentTotal, out current, out total, out newSense);
// If the timeout expired and the phase has just finished, return true and this is considered as succeeded SignalAndWait
//otherwise the timeout expired and the current phase has not been finished yet, return false
//The phase is finished if the phase member variable is changed (incremented) or the sense has been changed
// we have to use the statements in the comparison below for two cases:
// 1- The sense is changed but the last thread didn't update the phase yet
// 2- The phase is already incremented but the sense flipped twice due to the termination of the next phase
if (phase < CurrentPhaseNumber || sense != newSense)
{
// The current phase has been finished, but we shouldn't return before the events are set/reset otherwise this thread could start
// next phase and the appropriate event has not reset yet which could make it return immediately from the next phase SignalAndWait
// before waiting other threads
WaitCurrentPhase(eventToWaitOn, phase);
Debug.Assert(phase < CurrentPhaseNumber);
break;
}
//The phase has not been finished yet, try to update the current count.
if (SetCurrentTotal(currentTotal, current - 1, total, sense))
{
//if here, then the attempt to backout was successful.
//throw (a fresh) oce if cancellation woke the wait
//or return false if it was the timeout that woke the wait.
//
if (waitWasCanceled)
throw new OperationCanceledException(SR.Common_OperationCanceled, cancellationToken);
else
return false;
}
spinner.SpinOnce();
}
}
if (_exception != null)
throw new BarrierPostPhaseException(_exception);
return true;
}