ManualResetEventSlim.cs 35 KB

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