1 //------------------------------------------------------------------------------
2 // <copyright file="_LazyAsyncResult.cs" company="Microsoft">
3 // Copyright (c) Microsoft Corporation. All rights reserved.
5 //------------------------------------------------------------------------------
9 using System.Threading;
10 using System.Diagnostics;
11 using System.Collections;
13 // LazyAsyncResult - Base class for all IAsyncResult classes
14 // that want to take advantage of lazy allocated event handles
15 internal class LazyAsyncResult : IAsyncResult
17 private const int c_HighBit = unchecked((int) 0x80000000);
18 private const int c_ForceAsyncCount = 50;
21 // This is to avoid user mistakes when they queue another async op from a callback the completes sync.
23 private static ThreadContext t_ThreadContext;
25 private static ThreadContext CurrentThreadContext
29 ThreadContext threadContext = t_ThreadContext;
30 if (threadContext == null)
32 threadContext = new ThreadContext();
33 t_ThreadContext = threadContext;
39 private class ThreadContext
41 internal int m_NestedIOCount;
46 internal object _DebugAsyncChain; // Optionally used to track chains of async calls.
47 private bool _ProtectState; // Used by ContextAwareResult to prevent some calls.
51 internal static Hashtable _PendingResults = Hashtable.Synchronized(new Hashtable());
52 internal static int _PendingIndex = 0;
53 internal int _MyIndex;
59 private object m_AsyncObject; // Caller's async object.
60 private object m_AsyncState; // Caller's state object.
61 private AsyncCallback m_AsyncCallback; // Caller's callback method.
62 private object m_Result; // Final IO result to be returned byt the End*() method.
63 private int m_ErrorCode; // Win32 error code for Win32 IO async calls (that want to throw).
64 private int m_IntCompleted; // Sign bit indicates synchronous completion if set.
65 // Remaining bits count the number of InvokeCallbak() calls.
67 private bool m_EndCalled; // true if the user called the End*() method.
68 private bool m_UserEvent; // true if the event has been (or is about to be) handed to the user
70 private object m_Event; // lazy allocated event to be returned in the IAsyncResult for the client to wait on
73 internal LazyAsyncResult(object myObject, object myState, AsyncCallback myCallBack) {
74 m_AsyncObject = myObject;
75 m_AsyncState = myState;
76 m_AsyncCallback = myCallBack;
77 m_Result = DBNull.Value;
78 GlobalLog.Print("LazyAsyncResult#" + ValidationHelper.HashString(this) + "::.ctor()");
81 _MyIndex = Interlocked.Increment(ref _PendingIndex);
82 _PendingResults.Add(_MyIndex, this);
86 // Allows creating a pre-completed result with less interlockeds. Beware! Constructor calls the callback.
87 // if a derived class ever uses this and overloads Cleanup, this may need to change
88 internal LazyAsyncResult(object myObject, object myState, AsyncCallback myCallBack, object result)
90 GlobalLog.Assert(result != DBNull.Value, "LazyAsyncResult#{0}::.ctor()|Result can't be set to DBNull - it's a special internal value.", ValidationHelper.HashString(this));
91 m_AsyncObject = myObject;
92 m_AsyncState = myState;
93 m_AsyncCallback = myCallBack;
97 if (m_AsyncCallback != null) {
98 GlobalLog.Print("LazyAsyncResult#" + ValidationHelper.HashString(this) + "::Complete() invoking callback");
99 m_AsyncCallback(this);
102 GlobalLog.Print("LazyAsyncResult#" + ValidationHelper.HashString(this) + "::Complete() no callback to invoke");
105 GlobalLog.Print("LazyAsyncResult#" + ValidationHelper.HashString(this) + "::.ctor() (pre-completed)");
108 // Interface method to return the original async object:
109 internal object AsyncObject {
111 return m_AsyncObject;
115 // Interface method to return the caller's state object.
116 public object AsyncState {
122 protected AsyncCallback AsyncCallback
126 return m_AsyncCallback;
131 m_AsyncCallback = value;
135 // Interface property to return a WaitHandle that can be waited on for I/O completion.
136 // This property implements lazy event creation.
137 // the event object is only created when this property is accessed,
138 // since we're internally only using callbacks, as long as the user is using
139 // callbacks as well we will not create an event at all.
140 // If this is used, the event cannot be disposed because it is under the control of the
141 // application. Internal should use InternalWaitForCompletion instead - never AsyncWaitHandle.
142 public WaitHandle AsyncWaitHandle
146 GlobalLog.Print("LazyAsyncResult#" + ValidationHelper.HashString(this) + "::get_AsyncWaitHandle()");
149 // Can't be called when state is protected.
152 throw new InvalidOperationException("get_AsyncWaitHandle called in protected state");
156 ManualResetEvent asyncEvent;
158 // Indicates that the user has seen the event; it can't be disposed.
161 // The user has access to this object. Lock-in CompletedSynchronously.
162 if (m_IntCompleted == 0)
164 Interlocked.CompareExchange(ref m_IntCompleted, c_HighBit, 0);
167 // Because InternalWaitForCompletion() tries to dispose this event, it's
168 // possible for m_Event to become null immediately after being set, but only if
169 // IsCompleted has become true. Therefore it's possible for this property
170 // to give different (set) events to different callers when IsCompleted is true.
171 asyncEvent = (ManualResetEvent) m_Event;
172 while (asyncEvent == null)
174 LazilyCreateEvent(out asyncEvent);
177 GlobalLog.Print("LazyAsyncResult#" + ValidationHelper.HashString(this) + "::get_AsyncWaitHandle() m_Event:" + ValidationHelper.HashString(m_Event));
182 // Returns true if this call created the event.
183 // May return with a null handle. That means it thought it got one, but it was disposed in the mean time.
184 private bool LazilyCreateEvent(out ManualResetEvent waitHandle)
186 // lazy allocation of the event:
187 // if this property is never accessed this object is never created
188 waitHandle = new ManualResetEvent(false);
191 if (Interlocked.CompareExchange(ref m_Event, waitHandle, null) == null)
193 if (InternalPeekCompleted)
202 waitHandle = (ManualResetEvent) m_Event;
203 // There's a chance here that m_Event became null. But the only way is if another thread completed
204 // in InternalWaitForCompletion and disposed it. If we're in InternalWaitForCompletion, we now know
205 // IsCompleted is set, so we can avoid the wait when waitHandle comes back null. AsyncWaitHandle
206 // will try again in this case.
212 // This should be very rare, but doing this will reduce the chance of deadlock.
214 if (waitHandle != null)
220 // This allows ContextAwareResult to not let anyone trigger the CompletedSynchronously tripwire while the context is being captured.
221 [Conditional("DEBUG")]
222 protected void DebugProtectState(bool protect)
225 _ProtectState = protect;
229 // Interface property, returning synchronous completion status.
230 public bool CompletedSynchronously {
232 GlobalLog.Print("LazyAsyncResult#" + ValidationHelper.HashString(this) + "::get_CompletedSynchronously()");
235 // Can't be called when state is protected.
238 throw new InvalidOperationException("get_CompletedSynchronously called in protected state");
242 // If this returns greater than zero, it means it was incremented by InvokeCallback before anyone ever saw it.
243 int result = m_IntCompleted;
246 result = Interlocked.CompareExchange(ref m_IntCompleted, c_HighBit, 0);
248 GlobalLog.Print("LazyAsyncResult#" + ValidationHelper.HashString(this) + "::get_CompletedSynchronously() returns: "+((result>0)?"true":"false"));
253 // Interface property, returning completion status.
254 public bool IsCompleted {
256 GlobalLog.Print("LazyAsyncResult#" + ValidationHelper.HashString(this) + "::get_IsCompleted()");
259 // Can't be called when state is protected.
262 throw new InvalidOperationException("get_IsCompleted called in protected state");
266 // Look at just the low bits to see if it's been incremented. If it hasn't, set the high bit
267 // to show that it's been looked at.
268 int result = m_IntCompleted;
271 result = Interlocked.CompareExchange(ref m_IntCompleted, c_HighBit, 0);
273 return (result & ~c_HighBit) != 0;
277 // Use to see if something's completed without fixing CompletedSynchronously
278 internal bool InternalPeekCompleted
282 return (m_IntCompleted & ~c_HighBit) != 0;
286 // Internal property for setting the IO result.
287 internal object Result {
289 return m_Result == DBNull.Value ? null : m_Result;
292 // Ideally this should never be called, since setting
293 // the result object really makes sense when the IO completes.
295 // But if the result was set here (as a preemptive error or for some other reason),
296 // then the "result" parameter passed to InvokeCallback() will be ignored.
299 // It's an error to call after the result has been completed or with DBNull.
300 GlobalLog.Assert(value != DBNull.Value, "LazyAsyncResult#{0}::set_Result()|Result can't be set to DBNull - it's a special internal value.", ValidationHelper.HashString(this));
301 GlobalLog.Assert(!InternalPeekCompleted, "LazyAsyncResult#{0}::set_Result()|Called on completed result.", ValidationHelper.HashString(this));
306 internal bool EndCalled {
315 // Internal property for setting the Win32 IO async error code.
316 internal int ErrorCode {
325 // A method for completing the IO with a result
326 // and invoking the user's callback.
327 // Used by derived classes to pass context into an overridden Complete(). Useful
328 // for determining the 'winning' thread in case several may simultaneously call
329 // the equivalent of InvokeCallback().
330 protected void ProtectedInvokeCallback(object result, IntPtr userToken)
332 GlobalLog.Print("LazyAsyncResult#" + ValidationHelper.HashString(this) + "::ProtectedInvokeCallback() result = " +
333 (result is Exception? ((Exception)result).Message: result == null? "<null>": result.ToString()) +
334 ", userToken:" + userToken.ToString());
336 // Critical to disallow DBNull here - it could result in a stuck spinlock in WaitForCompletion.
337 if (result == DBNull.Value)
339 throw new ArgumentNullException("result");
343 // Always safe to ask for the state now.
344 _ProtectState = false;
347 if ((m_IntCompleted & ~c_HighBit) == 0 && (Interlocked.Increment(ref m_IntCompleted) & ~c_HighBit) == 1)
349 // DBNull.Value is used to guarantee that the first caller wins,
350 // even if the result was set to null.
351 if (m_Result == DBNull.Value)
354 // Does this need a memory barrier to be sure this thread gets the m_Event if it's set? I don't think so
355 // because the Interlockeds on m_IntCompleted/m_Event should serve as the barrier.
356 ManualResetEvent asyncEvent = (ManualResetEvent) m_Event;
357 if (asyncEvent != null)
362 catch (ObjectDisposedException) {
363 // Simply ignore this exception - There is apparently a rare race condition
364 // where the event is disposed before the completion method is called.
372 // A method for completing the IO with a result
373 // and invoking the user's callback.
374 internal void InvokeCallback(object result)
376 ProtectedInvokeCallback(result, IntPtr.Zero);
379 // A method for completing the IO without a result
380 // and invoking the user's callback.
381 internal void InvokeCallback()
383 ProtectedInvokeCallback(null, IntPtr.Zero);
387 // MUST NOT BE CALLED DIRECTLY
388 // A protected method that does callback job and it is guaranteed to be called exactly once.
389 // A derived overriding method must call the base class somewhere or the completion is lost.
391 protected virtual void Complete(IntPtr userToken)
394 bool offloaded = false;
395 ThreadContext threadContext = CurrentThreadContext;
397 ++threadContext.m_NestedIOCount;
402 if (m_AsyncCallback != null) {
403 GlobalLog.Print("LazyAsyncResult#" + ValidationHelper.HashString(this) + "::Complete() invoking callback");
406 if (threadContext.m_NestedIOCount >= c_ForceAsyncCount)
408 GlobalLog.Print("LazyAsyncResult::Complete *** OFFLOADED the user callback ***");
409 ThreadPool.QueueUserWorkItem(new WaitCallback(WorkerThreadComplete));
415 m_AsyncCallback(this);
419 GlobalLog.Print("LazyAsyncResult#" + ValidationHelper.HashString(this) + "::Complete() no callback to invoke");
424 --threadContext.m_NestedIOCount;
426 // Never call this method unless interlocked m_IntCompleted check has succeeded (like in this case)
438 // Only called in the above method
439 void WorkerThreadComplete(object state)
443 m_AsyncCallback(this);
452 // Custom instance cleanup method.
453 // Derived types override this method to release unmanaged resources associated with an IO request.
454 protected virtual void Cleanup()
457 _PendingResults.Remove(_MyIndex);
461 internal object InternalWaitForCompletion()
463 return WaitForCompletion(true);
467 internal object InternalWaitForCompletionNoSideEffects()
469 return WaitForCompletion(false);
473 private object WaitForCompletion(bool snap) {
474 ManualResetEvent waitHandle = null;
475 bool createdByMe = false;
476 bool complete = snap ? IsCompleted : InternalPeekCompleted;
480 // Not done yet, so wait:
481 waitHandle = (ManualResetEvent) m_Event;
482 if (waitHandle == null)
484 createdByMe = LazilyCreateEvent(out waitHandle);
488 if (waitHandle != null)
492 GlobalLog.Print("LazyAsyncResult#" + ValidationHelper.HashString(this) + "::InternalWaitForCompletion() Waiting for completion m_Event#" + ValidationHelper.HashString(waitHandle));
493 waitHandle.WaitOne(Timeout.Infinite, false);
495 catch (ObjectDisposedException)
497 // This can occur if this method is called from two different threads.
498 // This possibility is the trade-off for not locking.
501 // We also want to dispose the event although we can't unless we did wait on it here.
502 if (createdByMe && !m_UserEvent)
504 // Does m_UserEvent need to be volatile (or m_Event set via Interlocked) in order
505 // to avoid giving a user a disposed event?
506 ManualResetEvent oldEvent = (ManualResetEvent) m_Event;
516 // A race condition exists because InvokeCallback sets m_IntCompleted before m_Result (so that m_Result
517 // can benefit from the synchronization of m_IntCompleted). That means you can get here before m_Result got
518 // set (although rarely - once every eight hours of stress). Handle that case with a spin-lock.
519 while (m_Result == DBNull.Value)
522 GlobalLog.Print("LazyAsyncResult#" + ValidationHelper.HashString(this) + "::InternalWaitForCompletion() done: " +
523 (m_Result is Exception? ((Exception)m_Result).Message: m_Result == null? "<null>": m_Result.ToString()));
528 // A general interface that is called to release unmanaged resources associated with the class.
529 // It completes the result but doesn't do any of the notifications.
530 internal void InternalCleanup()
532 if ((m_IntCompleted & ~c_HighBit) == 0 && (Interlocked.Increment(ref m_IntCompleted) & ~c_HighBit) == 1)
534 // Set no result so that just in case there are waiters, they don't hang in the spin lock.