ManualResetEventSlim.cs 36 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761
  1. // Licensed to the .NET Foundation under one or more agreements.
  2. // The .NET Foundation licenses this file to you under the MIT license.
  3. // See the LICENSE file in the project root for more information.
  4. using System.Diagnostics;
  5. namespace System.Threading
  6. {
  7. // ManualResetEventSlim wraps a manual-reset event internally with a little bit of
  8. // spinning. When an event will be set imminently, it is often advantageous to avoid
  9. // a 4k+ cycle context switch in favor of briefly spinning. Therefore we layer on to
  10. // a brief amount of spinning that should, on the average, make using the slim event
  11. // cheaper than using Win32 events directly. This can be reset manually, much like
  12. // a Win32 manual-reset would be.
  13. //
  14. // Notes:
  15. // We lazily allocate the Win32 event internally. Therefore, the caller should
  16. // always call Dispose to clean it up, just in case. This API is a no-op of the
  17. // event wasn't allocated, but if it was, ensures that the event goes away
  18. // eagerly, instead of waiting for finalization.
  19. /// <summary>
  20. /// Provides a slimmed down version of <see cref="T:System.Threading.ManualResetEvent"/>.
  21. /// </summary>
  22. /// <remarks>
  23. /// All public and protected members of <see cref="ManualResetEventSlim"/> are thread-safe and may be used
  24. /// concurrently from multiple threads, with the exception of Dispose, which
  25. /// must only be used when all other operations on the <see cref="ManualResetEventSlim"/> have
  26. /// completed, and Reset, which should only be used when no other threads are
  27. /// accessing the event.
  28. /// </remarks>
  29. [DebuggerDisplay("Set = {IsSet}")]
  30. public class ManualResetEventSlim : IDisposable
  31. {
  32. // These are the default spin counts we use on single-proc and MP machines.
  33. private const int DEFAULT_SPIN_SP = 1;
  34. private volatile object? m_lock;
  35. // A lock used for waiting and pulsing. Lazily initialized via EnsureLockObjectCreated()
  36. private volatile ManualResetEvent? m_eventObj; // A true Win32 event used for waiting.
  37. // -- State -- //
  38. //For a packed word a uint would seem better, but Interlocked.* doesn't support them as uint isn't CLS-compliant.
  39. private volatile int m_combinedState; //ie a uint. Used for the state items listed below.
  40. //1-bit for signalled state
  41. private const int SignalledState_BitMask = unchecked((int)0x80000000);//1000 0000 0000 0000 0000 0000 0000 0000
  42. private const int SignalledState_ShiftCount = 31;
  43. //1-bit for disposed state
  44. private const int Dispose_BitMask = unchecked((int)0x40000000);//0100 0000 0000 0000 0000 0000 0000 0000
  45. //11-bits for m_spinCount
  46. private const int SpinCountState_BitMask = unchecked((int)0x3FF80000); //0011 1111 1111 1000 0000 0000 0000 0000
  47. private const int SpinCountState_ShiftCount = 19;
  48. private const int SpinCountState_MaxValue = (1 << 11) - 1; //2047
  49. //19-bits for m_waiters. This allows support of 512K threads waiting which should be ample
  50. private const int NumWaitersState_BitMask = unchecked((int)0x0007FFFF); // 0000 0000 0000 0111 1111 1111 1111 1111
  51. private const int NumWaitersState_ShiftCount = 0;
  52. private const int NumWaitersState_MaxValue = (1 << 19) - 1; //512K-1
  53. // ----------- //
  54. #if DEBUG
  55. private static int s_nextId; // The next id that will be given out.
  56. private int m_id = Interlocked.Increment(ref s_nextId); // A unique id for debugging purposes only.
  57. private long m_lastSetTime;
  58. private long m_lastResetTime;
  59. #endif
  60. /// <summary>
  61. /// Gets the underlying <see cref="T:System.Threading.WaitHandle"/> object for this <see
  62. /// cref="ManualResetEventSlim"/>.
  63. /// </summary>
  64. /// <value>The underlying <see cref="T:System.Threading.WaitHandle"/> event object fore this <see
  65. /// cref="ManualResetEventSlim"/>.</value>
  66. /// <remarks>
  67. /// Accessing this property forces initialization of an underlying event object if one hasn't
  68. /// already been created. To simply wait on this <see cref="ManualResetEventSlim"/>,
  69. /// the public Wait methods should be preferred.
  70. /// </remarks>
  71. public WaitHandle WaitHandle
  72. {
  73. get
  74. {
  75. ThrowIfDisposed();
  76. if (m_eventObj == null)
  77. {
  78. // Lazily initialize the event object if needed.
  79. LazyInitializeEvent();
  80. Debug.Assert(m_eventObj != null);
  81. }
  82. return m_eventObj;
  83. }
  84. }
  85. /// <summary>
  86. /// Gets whether the event is set.
  87. /// </summary>
  88. /// <value>true if the event has is set; otherwise, false.</value>
  89. public bool IsSet
  90. {
  91. get
  92. {
  93. return 0 != ExtractStatePortion(m_combinedState, SignalledState_BitMask);
  94. }
  95. private set
  96. {
  97. UpdateStateAtomically(((value) ? 1 : 0) << SignalledState_ShiftCount, SignalledState_BitMask);
  98. }
  99. }
  100. /// <summary>
  101. /// Gets the number of spin waits that will be occur before falling back to a true wait.
  102. /// </summary>
  103. public int SpinCount
  104. {
  105. get
  106. {
  107. return ExtractStatePortionAndShiftRight(m_combinedState, SpinCountState_BitMask, SpinCountState_ShiftCount);
  108. }
  109. private set
  110. {
  111. Debug.Assert(value >= 0, "SpinCount is a restricted-width integer. The value supplied is outside the legal range.");
  112. Debug.Assert(value <= SpinCountState_MaxValue, "SpinCount is a restricted-width integer. The value supplied is outside the legal range.");
  113. // Don't worry about thread safety because it's set one time from the constructor
  114. m_combinedState = (m_combinedState & ~SpinCountState_BitMask) | (value << SpinCountState_ShiftCount);
  115. }
  116. }
  117. /// <summary>
  118. /// How many threads are waiting.
  119. /// </summary>
  120. private int Waiters
  121. {
  122. get
  123. {
  124. return ExtractStatePortionAndShiftRight(m_combinedState, NumWaitersState_BitMask, NumWaitersState_ShiftCount);
  125. }
  126. set
  127. {
  128. //setting to <0 would indicate an internal flaw, hence Assert is appropriate.
  129. Debug.Assert(value >= 0, "NumWaiters should never be less than zero. This indicates an internal error.");
  130. // it is possible for the max number of waiters to be exceeded via user-code, hence we use a real exception here.
  131. if (value >= NumWaitersState_MaxValue)
  132. throw new InvalidOperationException(SR.Format(SR.ManualResetEventSlim_ctor_TooManyWaiters, NumWaitersState_MaxValue));
  133. UpdateStateAtomically(value << NumWaitersState_ShiftCount, NumWaitersState_BitMask);
  134. }
  135. }
  136. //-----------------------------------------------------------------------------------
  137. // Constructs a new event, optionally specifying the initial state and spin count.
  138. // The defaults are that the event is unsignaled and some reasonable default spin.
  139. //
  140. /// <summary>
  141. /// Initializes a new instance of the <see cref="ManualResetEventSlim"/>
  142. /// class with an initial state of nonsignaled.
  143. /// </summary>
  144. public ManualResetEventSlim()
  145. : this(false)
  146. {
  147. }
  148. /// <summary>
  149. /// Initializes a new instance of the <see cref="ManualResetEventSlim"/>
  150. /// class with a boolean value indicating whether to set the initial state to signaled.
  151. /// </summary>
  152. /// <param name="initialState">true to set the initial state signaled; false to set the initial state
  153. /// to nonsignaled.</param>
  154. public ManualResetEventSlim(bool initialState)
  155. {
  156. // Specify the default spin count, and use default spin if we're
  157. // on a multi-processor machine. Otherwise, we won't.
  158. Initialize(initialState, SpinWait.SpinCountforSpinBeforeWait);
  159. }
  160. /// <summary>
  161. /// Initializes a new instance of the <see cref="ManualResetEventSlim"/>
  162. /// class with a Boolean value indicating whether to set the initial state to signaled and a specified
  163. /// spin count.
  164. /// </summary>
  165. /// <param name="initialState">true to set the initial state to signaled; false to set the initial state
  166. /// to nonsignaled.</param>
  167. /// <param name="spinCount">The number of spin waits that will occur before falling back to a true
  168. /// wait.</param>
  169. /// <exception cref="T:System.ArgumentOutOfRangeException"><paramref name="spinCount"/> is less than
  170. /// 0 or greater than the maximum allowed value.</exception>
  171. public ManualResetEventSlim(bool initialState, int spinCount)
  172. {
  173. if (spinCount < 0)
  174. {
  175. throw new ArgumentOutOfRangeException(nameof(spinCount));
  176. }
  177. if (spinCount > SpinCountState_MaxValue)
  178. {
  179. throw new ArgumentOutOfRangeException(
  180. nameof(spinCount),
  181. SR.Format(SR.ManualResetEventSlim_ctor_SpinCountOutOfRange, SpinCountState_MaxValue));
  182. }
  183. // We will suppress default spin because the user specified a count.
  184. Initialize(initialState, spinCount);
  185. }
  186. /// <summary>
  187. /// Initializes the internal state of the event.
  188. /// </summary>
  189. /// <param name="initialState">Whether the event is set initially or not.</param>
  190. /// <param name="spinCount">The spin count that decides when the event will block.</param>
  191. private void Initialize(bool initialState, int spinCount)
  192. {
  193. m_combinedState = initialState ? (1 << SignalledState_ShiftCount) : 0;
  194. //the spinCount argument has been validated by the ctors.
  195. //but we now sanity check our predefined constants.
  196. Debug.Assert(DEFAULT_SPIN_SP >= 0, "Internal error - DEFAULT_SPIN_SP is outside the legal range.");
  197. Debug.Assert(DEFAULT_SPIN_SP <= SpinCountState_MaxValue, "Internal error - DEFAULT_SPIN_SP is outside the legal range.");
  198. SpinCount = PlatformHelper.IsSingleProcessor ? DEFAULT_SPIN_SP : spinCount;
  199. }
  200. /// <summary>
  201. /// Helper to ensure the lock object is created before first use.
  202. /// </summary>
  203. private void EnsureLockObjectCreated()
  204. {
  205. if (m_lock != null)
  206. return;
  207. object newObj = new object();
  208. Interlocked.CompareExchange(ref m_lock, newObj, null); // failure is benign. Someone else set the value.
  209. }
  210. /// <summary>
  211. /// This method lazily initializes the event object. It uses CAS to guarantee that
  212. /// many threads racing to call this at once don't result in more than one event
  213. /// being stored and used. The event will be signaled or unsignaled depending on
  214. /// the state of the thin-event itself, with synchronization taken into account.
  215. /// </summary>
  216. /// <returns>True if a new event was created and stored, false otherwise.</returns>
  217. private bool LazyInitializeEvent()
  218. {
  219. bool preInitializeIsSet = IsSet;
  220. ManualResetEvent newEventObj = new ManualResetEvent(preInitializeIsSet);
  221. // We have to CAS this in case we are racing with another thread. We must
  222. // guarantee only one event is actually stored in this field.
  223. if (Interlocked.CompareExchange(ref m_eventObj, newEventObj, null) != null)
  224. {
  225. // Someone else set the value due to a race condition. Destroy the garbage event.
  226. newEventObj.Dispose();
  227. return false;
  228. }
  229. else
  230. {
  231. // Now that the event is published, verify that the state hasn't changed since
  232. // we snapped the preInitializeState. Another thread could have done that
  233. // between our initial observation above and here. The barrier incurred from
  234. // the CAS above (in addition to m_state being volatile) prevents this read
  235. // from moving earlier and being collapsed with our original one.
  236. bool currentIsSet = IsSet;
  237. if (currentIsSet != preInitializeIsSet)
  238. {
  239. Debug.Assert(currentIsSet,
  240. "The only safe concurrent transition is from unset->set: detected set->unset.");
  241. // We saw it as unsignaled, but it has since become set.
  242. lock (newEventObj)
  243. {
  244. // If our event hasn't already been disposed of, we must set it.
  245. if (m_eventObj == newEventObj)
  246. {
  247. newEventObj.Set();
  248. }
  249. }
  250. }
  251. return true;
  252. }
  253. }
  254. /// <summary>
  255. /// Sets the state of the event to signaled, which allows one or more threads waiting on the event to
  256. /// proceed.
  257. /// </summary>
  258. public void Set()
  259. {
  260. Set(false);
  261. }
  262. /// <summary>
  263. /// Private helper to actually perform the Set.
  264. /// </summary>
  265. /// <param name="duringCancellation">Indicates whether we are calling Set() during cancellation.</param>
  266. /// <exception cref="T:System.OperationCanceledException">The object has been canceled.</exception>
  267. private void Set(bool duringCancellation)
  268. {
  269. // We need to ensure that IsSet=true does not get reordered past the read of m_eventObj
  270. // This would be a legal movement according to the .NET memory model.
  271. // The code is safe as IsSet involves an Interlocked.CompareExchange which provides a full memory barrier.
  272. IsSet = true;
  273. // If there are waiting threads, we need to pulse them.
  274. if (Waiters > 0)
  275. {
  276. Debug.Assert(m_lock != null); //if waiters>0, then m_lock has already been created.
  277. lock (m_lock)
  278. {
  279. Monitor.PulseAll(m_lock);
  280. }
  281. }
  282. ManualResetEvent? eventObj = m_eventObj;
  283. //Design-decision: do not set the event if we are in cancellation -> better to deadlock than to wake up waiters incorrectly
  284. //It would be preferable to wake up the event and have it throw OCE. This requires MRE to implement cancellation logic
  285. if (eventObj != null && !duringCancellation)
  286. {
  287. // We must surround this call to Set in a lock. The reason is fairly subtle.
  288. // Sometimes a thread will issue a Wait and wake up after we have set m_state,
  289. // but before we have gotten around to setting m_eventObj (just below). That's
  290. // because Wait first checks m_state and will only access the event if absolutely
  291. // necessary. However, the coding pattern { event.Wait(); event.Dispose() } is
  292. // quite common, and we must support it. If the waiter woke up and disposed of
  293. // the event object before the setter has finished, however, we would try to set a
  294. // now-disposed Win32 event. Crash! To deal with this race condition, we use a lock to
  295. // protect access to the event object when setting and disposing of it. We also
  296. // double-check that the event has not become null in the meantime when in the lock.
  297. lock (eventObj)
  298. {
  299. if (m_eventObj != null)
  300. {
  301. // If somebody is waiting, we must set the event.
  302. m_eventObj.Set();
  303. }
  304. }
  305. }
  306. #if DEBUG
  307. m_lastSetTime = DateTime.UtcNow.Ticks;
  308. #endif
  309. }
  310. /// <summary>
  311. /// Sets the state of the event to nonsignaled, which causes threads to block.
  312. /// </summary>
  313. /// <remarks>
  314. /// Unlike most of the members of <see cref="ManualResetEventSlim"/>, <see cref="Reset()"/> is not
  315. /// thread-safe and may not be used concurrently with other members of this instance.
  316. /// </remarks>
  317. public void Reset()
  318. {
  319. ThrowIfDisposed();
  320. // If there's an event, reset it.
  321. if (m_eventObj != null)
  322. {
  323. m_eventObj.Reset();
  324. }
  325. // There is a race condition here. If another thread Sets the event, we will get into a state
  326. // where m_state will be unsignaled, yet the Win32 event object will have been signaled.
  327. // This could cause waiting threads to wake up even though the event is in an
  328. // unsignaled state. This is fine -- those that are calling Reset concurrently are
  329. // responsible for doing "the right thing" -- e.g. rechecking the condition and
  330. // resetting the event manually.
  331. // And finally set our state back to unsignaled.
  332. IsSet = false;
  333. #if DEBUG
  334. m_lastResetTime = DateTime.UtcNow.Ticks;
  335. #endif
  336. }
  337. /// <summary>
  338. /// Blocks the current thread until the current <see cref="ManualResetEventSlim"/> is set.
  339. /// </summary>
  340. /// <exception cref="T:System.InvalidOperationException">
  341. /// The maximum number of waiters has been exceeded.
  342. /// </exception>
  343. /// <remarks>
  344. /// The caller of this method blocks indefinitely until the current instance is set. The caller will
  345. /// return immediately if the event is currently in a set state.
  346. /// </remarks>
  347. public void Wait()
  348. {
  349. Wait(Timeout.Infinite, new CancellationToken());
  350. }
  351. /// <summary>
  352. /// Blocks the current thread until the current <see cref="ManualResetEventSlim"/> receives a signal,
  353. /// while observing a <see cref="T:System.Threading.CancellationToken"/>.
  354. /// </summary>
  355. /// <param name="cancellationToken">The <see cref="T:System.Threading.CancellationToken"/> to
  356. /// observe.</param>
  357. /// <exception cref="T:System.InvalidOperationException">
  358. /// The maximum number of waiters has been exceeded.
  359. /// </exception>
  360. /// <exception cref="T:System.OperationCanceledExcepton"><paramref name="cancellationToken"/> was
  361. /// canceled.</exception>
  362. /// <remarks>
  363. /// The caller of this method blocks indefinitely until the current instance is set. The caller will
  364. /// return immediately if the event is currently in a set state.
  365. /// </remarks>
  366. public void Wait(CancellationToken cancellationToken)
  367. {
  368. Wait(Timeout.Infinite, cancellationToken);
  369. }
  370. /// <summary>
  371. /// Blocks the current thread until the current <see cref="ManualResetEventSlim"/> is set, using a
  372. /// <see cref="T:System.TimeSpan"/> to measure the time interval.
  373. /// </summary>
  374. /// <param name="timeout">A <see cref="System.TimeSpan"/> that represents the number of milliseconds
  375. /// to wait, or a <see cref="System.TimeSpan"/> that represents -1 milliseconds to wait indefinitely.
  376. /// </param>
  377. /// <returns>true if the <see cref="System.Threading.ManualResetEventSlim"/> was set; otherwise,
  378. /// false.</returns>
  379. /// <exception cref="T:System.ArgumentOutOfRangeException"><paramref name="timeout"/> is a negative
  380. /// number other than -1 milliseconds, which represents an infinite time-out -or- timeout is greater
  381. /// than <see cref="System.Int32.MaxValue"/>.</exception>
  382. /// <exception cref="T:System.InvalidOperationException">
  383. /// The maximum number of waiters has been exceeded.
  384. /// </exception>
  385. public bool Wait(TimeSpan timeout)
  386. {
  387. long totalMilliseconds = (long)timeout.TotalMilliseconds;
  388. if (totalMilliseconds < -1 || totalMilliseconds > int.MaxValue)
  389. {
  390. throw new ArgumentOutOfRangeException(nameof(timeout));
  391. }
  392. return Wait((int)totalMilliseconds, new CancellationToken());
  393. }
  394. /// <summary>
  395. /// Blocks the current thread until the current <see cref="ManualResetEventSlim"/> is set, using a
  396. /// <see cref="T:System.TimeSpan"/> to measure the time interval, while observing a <see
  397. /// cref="T:System.Threading.CancellationToken"/>.
  398. /// </summary>
  399. /// <param name="timeout">A <see cref="System.TimeSpan"/> that represents the number of milliseconds
  400. /// to wait, or a <see cref="System.TimeSpan"/> that represents -1 milliseconds to wait indefinitely.
  401. /// </param>
  402. /// <param name="cancellationToken">The <see cref="T:System.Threading.CancellationToken"/> to
  403. /// observe.</param>
  404. /// <returns>true if the <see cref="System.Threading.ManualResetEventSlim"/> was set; otherwise,
  405. /// false.</returns>
  406. /// <exception cref="T:System.ArgumentOutOfRangeException"><paramref name="timeout"/> is a negative
  407. /// number other than -1 milliseconds, which represents an infinite time-out -or- timeout is greater
  408. /// than <see cref="System.Int32.MaxValue"/>.</exception>
  409. /// <exception cref="T:System.Threading.OperationCanceledException"><paramref
  410. /// name="cancellationToken"/> was canceled.</exception>
  411. /// <exception cref="T:System.InvalidOperationException">
  412. /// The maximum number of waiters has been exceeded.
  413. /// </exception>
  414. public bool Wait(TimeSpan timeout, CancellationToken cancellationToken)
  415. {
  416. long totalMilliseconds = (long)timeout.TotalMilliseconds;
  417. if (totalMilliseconds < -1 || totalMilliseconds > int.MaxValue)
  418. {
  419. throw new ArgumentOutOfRangeException(nameof(timeout));
  420. }
  421. return Wait((int)totalMilliseconds, cancellationToken);
  422. }
  423. /// <summary>
  424. /// Blocks the current thread until the current <see cref="ManualResetEventSlim"/> is set, using a
  425. /// 32-bit signed integer to measure the time interval.
  426. /// </summary>
  427. /// <param name="millisecondsTimeout">The number of milliseconds to wait, or <see
  428. /// cref="Timeout.Infinite"/>(-1) to wait indefinitely.</param>
  429. /// <returns>true if the <see cref="System.Threading.ManualResetEventSlim"/> was set; otherwise,
  430. /// false.</returns>
  431. /// <exception cref="T:System.ArgumentOutOfRangeException"><paramref name="millisecondsTimeout"/> is a
  432. /// negative number other than -1, which represents an infinite time-out.</exception>
  433. /// <exception cref="T:System.InvalidOperationException">
  434. /// The maximum number of waiters has been exceeded.
  435. /// </exception>
  436. public bool Wait(int millisecondsTimeout)
  437. {
  438. return Wait(millisecondsTimeout, new CancellationToken());
  439. }
  440. /// <summary>
  441. /// Blocks the current thread until the current <see cref="ManualResetEventSlim"/> is set, using a
  442. /// 32-bit signed integer to measure the time interval, while observing a <see
  443. /// cref="T:System.Threading.CancellationToken"/>.
  444. /// </summary>
  445. /// <param name="millisecondsTimeout">The number of milliseconds to wait, or <see
  446. /// cref="Timeout.Infinite"/>(-1) to wait indefinitely.</param>
  447. /// <param name="cancellationToken">The <see cref="T:System.Threading.CancellationToken"/> to
  448. /// observe.</param>
  449. /// <returns>true if the <see cref="System.Threading.ManualResetEventSlim"/> was set; otherwise,
  450. /// false.</returns>
  451. /// <exception cref="T:System.ArgumentOutOfRangeException"><paramref name="millisecondsTimeout"/> is a
  452. /// negative number other than -1, which represents an infinite time-out.</exception>
  453. /// <exception cref="T:System.InvalidOperationException">
  454. /// The maximum number of waiters has been exceeded.
  455. /// </exception>
  456. /// <exception cref="T:System.Threading.OperationCanceledException"><paramref
  457. /// name="cancellationToken"/> was canceled.</exception>
  458. public bool Wait(int millisecondsTimeout, CancellationToken cancellationToken)
  459. {
  460. ThrowIfDisposed();
  461. cancellationToken.ThrowIfCancellationRequested(); // an early convenience check
  462. if (millisecondsTimeout < -1)
  463. {
  464. throw new ArgumentOutOfRangeException(nameof(millisecondsTimeout));
  465. }
  466. if (!IsSet)
  467. {
  468. if (millisecondsTimeout == 0)
  469. {
  470. // For 0-timeouts, we just return immediately.
  471. return false;
  472. }
  473. // We spin briefly before falling back to allocating and/or waiting on a true event.
  474. uint startTime = 0;
  475. bool bNeedTimeoutAdjustment = false;
  476. int realMillisecondsTimeout = millisecondsTimeout; //this will be adjusted if necessary.
  477. if (millisecondsTimeout != Timeout.Infinite)
  478. {
  479. // We will account for time spent spinning, so that we can decrement it from our
  480. // timeout. In most cases the time spent in this section will be negligible. But
  481. // we can't discount the possibility of our thread being switched out for a lengthy
  482. // period of time. The timeout adjustments only take effect when and if we actually
  483. // decide to block in the kernel below.
  484. startTime = TimeoutHelper.GetTime();
  485. bNeedTimeoutAdjustment = true;
  486. }
  487. // Spin
  488. int spinCount = SpinCount;
  489. var spinner = new SpinWait();
  490. while (spinner.Count < spinCount)
  491. {
  492. spinner.SpinOnce(sleep1Threshold: -1);
  493. if (IsSet)
  494. {
  495. return true;
  496. }
  497. if (spinner.Count >= 100 && spinner.Count % 10 == 0) // check the cancellation token if the user passed a very large spin count
  498. cancellationToken.ThrowIfCancellationRequested();
  499. }
  500. // Now enter the lock and wait. Must be created before registering the cancellation callback,
  501. // which will try to take this lock.
  502. EnsureLockObjectCreated();
  503. // We must register and unregister the token outside of the lock, to avoid deadlocks.
  504. using (cancellationToken.UnsafeRegister(s_cancellationTokenCallback, this))
  505. {
  506. lock (m_lock!)
  507. {
  508. // Loop to cope with spurious wakeups from other waits being canceled
  509. while (!IsSet)
  510. {
  511. // If our token was canceled, we must throw and exit.
  512. cancellationToken.ThrowIfCancellationRequested();
  513. //update timeout (delays in wait commencement are due to spinning and/or spurious wakeups from other waits being canceled)
  514. if (bNeedTimeoutAdjustment)
  515. {
  516. realMillisecondsTimeout = TimeoutHelper.UpdateTimeOut(startTime, millisecondsTimeout);
  517. if (realMillisecondsTimeout <= 0)
  518. return false;
  519. }
  520. // There is a race condition that Set will fail to see that there are waiters as Set does not take the lock,
  521. // so after updating waiters, we must check IsSet again.
  522. // Also, we must ensure there cannot be any reordering of the assignment to Waiters and the
  523. // read from IsSet. This is guaranteed as Waiters{set;} involves an Interlocked.CompareExchange
  524. // operation which provides a full memory barrier.
  525. // If we see IsSet=false, then we are guaranteed that Set() will see that we are
  526. // waiting and will pulse the monitor correctly.
  527. Waiters = Waiters + 1;
  528. if (IsSet) //This check must occur after updating Waiters.
  529. {
  530. Waiters--; //revert the increment.
  531. return true;
  532. }
  533. // Now finally perform the wait.
  534. try
  535. {
  536. // ** the actual wait **
  537. if (!Monitor.Wait(m_lock, realMillisecondsTimeout))
  538. return false; //return immediately if the timeout has expired.
  539. }
  540. finally
  541. {
  542. // Clean up: we're done waiting.
  543. Waiters = Waiters - 1;
  544. }
  545. // Now just loop back around, and the right thing will happen. Either:
  546. // 1. We had a spurious wake-up due to some other wait being canceled via a different cancellationToken (rewait)
  547. // or 2. the wait was successful. (the loop will break)
  548. }
  549. }
  550. }
  551. } // automatically disposes (and unregisters) the callback
  552. return true; //done. The wait was satisfied.
  553. }
  554. /// <summary>
  555. /// Releases all resources used by the current instance of <see cref="ManualResetEventSlim"/>.
  556. /// </summary>
  557. /// <remarks>
  558. /// Unlike most of the members of <see cref="ManualResetEventSlim"/>, <see cref="Dispose()"/> is not
  559. /// thread-safe and may not be used concurrently with other members of this instance.
  560. /// </remarks>
  561. public void Dispose()
  562. {
  563. Dispose(true);
  564. GC.SuppressFinalize(this);
  565. }
  566. /// <summary>
  567. /// When overridden in a derived class, releases the unmanaged resources used by the
  568. /// <see cref="ManualResetEventSlim"/>, and optionally releases the managed resources.
  569. /// </summary>
  570. /// <param name="disposing">true to release both managed and unmanaged resources;
  571. /// false to release only unmanaged resources.</param>
  572. /// <remarks>
  573. /// Unlike most of the members of <see cref="ManualResetEventSlim"/>, <see cref="Dispose(bool)"/> is not
  574. /// thread-safe and may not be used concurrently with other members of this instance.
  575. /// </remarks>
  576. protected virtual void Dispose(bool disposing)
  577. {
  578. if ((m_combinedState & Dispose_BitMask) != 0)
  579. return; // already disposed
  580. m_combinedState |= Dispose_BitMask; //set the dispose bit
  581. if (disposing)
  582. {
  583. // We will dispose of the event object. We do this under a lock to protect
  584. // against the race condition outlined in the Set method above.
  585. ManualResetEvent? eventObj = m_eventObj;
  586. if (eventObj != null)
  587. {
  588. lock (eventObj)
  589. {
  590. eventObj.Dispose();
  591. m_eventObj = null;
  592. }
  593. }
  594. }
  595. }
  596. /// <summary>
  597. /// Throw ObjectDisposedException if the MRES is disposed
  598. /// </summary>
  599. private void ThrowIfDisposed()
  600. {
  601. if ((m_combinedState & Dispose_BitMask) != 0)
  602. throw new ObjectDisposedException(SR.ManualResetEventSlim_Disposed);
  603. }
  604. /// <summary>
  605. /// Private helper method to wake up waiters when a cancellationToken gets canceled.
  606. /// </summary>
  607. private static readonly Action<object?> s_cancellationTokenCallback = new Action<object?>(CancellationTokenCallback);
  608. private static void CancellationTokenCallback(object? obj)
  609. {
  610. Debug.Assert(obj is ManualResetEventSlim, "Expected a ManualResetEventSlim");
  611. ManualResetEventSlim mre = (ManualResetEventSlim)obj;
  612. Debug.Assert(mre.m_lock != null); //the lock should have been created before this callback is registered for use.
  613. lock (mre.m_lock)
  614. {
  615. Monitor.PulseAll(mre.m_lock); // awaken all waiters
  616. }
  617. }
  618. /// <summary>
  619. /// Private helper method for updating parts of a bit-string state value.
  620. /// Mainly called from the IsSet and Waiters properties setters
  621. /// </summary>
  622. /// <remarks>
  623. /// Note: the parameter types must be int as CompareExchange cannot take a Uint
  624. /// </remarks>
  625. /// <param name="newBits">The new value</param>
  626. /// <param name="updateBitsMask">The mask used to set the bits</param>
  627. private void UpdateStateAtomically(int newBits, int updateBitsMask)
  628. {
  629. SpinWait sw = new SpinWait();
  630. Debug.Assert((newBits | updateBitsMask) == updateBitsMask, "newBits do not fall within the updateBitsMask.");
  631. do
  632. {
  633. int oldState = m_combinedState; // cache the old value for testing in CAS
  634. // Procedure:(1) zero the updateBits. eg oldState = [11111111] flag= [00111000] newState = [11000111]
  635. // then (2) map in the newBits. eg [11000111] newBits=00101000, newState=[11101111]
  636. int newState = (oldState & ~updateBitsMask) | newBits;
  637. if (Interlocked.CompareExchange(ref m_combinedState, newState, oldState) == oldState)
  638. {
  639. return;
  640. }
  641. sw.SpinOnce(sleep1Threshold: -1);
  642. } while (true);
  643. }
  644. /// <summary>
  645. /// Private helper method - performs Mask and shift, particular helpful to extract a field from a packed word.
  646. /// eg ExtractStatePortionAndShiftRight(0x12345678, 0xFF000000, 24) => 0x12, ie extracting the top 8-bits as a simple integer
  647. ///
  648. /// ?? is there a common place to put this rather than being private to MRES?
  649. /// </summary>
  650. /// <param name="state"></param>
  651. /// <param name="mask"></param>
  652. /// <param name="rightBitShiftCount"></param>
  653. /// <returns></returns>
  654. private static int ExtractStatePortionAndShiftRight(int state, int mask, int rightBitShiftCount)
  655. {
  656. //convert to uint before shifting so that right-shift does not replicate the sign-bit,
  657. //then convert back to int.
  658. return unchecked((int)(((uint)(state & mask)) >> rightBitShiftCount));
  659. }
  660. /// <summary>
  661. /// Performs a Mask operation, but does not perform the shift.
  662. /// This is acceptable for boolean values for which the shift is unnecessary
  663. /// eg (val &amp; Mask) != 0 is an appropriate way to extract a boolean rather than using
  664. /// ((val &amp; Mask) &gt;&gt; shiftAmount) == 1
  665. ///
  666. /// ?? is there a common place to put this rather than being private to MRES?
  667. /// </summary>
  668. /// <param name="state"></param>
  669. /// <param name="mask"></param>
  670. private static int ExtractStatePortion(int state, int mask)
  671. {
  672. return state & mask;
  673. }
  674. }
  675. }