You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
951 lines
44 KiB
951 lines
44 KiB
#if NET20 || NET30 || NET35 || !NET_4_6 |
|
|
|
// Licensed to the .NET Foundation under one or more agreements. |
|
// The .NET Foundation licenses this file to you under the MIT license. |
|
// See the LICENSE file in the project root for more information. |
|
|
|
using System.Diagnostics; |
|
using System.Runtime.Serialization; |
|
using System.Security; |
|
using LinqInternal.Core; |
|
|
|
namespace System.Threading |
|
{ |
|
/// <summary> |
|
/// The exception that is thrown when the post-phase action of a <see cref="Barrier"/> fails. |
|
/// </summary> |
|
[Serializable] |
|
public class BarrierPostPhaseException : Exception |
|
{ |
|
/// <summary> |
|
/// Initializes a new instance of the <see cref="BarrierPostPhaseException"/> class. |
|
/// </summary> |
|
public BarrierPostPhaseException() |
|
: this((string)null) |
|
{ |
|
} |
|
|
|
/// <summary> |
|
/// Initializes a new instance of the <see cref="BarrierPostPhaseException"/> class with the specified inner exception. |
|
/// </summary> |
|
/// <param name="innerException">The exception that is the cause of the current exception.</param> |
|
public BarrierPostPhaseException(Exception innerException) |
|
: this(null, innerException) |
|
{ |
|
} |
|
|
|
/// <summary> |
|
/// Initializes a new instance of the <see cref="BarrierPostPhaseException"/> class with a specified error message. |
|
/// </summary> |
|
/// <param name="message">A string that describes the exception.</param> |
|
public BarrierPostPhaseException(string message) |
|
: this(message, null) |
|
{ |
|
} |
|
|
|
/// <summary> |
|
/// Initializes a new instance of the <see cref="BarrierPostPhaseException"/> class with a specified error message and inner exception. |
|
/// </summary> |
|
/// <param name="message">A string that describes the exception.</param> |
|
/// <param name="innerException">The exception that is the cause of the current exception.</param> |
|
public BarrierPostPhaseException(string message, Exception innerException) |
|
: base(message ?? "The postPhaseAction failed with an exception.", innerException) |
|
{ |
|
} |
|
|
|
/// <summary> |
|
/// Initializes a new instance of the BarrierPostPhaseException class with serialized data. |
|
/// </summary> |
|
/// <param name="info">The object that holds the serialized object data.</param> |
|
/// <param name="context">The contextual information about the source or destination.</param> |
|
protected BarrierPostPhaseException(SerializationInfo info, StreamingContext context) |
|
: base(info, context) |
|
{ |
|
} |
|
} |
|
|
|
/// <summary> |
|
/// Enables multiple tasks to cooperatively work on an algorithm in parallel through multiple phases. |
|
/// </summary> |
|
/// <remarks> |
|
/// <para> |
|
/// A group of tasks cooperate by moving through a series of phases, where each in the group signals it |
|
/// has arrived at the <see cref="Barrier"/> in a given phase and implicitly waits for all others to |
|
/// arrive. The same <see cref="Barrier"/> can be used for multiple phases. |
|
/// </para> |
|
/// <para> |
|
/// All public and protected members of <see cref="Barrier"/> are thread-safe and may be used |
|
/// concurrently from multiple threads, with the exception of Dispose, which |
|
/// must only be used when all other operations on the <see cref="Barrier"/> have |
|
/// completed. |
|
/// </para> |
|
/// </remarks> |
|
[DebuggerDisplay("Participant Count={ParticipantCount},Participants Remaining={ParticipantsRemaining}")] |
|
public class Barrier : IDisposable |
|
{ |
|
//This variable holds the basic barrier variables: |
|
// 1- The current participants count |
|
// 2- The total participants count |
|
// 3- The sense flag (true if the current phase is even, false otherwise) |
|
// The first 15 bits are for the total count which means the maximum participants for the barrier is about 32K |
|
// The 16th bit is dummy |
|
// The next 15th bit for the current |
|
// And the last highest bit is for the sense |
|
private int _currentTotalCount; |
|
|
|
// Bitmask to extract the current count |
|
private const int _currentMask = 0x7FFF0000; |
|
|
|
// Bitmask to extract the total count |
|
private const int _totalMask = 0x00007FFF; |
|
|
|
// Bitmask to extract the sense flag |
|
private const int _senseMask = unchecked((int)0x80000000); |
|
|
|
// The maximum participants the barrier can operate = 32767 ( 2 power 15 - 1 ) |
|
private const int _maxParticipants = _totalMask; |
|
|
|
// The current barrier phase |
|
// We don't need to worry about overflow, the max value is 2^63-1; If it starts from 0 at a |
|
// rate of 4 billion increments per second, it will takes about 64 years to overflow. |
|
private long _currentPhase; |
|
|
|
// dispose flag |
|
private bool _disposed; |
|
|
|
// Odd phases event |
|
private readonly ManualResetEventSlim _oddEvent; |
|
|
|
// Even phases event |
|
private readonly ManualResetEventSlim _evenEvent; |
|
|
|
// The execution context of the creator thread |
|
private readonly ExecutionContext _ownerThreadContext; |
|
|
|
// The EC callback that invokes the post phase action |
|
[SecurityCritical] |
|
private static ContextCallback _invokePostPhaseAction; |
|
|
|
// Post phase action after each phase |
|
private readonly Action<Barrier> _postPhaseAction; |
|
|
|
// In case the post phase action throws an exception, wraps it in BarrierPostPhaseException |
|
private Exception _exception; |
|
|
|
// This is the ManagedThreadID of the postPhaseAction caller thread, this is used to determine if the SignalAndWait, Dispose or Add/RemoveParticipant caller thread is |
|
// the same thread as the postPhaseAction thread which means this method was called from the postPhaseAction which is illegal. |
|
// This value is captured before calling the action and reset back to zero after it. |
|
private int _actionCallerId; |
|
|
|
#region Properties |
|
|
|
/// <summary> |
|
/// Gets the number of participants in the barrier that haven't yet signaled |
|
/// in the current phase. |
|
/// </summary> |
|
/// <remarks> |
|
/// This could be 0 during a post-phase action delegate execution or if the |
|
/// ParticipantCount is 0. |
|
/// </remarks> |
|
public int ParticipantsRemaining |
|
{ |
|
get |
|
{ |
|
var currentTotal = Volatile.Read(ref _currentTotalCount); |
|
var total = currentTotal & _totalMask; |
|
var current = (currentTotal & _currentMask) >> 16; |
|
return total - current; |
|
} |
|
} |
|
|
|
/// <summary> |
|
/// Gets the total number of participants in the barrier. |
|
/// </summary> |
|
public int ParticipantCount |
|
{ |
|
get { return Volatile.Read(ref _currentTotalCount) & _totalMask; } |
|
} |
|
|
|
/// <summary> |
|
/// Gets the number of the barrier's current phase. |
|
/// </summary> |
|
public long CurrentPhaseNumber |
|
{ |
|
// use the new Volatile.Read/Write method because it is cheaper than Interlocked.Read on AMD64 architecture |
|
get { return Volatile.Read(ref _currentPhase); } |
|
|
|
internal set { Volatile.Write(ref _currentPhase, value); } |
|
} |
|
|
|
#endregion Properties |
|
|
|
/// <summary> |
|
/// Initializes a new instance of the <see cref="Barrier"/> class. |
|
/// </summary> |
|
/// <param name="participantCount">The number of participating threads.</param> |
|
/// <exception cref="ArgumentOutOfRangeException"> <paramref name="participantCount"/> is less than 0 |
|
/// or greater than <see cref="T:System.Int16.MaxValue"/>.</exception> |
|
public Barrier(int participantCount) |
|
: this(participantCount, null) |
|
{ |
|
} |
|
|
|
/// <summary> |
|
/// Initializes a new instance of the <see cref="Barrier"/> class. |
|
/// </summary> |
|
/// <param name="participantCount">The number of participating threads.</param> |
|
/// <param name="postPhaseAction">The <see cref="T:System.Action`1"/> to be executed after each |
|
/// phase.</param> |
|
/// <exception cref="T:System.ArgumentOutOfRangeException"> <paramref name="participantCount"/> is less than 0 |
|
/// or greater than <see cref="T:System.Int32.MaxValue"/>.</exception> |
|
/// <remarks> |
|
/// The <paramref name="postPhaseAction"/> delegate will be executed after |
|
/// all participants have arrived at the barrier in one phase. The participants |
|
/// will not be released to the next phase until the postPhaseAction delegate |
|
/// has completed execution. |
|
/// </remarks> |
|
public Barrier(int participantCount, Action<Barrier> postPhaseAction) |
|
{ |
|
// the count must be non negative value |
|
if (participantCount < 0 || participantCount > _maxParticipants) |
|
{ |
|
throw new ArgumentOutOfRangeException("participantCount", participantCount, "The participantCount argument must be non-negative and less than or equal to 32767"); |
|
} |
|
_currentTotalCount = participantCount; |
|
_postPhaseAction = postPhaseAction; |
|
|
|
//Lazily initialize the events |
|
_oddEvent = new ManualResetEventSlim(true); |
|
_evenEvent = new ManualResetEventSlim(false); |
|
|
|
// Capture the context if the post phase action is not null |
|
if (postPhaseAction != null) |
|
{ |
|
_ownerThreadContext = ExecutionContext.Capture(); |
|
} |
|
|
|
_actionCallerId = 0; |
|
} |
|
|
|
/// <summary> |
|
/// Extract the three variables current, total and sense from a given big variable |
|
/// </summary> |
|
/// <param name="currentTotal">The integer variable that contains the other three variables</param> |
|
/// <param name="current">The current participant count</param> |
|
/// <param name="total">The total participants count</param> |
|
/// <param name="sense">The sense flag</param> |
|
private void GetCurrentTotal(int currentTotal, out int current, out int total, out bool sense) |
|
{ |
|
total = currentTotal & _totalMask; |
|
current = (currentTotal & _currentMask) >> 16; |
|
sense = (currentTotal & _senseMask) == 0; |
|
} |
|
|
|
/// <summary> |
|
/// Write the three variables current. total and the sense to the m_currentTotal |
|
/// </summary> |
|
/// <param name="currentTotal">The old current total to compare</param> |
|
/// <param name="current">The current participant count</param> |
|
/// <param name="total">The total participants count</param> |
|
/// <param name="sense">The sense flag</param> |
|
/// <returns>True if the CAS succeeded, false otherwise</returns> |
|
private bool SetCurrentTotal(int currentTotal, int current, int total, bool sense) |
|
{ |
|
var newCurrentTotal = (current << 16) | total; |
|
|
|
if (!sense) |
|
{ |
|
newCurrentTotal |= _senseMask; |
|
} |
|
|
|
return Interlocked.CompareExchange(ref _currentTotalCount, newCurrentTotal, currentTotal) == currentTotal; |
|
} |
|
|
|
/// <summary> |
|
/// Notifies the <see cref="Barrier"/> that there will be an additional participant. |
|
/// </summary> |
|
/// <returns>The phase number of the barrier in which the new participants will first |
|
/// participate.</returns> |
|
/// <exception cref="T:System.InvalidOperationException"> |
|
/// Adding a participant would cause the barrier's participant count to |
|
/// exceed <see cref="T:System.Int16.MaxValue"/>. |
|
/// </exception> |
|
/// <exception cref="T:System.InvalidOperationException"> |
|
/// The method was invoked from within a post-phase action. |
|
/// </exception> |
|
/// <exception cref="T:System.ObjectDisposedException">The current instance has already been |
|
/// disposed.</exception> |
|
public long AddParticipant() |
|
{ |
|
try |
|
{ |
|
return AddParticipants(1); |
|
} |
|
catch (ArgumentOutOfRangeException) |
|
{ |
|
throw new InvalidOperationException("Adding participantCount participants would result in the number of participants exceeding the maximum number allowed."); |
|
} |
|
} |
|
|
|
/// <summary> |
|
/// Notifies the <see cref="Barrier"/> that there will be additional participants. |
|
/// </summary> |
|
/// <param name="participantCount">The number of additional participants to add to the |
|
/// barrier.</param> |
|
/// <returns>The phase number of the barrier in which the new participants will first |
|
/// participate.</returns> |
|
/// <exception cref="T:System.ArgumentOutOfRangeException"><paramref name="participantCount"/> is less than |
|
/// 0.</exception> |
|
/// <exception cref="T:System.ArgumentOutOfRangeException">Adding <paramref name="participantCount"/> participants would cause the |
|
/// barrier's participant count to exceed <see cref="T:System.Int16.MaxValue"/>.</exception> |
|
/// <exception cref="T:System.InvalidOperationException"> |
|
/// The method was invoked from within a post-phase action. |
|
/// </exception> |
|
/// <exception cref="T:System.ObjectDisposedException">The current instance has already been |
|
/// disposed.</exception> |
|
public long AddParticipants(int participantCount) |
|
{ |
|
// check dispose |
|
ThrowIfDisposed(); |
|
|
|
if (participantCount < 1) |
|
{ |
|
throw new ArgumentOutOfRangeException("participantCount", participantCount, |
|
"The participantCount argument must be a positive value."); |
|
} |
|
else if (participantCount > _maxParticipants) //overflow |
|
{ |
|
throw new ArgumentOutOfRangeException("participantCount", |
|
"Adding participantCount participants would result in the number of participants exceeding the maximum number allowed."); |
|
} |
|
|
|
// in case of this is called from the PHA |
|
if (_actionCallerId != 0 && Thread.CurrentThread.ManagedThreadId == _actionCallerId) |
|
{ |
|
throw new InvalidOperationException("This method may not be called from within the postPhaseAction."); |
|
} |
|
|
|
var spinner = new SpinWait(); |
|
long newPhase; |
|
while (true) |
|
{ |
|
var currentTotal = Volatile.Read(ref _currentTotalCount); |
|
int total; |
|
int current; |
|
bool sense; |
|
GetCurrentTotal(currentTotal, out current, out total, out sense); |
|
if (participantCount + total > _maxParticipants) //overflow |
|
{ |
|
throw new ArgumentOutOfRangeException("participantCount", |
|
"Adding participantCount participants would result in the number of participants exceeding the maximum number allowed."); |
|
} |
|
|
|
if (SetCurrentTotal(currentTotal, current, total + participantCount, sense)) |
|
{ |
|
// Calculating the first phase for that participant, if the current phase already finished return the next phase else return the current phase |
|
// To know that the current phase is the sense doesn't match the |
|
// phase odd even, so that means it didn't yet change the phase count, so currentPhase +1 is returned, otherwise currentPhase is returned |
|
var currPhase = CurrentPhaseNumber; |
|
newPhase = (sense != (currPhase % 2 == 0)) ? currPhase + 1 : currPhase; |
|
|
|
// If this participant is going to join the next phase, which means the postPhaseAction is being running, this participants must wait until this done |
|
// and its event is reset. |
|
// Without that, if the postPhaseAction takes long time, this means the event that the current participant is going to wait on is still set |
|
// (FinishPhase didn't reset it yet) so it should wait until it reset |
|
if (newPhase != currPhase) |
|
{ |
|
// Wait on the opposite event |
|
if (sense) |
|
{ |
|
_oddEvent.Wait(); |
|
} |
|
else |
|
{ |
|
_evenEvent.Wait(); |
|
} |
|
} |
|
|
|
//This else to fix the racing where the current phase has been finished, m_currentPhase has been updated but the events have not been set/reset yet |
|
// otherwise when this participant calls SignalAndWait it will wait on a set event however all other participants have not arrived yet. |
|
else |
|
{ |
|
if (sense && _evenEvent.IsSet) |
|
{ |
|
_evenEvent.Reset(); |
|
} |
|
else if (!sense && _oddEvent.IsSet) |
|
{ |
|
_oddEvent.Reset(); |
|
} |
|
} |
|
break; |
|
} |
|
spinner.SpinOnce(); |
|
} |
|
return newPhase; |
|
} |
|
|
|
/// <summary> |
|
/// Notifies the <see cref="Barrier"/> that there will be one less participant. |
|
/// </summary> |
|
/// <exception cref="T:System.InvalidOperationException">The barrier already has 0 |
|
/// participants.</exception> |
|
/// <exception cref="T:System.InvalidOperationException"> |
|
/// The method was invoked from within a post-phase action. |
|
/// </exception> |
|
/// <exception cref="T:System.ObjectDisposedException">The current instance has already been |
|
/// disposed.</exception> |
|
public void RemoveParticipant() |
|
{ |
|
RemoveParticipants(1); |
|
} |
|
|
|
/// <summary> |
|
/// Notifies the <see cref="Barrier"/> that there will be fewer participants. |
|
/// </summary> |
|
/// <param name="participantCount">The number of additional participants to remove from the barrier.</param> |
|
/// <exception cref="T:System.ArgumentOutOfRangeException"><paramref name="participantCount"/> is less than |
|
/// 0.</exception> |
|
/// <exception cref="T:System.InvalidOperationException">The barrier already has 0 participants.</exception> |
|
/// <exception cref="T:System.InvalidOperationException"> |
|
/// The method was invoked from within a post-phase action. |
|
/// </exception> |
|
/// <exception cref="T:System.ObjectDisposedException">The current instance has already been |
|
/// disposed.</exception> |
|
public void RemoveParticipants(int participantCount) |
|
{ |
|
// check dispose |
|
ThrowIfDisposed(); |
|
|
|
// Validate input |
|
if (participantCount < 1) |
|
{ |
|
throw new ArgumentOutOfRangeException("participantCount", participantCount, |
|
"The participantCount argument must be a positive value."); |
|
} |
|
|
|
// in case of this is called from the PHA |
|
if (_actionCallerId != 0 && Thread.CurrentThread.ManagedThreadId == _actionCallerId) |
|
{ |
|
throw new InvalidOperationException("This method may not be called from within the postPhaseAction."); |
|
} |
|
|
|
var spinner = new SpinWait(); |
|
while (true) |
|
{ |
|
var currentTotal = Volatile.Read(ref _currentTotalCount); |
|
int total; |
|
int current; |
|
bool sense; |
|
GetCurrentTotal(currentTotal, out current, out total, out sense); |
|
|
|
if (total < participantCount) |
|
{ |
|
throw new ArgumentOutOfRangeException("participantCount", |
|
"The participantCount argument must be less than or equal the number of participants."); |
|
} |
|
if (total - participantCount < current) |
|
{ |
|
throw new InvalidOperationException("The participantCount argument is greater than the number of participants that haven't yet arrived at the barrier in this phase."); |
|
} |
|
// If the remaining participants = current participants, then finish the current phase |
|
var remaingParticipants = total - participantCount; |
|
if (remaingParticipants > 0 && current == remaingParticipants) |
|
{ |
|
if (SetCurrentTotal(currentTotal, 0, total - participantCount, !sense)) |
|
{ |
|
FinishPhase(sense); |
|
break; |
|
} |
|
} |
|
else |
|
{ |
|
if (SetCurrentTotal(currentTotal, current, total - participantCount, sense)) |
|
{ |
|
break; |
|
} |
|
} |
|
spinner.SpinOnce(); |
|
} |
|
} |
|
|
|
/// <summary> |
|
/// Signals that a participant has reached the <see cref="Barrier"/> and waits for all other |
|
/// participants to reach the barrier as well. |
|
/// </summary> |
|
/// <exception cref="T:System.InvalidOperationException"> |
|
/// The method was invoked from within a post-phase action, the barrier currently has 0 participants, |
|
/// or the barrier is being used by more threads than are registered as participants. |
|
/// </exception> |
|
/// <exception cref="T:System.ObjectDisposedException">The current instance has already been |
|
/// disposed.</exception> |
|
public void SignalAndWait() |
|
{ |
|
SignalAndWait(new CancellationToken()); |
|
} |
|
|
|
/// <summary> |
|
/// Signals that a participant has reached the <see cref="Barrier"/> and waits for all other |
|
/// participants to reach the barrier, while observing a <see cref="T:System.Threading.CancellationToken"/>. |
|
/// </summary> |
|
/// <param name="cancellationToken">The <see cref="T:System.Threading.CancellationToken"/> to |
|
/// observe.</param> |
|
/// <exception cref="T:System.InvalidOperationException"> |
|
/// The method was invoked from within a post-phase action, the barrier currently has 0 participants, |
|
/// or the barrier is being used by more threads than are registered as participants. |
|
/// </exception> |
|
/// <exception cref="T:System.OperationCanceledException"><paramref name="cancellationToken"/> has been |
|
/// canceled.</exception> |
|
/// <exception cref="T:System.ObjectDisposedException">The current instance has already been |
|
/// disposed.</exception> |
|
public void SignalAndWait(CancellationToken cancellationToken) |
|
{ |
|
#if DEBUG |
|
var result = |
|
#endif |
|
SignalAndWait(Timeout.Infinite, cancellationToken); |
|
#if DEBUG |
|
Debug.Assert(result); |
|
#endif |
|
} |
|
|
|
/// <summary> |
|
/// Signals that a participant has reached the <see cref="Barrier"/> and waits for all other |
|
/// participants to reach the barrier as well, using a |
|
/// <see cref="T:System.TimeSpan"/> to measure the time interval. |
|
/// </summary> |
|
/// <param name="timeout">A <see cref="T:System.TimeSpan"/> that represents the number of |
|
/// milliseconds to wait, or a <see cref="T:System.TimeSpan"/> that represents -1 milliseconds to |
|
/// wait indefinitely.</param> |
|
/// <returns>true if all other participants reached the barrier; otherwise, false.</returns> |
|
/// <exception cref="T:System.ArgumentOutOfRangeException"><paramref name="timeout"/>is a negative number |
|
/// other than -1 milliseconds, which represents an infinite time-out, or it is greater than |
|
/// <see cref="T:System.Int32.MaxValue"/>.</exception> |
|
/// <exception cref="T:System.InvalidOperationException"> |
|
/// The method was invoked from within a post-phase action, the barrier currently has 0 participants, |
|
/// or the barrier is being used by more threads than are registered as participants. |
|
/// </exception> |
|
/// <exception cref="T:System.ObjectDisposedException">The current instance has already been |
|
/// disposed.</exception> |
|
public bool SignalAndWait(TimeSpan timeout) |
|
{ |
|
return SignalAndWait(timeout, new CancellationToken()); |
|
} |
|
|
|
/// <summary> |
|
/// Signals that a participant has reached the <see cref="Barrier"/> and waits for all other |
|
/// participants to reach the barrier as well, using a |
|
/// <see cref="T:System.TimeSpan"/> to measure the time interval, while observing a <see cref="T:System.Threading.CancellationToken"/>. |
|
/// </summary> |
|
/// <param name="timeout">A <see cref="T:System.TimeSpan"/> that represents the number of |
|
/// milliseconds to wait, or a <see cref="T:System.TimeSpan"/> that represents -1 milliseconds to |
|
/// wait indefinitely.</param> |
|
/// <param name="cancellationToken">The <see cref="T:System.Threading.CancellationToken"/> to |
|
/// observe.</param> |
|
/// <returns>true if all other participants reached the barrier; otherwise, false.</returns> |
|
/// <exception cref="T:System.ArgumentOutOfRangeException"><paramref name="timeout"/>is a negative number |
|
/// other than -1 milliseconds, which represents an infinite time-out.</exception> |
|
/// <exception cref="T:System.InvalidOperationException"> |
|
/// The method was invoked from within a post-phase action, the barrier currently has 0 participants, |
|
/// or the barrier is being used by more threads than are registered as participants. |
|
/// </exception> |
|
/// <exception cref="T:System.OperationCanceledException"><paramref name="cancellationToken"/> has been |
|
/// canceled.</exception> |
|
/// <exception cref="T:System.ObjectDisposedException">The current instance has already been |
|
/// disposed.</exception> |
|
public bool SignalAndWait(TimeSpan timeout, CancellationToken cancellationToken) |
|
{ |
|
var totalMilliseconds = (long)timeout.TotalMilliseconds; |
|
if (totalMilliseconds < -1 || totalMilliseconds > int.MaxValue) |
|
{ |
|
throw new ArgumentOutOfRangeException("timeout", timeout, |
|
"The specified timeout must represent a value between -1 and Int32.MaxValue, inclusive."); |
|
} |
|
return SignalAndWait((int)timeout.TotalMilliseconds, cancellationToken); |
|
} |
|
|
|
/// <summary> |
|
/// Signals that a participant has reached the <see cref="Barrier"/> and waits for all other |
|
/// participants to reach the barrier as well, using a |
|
/// 32-bit signed integer to measure the time interval. |
|
/// </summary> |
|
/// <param name="millisecondsTimeout">The number of milliseconds to wait, or <see cref="Timeout.Infinite"/>(-1) to wait indefinitely.</param> |
|
/// <returns>true if all other participants reached the barrier; otherwise, false.</returns> |
|
/// <exception cref="T:System.ArgumentOutOfRangeException"><paramref name="millisecondsTimeout"/> is a |
|
/// negative number other than -1, which represents an infinite time-out.</exception> |
|
/// <exception cref="T:System.InvalidOperationException"> |
|
/// The method was invoked from within a post-phase action, the barrier currently has 0 participants, |
|
/// or the barrier is being used by more threads than are registered as participants. |
|
/// </exception> |
|
/// <exception cref="T:System.ObjectDisposedException">The current instance has already been |
|
/// disposed.</exception> |
|
public bool SignalAndWait(int millisecondsTimeout) |
|
{ |
|
return SignalAndWait(millisecondsTimeout, new CancellationToken()); |
|
} |
|
|
|
/// <summary> |
|
/// Signals that a participant has reached the barrier and waits for all other participants to reach |
|
/// the barrier as well, using a |
|
/// 32-bit signed integer to measure the time interval, while observing a <see cref="T:System.Threading.CancellationToken"/>. |
|
/// </summary> |
|
/// <param name="millisecondsTimeout">The number of milliseconds to wait, or <see cref="Timeout.Infinite"/>(-1) to wait indefinitely.</param> |
|
/// <param name="cancellationToken">The <see cref="T:System.Threading.CancellationToken"/> to |
|
/// observe.</param> |
|
/// <returns>true if all other participants reached the barrier; otherwise, false.</returns> |
|
/// <exception cref="T:System.ArgumentOutOfRangeException"><paramref name="millisecondsTimeout"/> is a |
|
/// negative number other than -1, which represents an infinite time-out.</exception> |
|
/// <exception cref="T:System.InvalidOperationException"> |
|
/// The method was invoked from within a post-phase action, the barrier currently has 0 participants, |
|
/// or the barrier is being used by more threads than are registered as participants. |
|
/// </exception> |
|
/// <exception cref="T:System.OperationCanceledException"><paramref name="cancellationToken"/> has been |
|
/// canceled.</exception> |
|
/// <exception cref="T:System.ObjectDisposedException">The current instance has already been |
|
/// disposed.</exception> |
|
public bool SignalAndWait(int millisecondsTimeout, CancellationToken cancellationToken) |
|
{ |
|
ThrowIfDisposed(); |
|
cancellationToken.ThrowIfCancellationRequested(); |
|
|
|
if (millisecondsTimeout < -1) |
|
{ |
|
throw new ArgumentOutOfRangeException("millisecondsTimeout", millisecondsTimeout, |
|
"The specified timeout must represent a value between -1 and Int32.MaxValue, inclusive."); |
|
} |
|
|
|
// in case of this is called from the PHA |
|
if (_actionCallerId != 0 && Thread.CurrentThread.ManagedThreadId == _actionCallerId) |
|
{ |
|
throw new InvalidOperationException("This method may not be called from within the postPhaseAction."); |
|
} |
|
|
|
// 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; |
|
var spinner = new SpinWait(); |
|
while (true) |
|
{ |
|
currentTotal = Volatile.Read(ref _currentTotalCount); |
|
GetCurrentTotal(currentTotal, out current, out total, out sense); |
|
phase = CurrentPhaseNumber; |
|
// throw if zero participants |
|
if (total == 0) |
|
{ |
|
throw new InvalidOperationException("The barrier has no registered participants."); |
|
} |
|
// 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("The number of threads using the barrier exceeded the total number of registered participants."); |
|
} |
|
//This is the last thread, finish the phase |
|
if (current + 1 == total) |
|
{ |
|
if (SetCurrentTotal(currentTotal, 0, total, !sense)) |
|
{ |
|
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. |
|
var eventToWaitOn = (sense) ? _evenEvent : _oddEvent; |
|
|
|
var waitWasCanceled = false; |
|
var 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 = Volatile.Read(ref _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 back out 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 NewOperationCanceledException("The operation was canceled.", cancellationToken); |
|
} |
|
else |
|
{ |
|
return false; |
|
} |
|
} |
|
spinner.SpinOnce(); |
|
} |
|
} |
|
|
|
if (_exception != null) |
|
{ |
|
throw new BarrierPostPhaseException(_exception); |
|
} |
|
|
|
return true; |
|
} |
|
|
|
/// <summary> |
|
/// Finish the phase by invoking the post phase action, and setting the event, this must be called by the |
|
/// last arrival thread |
|
/// </summary> |
|
/// <param name="observedSense">The current phase sense</param> |
|
[SecuritySafeCritical] |
|
private void FinishPhase(bool observedSense) |
|
{ |
|
// Execute the PHA in try/finally block to reset the variables back in case of it threw an exception |
|
if (_postPhaseAction != null) |
|
{ |
|
try |
|
{ |
|
// Capture the caller thread ID to check if the Add/RemoveParticipant(s) is called from the PHA |
|
_actionCallerId = Thread.CurrentThread.ManagedThreadId; |
|
if (_ownerThreadContext != null) |
|
{ |
|
GC.KeepAlive(_ownerThreadContext); |
|
|
|
var handler = _invokePostPhaseAction; |
|
if (handler == null) |
|
{ |
|
_invokePostPhaseAction = handler = InvokePostPhaseAction; |
|
} |
|
ExecutionContext.Run(_ownerThreadContext, handler, this); |
|
} |
|
else |
|
{ |
|
_postPhaseAction(this); |
|
} |
|
|
|
_exception = null; // reset the exception if it was set previously |
|
} |
|
catch (Exception ex) |
|
{ |
|
_exception = ex; |
|
} |
|
finally |
|
{ |
|
_actionCallerId = 0; |
|
SetResetEvents(observedSense); |
|
if (_exception != null) |
|
{ |
|
throw new BarrierPostPhaseException(_exception); |
|
} |
|
} |
|
} |
|
else |
|
{ |
|
SetResetEvents(observedSense); |
|
} |
|
} |
|
|
|
/// <summary> |
|
/// Helper method to call the post phase action |
|
/// </summary> |
|
/// <param name="obj"></param> |
|
[SecurityCritical] |
|
private static void InvokePostPhaseAction(object obj) |
|
{ |
|
var thisBarrier = (Barrier)obj; |
|
thisBarrier._postPhaseAction(thisBarrier); |
|
} |
|
|
|
/// <summary> |
|
/// Sets the current phase event and reset the next phase event |
|
/// </summary> |
|
/// <param name="observedSense">The current phase sense</param> |
|
private void SetResetEvents(bool observedSense) |
|
{ |
|
// Increment the phase count using Volatile class because m_currentPhase is 64 bit long type, that could cause torn write on 32 bit machines |
|
CurrentPhaseNumber = CurrentPhaseNumber + 1; |
|
if (observedSense) |
|
{ |
|
_oddEvent.Reset(); |
|
_evenEvent.Set(); |
|
} |
|
else |
|
{ |
|
_evenEvent.Reset(); |
|
_oddEvent.Set(); |
|
} |
|
} |
|
|
|
/// <summary> |
|
/// Wait until the current phase finishes completely by spinning until either the event is set, |
|
/// or the phase count is incremented more than one time |
|
/// </summary> |
|
/// <param name="currentPhaseEvent">The current phase event</param> |
|
/// <param name="observedPhase">The current phase for that thread</param> |
|
private void WaitCurrentPhase(ManualResetEventSlim currentPhaseEvent, long observedPhase) |
|
{ |
|
//spin until either of these two conditions succeeds |
|
//1- The event is set |
|
//2- the phase count is incremented more than one time, this means the next phase is finished as well, |
|
//but the event will be reset again, so we check the phase count instead |
|
var spinner = new SpinWait(); |
|
while (!currentPhaseEvent.IsSet && CurrentPhaseNumber - observedPhase <= 1) |
|
{ |
|
spinner.SpinOnce(); |
|
} |
|
} |
|
|
|
/// <summary> |
|
/// The reason of discontinuous waiting instead of direct waiting on the event is to avoid the race where the sense is |
|
/// changed twice because the next phase is finished (due to either RemoveParticipant is called or another thread joined |
|
/// the next phase instead of the current thread) so the current thread will be stuck on the event because it is reset back |
|
/// The maxWait and the shift numbers are arbitrarily chosen, there were no references picking them |
|
/// </summary> |
|
/// <param name="currentPhaseEvent">The current phase event</param> |
|
/// <param name="totalTimeout">wait timeout in milliseconds</param> |
|
/// <param name="token">cancellation token passed to SignalAndWait</param> |
|
/// <param name="observedPhase">The current phase number for this thread</param> |
|
/// <returns>True if the event is set or the phase number changed, false if the timeout expired</returns> |
|
private bool DiscontinuousWait(ManualResetEventSlim currentPhaseEvent, int totalTimeout, CancellationToken token, long observedPhase) |
|
{ |
|
var maxWait = 100; // 100 ms |
|
const int waitTimeCeiling = 10000; // 10 seconds |
|
while (observedPhase == CurrentPhaseNumber) |
|
{ |
|
// the next wait time, the min of the maxWait and the totalTimeout |
|
var waitTime = totalTimeout == Timeout.Infinite ? maxWait : Math.Min(maxWait, totalTimeout); |
|
|
|
if (currentPhaseEvent.Wait(waitTime, token)) |
|
{ |
|
return true; |
|
} |
|
|
|
//update the total wait time |
|
if (totalTimeout != Timeout.Infinite) |
|
{ |
|
totalTimeout -= waitTime; |
|
if (totalTimeout <= 0) |
|
{ |
|
return false; |
|
} |
|
} |
|
|
|
//if the maxwait exceeded 10 seconds then we will stop increasing the maxWait time and keep it 10 seconds, otherwise keep doubling it |
|
maxWait = maxWait >= waitTimeCeiling ? waitTimeCeiling : Math.Min(maxWait << 1, waitTimeCeiling); |
|
} |
|
|
|
//if we exited the loop because the observed phase doesn't match the current phase, then we have to spin to make sure |
|
//the event is set or the next phase is finished |
|
WaitCurrentPhase(currentPhaseEvent, observedPhase); |
|
|
|
return true; |
|
} |
|
|
|
/// <summary> |
|
/// Releases all resources used by the current instance of <see cref="Barrier"/>. |
|
/// </summary> |
|
/// <exception cref="T:System.InvalidOperationException"> |
|
/// The method was invoked from within a post-phase action. |
|
/// </exception> |
|
/// <remarks> |
|
/// Unlike most of the members of <see cref="Barrier"/>, Dispose is not thread-safe and may not be |
|
/// used concurrently with other members of this instance. |
|
/// </remarks> |
|
public void Dispose() |
|
{ |
|
// in case of this is called from the PHA |
|
if (_actionCallerId != 0 && Thread.CurrentThread.ManagedThreadId == _actionCallerId) |
|
{ |
|
throw new InvalidOperationException("This method may not be called from within the postPhaseAction."); |
|
} |
|
Dispose(true); |
|
GC.SuppressFinalize(this); |
|
} |
|
|
|
/// <summary> |
|
/// When overridden in a derived class, releases the unmanaged resources used by the |
|
/// <see cref="Barrier"/>, and optionally releases the managed resources. |
|
/// </summary> |
|
/// <param name="disposing">true to release both managed and unmanaged resources; false to release |
|
/// only unmanaged resources.</param> |
|
/// <remarks> |
|
/// Unlike most of the members of <see cref="Barrier"/>, Dispose is not thread-safe and may not be |
|
/// used concurrently with other members of this instance. |
|
/// </remarks> |
|
protected virtual void Dispose(bool disposing) |
|
{ |
|
if (!_disposed) |
|
{ |
|
if (disposing) |
|
{ |
|
_oddEvent.Dispose(); |
|
_evenEvent.Dispose(); |
|
} |
|
_disposed = true; |
|
} |
|
} |
|
|
|
/// <summary> |
|
/// Throw ObjectDisposedException if the barrier is disposed |
|
/// </summary> |
|
private void ThrowIfDisposed() |
|
{ |
|
if (_disposed) |
|
{ |
|
throw new ObjectDisposedException("Barrier", "The barrier has been disposed."); |
|
} |
|
} |
|
} |
|
} |
|
|
|
#endif |