ManualResetEventSlim.cs
Go to the documentation of this file.
1 // ManualResetEventSlim.cs
2 //
3 // Authors:
4 // Marek Safar <marek.safar@gmail.com>
5 //
6 // Copyright (c) 2008 Jérémie "Garuma" Laval
7 // Copyright 2011 Xamarin Inc (http://www.xamarin.com).
8 //
9 // Permission is hereby granted, free of charge, to any person obtaining a copy
10 // of this software and associated documentation files (the "Software"), to deal
11 // in the Software without restriction, including without limitation the rights
12 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13 // copies of the Software, and to permit persons to whom the Software is
14 // furnished to do so, subject to the following conditions:
15 //
16 // The above copyright notice and this permission notice shall be included in
17 // all copies or substantial portions of the Software.
18 //
19 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
25 // THE SOFTWARE.
26 //
27 //
28 
29 #if NET_4_0
30 
31 namespace System.Threading
32 {
33  [System.Diagnostics.DebuggerDisplayAttribute ("Set = {IsSet}")]
34  public class ManualResetEventSlim : IDisposable
35  {
36  readonly int spinCount;
37 
38  ManualResetEvent handle;
39  internal AtomicBooleanValue disposed;
40  int used;
41  long state;
42 
44  : this (false, 10)
45  {
46  }
47 
48  public ManualResetEventSlim (bool initialState)
49  : this (initialState, 10)
50  {
51  }
52 
53  public ManualResetEventSlim (bool initialState, int spinCount)
54  {
55  if (spinCount < 0 || spinCount > 2047)
56  throw new ArgumentOutOfRangeException ("spinCount");
57 
58  this.state = initialState ? 1 : 0;
59  this.spinCount = spinCount;
60  }
61 
62  public bool IsSet {
63  get {
64  return (state & 1) == 1;
65  }
66  }
67 
68  public int SpinCount {
69  get {
70  return spinCount;
71  }
72  }
73 
74  public void Reset ()
75  {
76  ThrowIfDisposed ();
77 
78  var stamp = UpdateStateWithOp (false);
79  if (handle != null)
80  CommitChangeToHandle (stamp);
81  }
82 
83  public void Set ()
84  {
85  var stamp = UpdateStateWithOp (true);
86  if (handle != null)
87  CommitChangeToHandle (stamp);
88  }
89 
90  long UpdateStateWithOp (bool set)
91  {
92  long oldValue, newValue;
93  do {
94  oldValue = state;
95  newValue = (long)(((oldValue >> 1) + 1) << 1) | (set ? 1u : 0u);
96  } while (Interlocked.CompareExchange (ref state, newValue, oldValue) != oldValue);
97  return newValue;
98  }
99 
100  void CommitChangeToHandle (long stamp)
101  {
102  Interlocked.Increment (ref used);
103  var tmpHandle = handle;
104  if (tmpHandle != null) {
105  // First in all case we carry the operation we were called for
106  if ((stamp & 1) == 1)
107  tmpHandle.Set ();
108  else
109  tmpHandle.Reset ();
110 
111  /* Then what may happen is that the two suboperations (state change and handle change)
112  * overlapped with others. In our case it doesn't matter if the two suboperations aren't
113  * executed together at the same time, the only thing we have to make sure of is that both
114  * state and handle are synchronized on the last visible state change.
115  *
116  * For instance if S is state change and H is handle change, for 3 concurrent operations
117  * we may have the following serialized timeline: S1 S2 H2 S3 H3 H1
118  * Which is perfectly fine (all S were converted to H at some stage) but in that case
119  * we have a mismatch between S and H at the end because the last operations done were
120  * S3/H1. We thus need to repeat H3 to get to the desired final state.
121  */
122  long currentState;
123  do {
124  currentState = state;
125  if (currentState != stamp && (stamp & 1) != (currentState & 1)) {
126  if ((currentState & 1) == 1)
127  tmpHandle.Set ();
128  else
129  tmpHandle.Reset ();
130  }
131  } while (currentState != state);
132  }
133  Interlocked.Decrement (ref used);
134  }
135 
136  public void Wait ()
137  {
138  Wait (CancellationToken.None);
139  }
140 
141  public bool Wait (int millisecondsTimeout)
142  {
143  return Wait (millisecondsTimeout, CancellationToken.None);
144  }
145 
146  public bool Wait (TimeSpan timeout)
147  {
148  return Wait (CheckTimeout (timeout), CancellationToken.None);
149  }
150 
151  public void Wait (CancellationToken cancellationToken)
152  {
153  Wait (Timeout.Infinite, cancellationToken);
154  }
155 
156  public bool Wait (int millisecondsTimeout, CancellationToken cancellationToken)
157  {
158  if (millisecondsTimeout < -1)
159  throw new ArgumentOutOfRangeException ("millisecondsTimeout");
160 
161  ThrowIfDisposed ();
162 
163  if (!IsSet) {
164  SpinWait wait = new SpinWait ();
165 
166  while (!IsSet) {
167  cancellationToken.ThrowIfCancellationRequested ();
168 
169  if (wait.Count < spinCount) {
170  wait.SpinOnce ();
171  continue;
172  }
173 
174  break;
175  }
176 
177  if (IsSet)
178  return true;
179 
180  WaitHandle handle = WaitHandle;
181 
182  if (cancellationToken.CanBeCanceled) {
183  var result = WaitHandle.WaitAny (new[] { handle, cancellationToken.WaitHandle }, millisecondsTimeout, false);
184  if (result == 1)
185  throw new OperationCanceledException (cancellationToken);
186  if (result == WaitHandle.WaitTimeout)
187  return false;
188  } else {
189  if (!handle.WaitOne (millisecondsTimeout, false))
190  return false;
191  }
192  }
193 
194  return true;
195  }
196 
197  public bool Wait (TimeSpan timeout, CancellationToken cancellationToken)
198  {
199  return Wait (CheckTimeout (timeout), cancellationToken);
200  }
201 
202  public WaitHandle WaitHandle {
203  get {
204  ThrowIfDisposed ();
205 
206  if (handle != null)
207  return handle;
208 
209  var isSet = IsSet;
210  var mre = new ManualResetEvent (IsSet);
211  if (AotInterlocked.CompareExchange (ref handle, mre, null) == null) {
212  //
213  // Ensure the Set has not ran meantime
214  //
215  if (isSet != IsSet) {
216  if (IsSet) {
217  mre.Set ();
218  } else {
219  mre.Reset ();
220  }
221  }
222  } else {
223  //
224  // Release the event when other thread was faster
225  //
226 
227  // Spicy Pixel: Must use the public interface (e.g. Close)
228  mre.Close ();
229  // mre.Dispose ();
230  }
231 
232  return handle;
233  }
234  }
235 
236  public void Dispose ()
237  {
238  Dispose (true);
239  }
240 
241  protected virtual void Dispose (bool disposing)
242  {
243  if (!disposed.TryRelaxedSet ())
244  return;
245 
246  if (handle != null) {
247  var tmpHandle = AotInterlocked.Exchange (ref handle, null);
248  if (used > 0) {
249  // A tiny wait (just a few cycles normally) before releasing
250  SpinWait wait = new SpinWait ();
251  while (used > 0)
252  wait.SpinOnce ();
253  }
254  // Spicy Pixel: Must use the public interface (e.g. Close)
255  tmpHandle.Close ();
256  // tmpHandle.Dispose ();
257  }
258  }
259 
260  void ThrowIfDisposed ()
261  {
262  if (disposed.Value)
263  throw new ObjectDisposedException ("ManualResetEventSlim");
264  }
265 
266  static int CheckTimeout (TimeSpan timeout)
267  {
268  try {
269  return checked ((int)timeout.TotalMilliseconds);
270  } catch (System.OverflowException) {
271  throw new ArgumentOutOfRangeException ("timeout");
272  }
273  }
274  }
275 }
276 #endif
void Wait(CancellationToken cancellationToken)
virtual void Dispose(bool disposing)
bool Wait(TimeSpan timeout, CancellationToken cancellationToken)
bool Wait(int millisecondsTimeout, CancellationToken cancellationToken)
Interlocked reference exchanges do not work with the older Mono AOT compiler so this type fudges arou...
ManualResetEventSlim(bool initialState, int spinCount)
bool Wait(int millisecondsTimeout)