ManualResetEventSlim.cs 35 KB

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