// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Threading; namespace System.Web.Mvc.Async { // This class is used for the following pattern: // public IAsyncResult BeginInner(..., callback, state); // public TInnerResult EndInner(asyncResult); // public IAsyncResult BeginOuter(..., callback, state); // public TOuterResult EndOuter(asyncResult); // That is, Begin/EndOuter() wrap Begin/EndInner(), potentially with pre- and post-processing. [DebuggerNonUserCode] internal static class AsyncResultWrapper { // helper methods private static readonly EndInvokeDelegate _voidEndInvoke = (IAsyncResult asyncResult, Action action) => { action(); return default(AsyncVoid); }; // kicks off an asynchronous operation public static IAsyncResult Begin(AsyncCallback callback, object state, BeginInvokeDelegate beginDelegate, EndInvokeDelegate endDelegate, object tag = null, int timeout = Timeout.Infinite) { WrappedAsyncResult asyncResult = new WrappedAsyncResult(beginDelegate, endDelegate, tag, callbackSyncContext: null); asyncResult.Begin(callback, state, timeout); return asyncResult; } public static IAsyncResult Begin(AsyncCallback callback, object callbackState, BeginInvokeDelegate beginDelegate, EndInvokeDelegate endDelegate, TState invokeState, object tag = null, int timeout = Timeout.Infinite, SynchronizationContext callbackSyncContext = null) { WrappedAsyncResult asyncResult = new WrappedAsyncResult(beginDelegate, endDelegate, invokeState, tag, callbackSyncContext); asyncResult.Begin(callback, callbackState, timeout); return asyncResult; } public static IAsyncResult Begin(AsyncCallback callback, object callbackState, BeginInvokeDelegate beginDelegate, EndInvokeVoidDelegate endDelegate, TState invokeState, object tag = null, int timeout = Timeout.Infinite, SynchronizationContext callbackSyncContext = null) { WrappedAsyncVoid asyncResult = new WrappedAsyncVoid(beginDelegate, endDelegate, invokeState, tag, callbackSyncContext); asyncResult.Begin(callback, callbackState, timeout); return asyncResult; } // wraps a synchronous operation in an asynchronous wrapper, but still completes synchronously public static IAsyncResult BeginSynchronous(AsyncCallback callback, object callbackState, EndInvokeDelegate func, TState funcState, object tag) { // Frequently called, so use static delegates // Inline delegates that take a generic argument from a generic method don't get cached by the compiler so use a field from a static generic class BeginInvokeDelegate beginDelegate = CachedDelegates.CompletedBeginInvoke; // Pass in the blocking function as the End() method WrappedAsyncResult asyncResult = new WrappedAsyncResult(beginDelegate, func, funcState, tag, callbackSyncContext: null); asyncResult.Begin(callback, callbackState, Timeout.Infinite); return asyncResult; } public static IAsyncResult BeginSynchronous(AsyncCallback callback, object state, Action action, object tag) { return BeginSynchronous(callback, state, _voidEndInvoke, action, tag); } // completes an asynchronous operation public static TResult End(IAsyncResult asyncResult) { return End(asyncResult, tag: null); } public static TResult End(IAsyncResult asyncResult, object tag) { return WrappedAsyncResultBase.Cast(asyncResult, tag).End(); } public static void End(IAsyncResult asyncResult) { End(asyncResult, tag: null); } public static void End(IAsyncResult asyncResult, object tag) { End(asyncResult, tag); } private static class CachedDelegates { internal static BeginInvokeDelegate CompletedBeginInvoke = (AsyncCallback asyncCallback, object asyncState, TState invokeState) => { // Begin() doesn't perform any work on its own and returns immediately. SimpleAsyncResult innerAsyncResult = new SimpleAsyncResult(asyncState); innerAsyncResult.MarkCompleted(completedSynchronously: true, callback: asyncCallback); return innerAsyncResult; }; } [DebuggerNonUserCode] [SuppressMessage("Microsoft.Design", "CA1001:TypesThatOwnDisposableFieldsShouldBeDisposable", Justification = "The Timer will be disposed of either when it fires or when the operation completes successfully.")] private abstract class WrappedAsyncResultBase : IAsyncResult { private const int AsyncStateNone = 0; private const int AsyncStateBeginUnwound = 1; private const int AsyncStateCallbackFired = 2; private int _asyncState; private readonly object _beginDelegateLockObj = new object(); private readonly SingleEntryGate _endExecutedGate = new SingleEntryGate(); // prevent End() from being called twice private readonly SingleEntryGate _handleCallbackGate = new SingleEntryGate(); // prevent callback from being handled multiple times private readonly object _tag; // prevent an instance of this type from being passed to the wrong End() method private IAsyncResult _innerAsyncResult; private AsyncCallback _originalCallback; private volatile bool _timedOut; private Timer _timer; private readonly SynchronizationContext _callbackSyncContext; protected WrappedAsyncResultBase(object tag, SynchronizationContext callbackSyncContext) { _tag = tag; _callbackSyncContext = callbackSyncContext; } public object AsyncState { get { return _innerAsyncResult.AsyncState; } } public WaitHandle AsyncWaitHandle { get { return null; } } public bool CompletedSynchronously { get; private set; } public bool IsCompleted { get { return _timedOut || _innerAsyncResult.IsCompleted; } } // kicks off the process, instantiates a timer if requested public void Begin(AsyncCallback callback, object state, int timeout) { _originalCallback = callback; // Force the target Begin() operation to complete before the callback can continue, // since the target operation might perform post-processing of the data. lock (_beginDelegateLockObj) { _innerAsyncResult = CallBeginDelegate(HandleAsynchronousCompletion, state); // If the callback has already fired, then the completion routine has no-oped and we // can just treat this as if it were a normal synchronous completion. int originalState = Interlocked.Exchange(ref _asyncState, AsyncStateBeginUnwound); bool callbackAlreadyFired = (originalState == AsyncStateCallbackFired); CompletedSynchronously = callbackAlreadyFired || _innerAsyncResult.CompletedSynchronously; if (!CompletedSynchronously) { if (timeout > Timeout.Infinite) { CreateTimer(timeout); } } } if (CompletedSynchronously) { if (callback != null) { callback(this); } } } protected abstract IAsyncResult CallBeginDelegate(AsyncCallback callback, object callbackState); protected abstract TResult CallEndDelegate(IAsyncResult asyncResult); public static WrappedAsyncResultBase Cast(IAsyncResult asyncResult, object tag) { if (asyncResult == null) { throw new ArgumentNullException("asyncResult"); } WrappedAsyncResultBase castResult = asyncResult as WrappedAsyncResultBase; if (castResult != null && Equals(castResult._tag, tag)) { return castResult; } else { throw Error.AsyncCommon_InvalidAsyncResult("asyncResult"); } } private void CallbackUsingSyncContext() { _callbackSyncContext.Sync(() => _originalCallback(this)); } private void CreateTimer(int timeout) { // this method should be called within a lock(_beginDelegateLockObj) _timer = new Timer(HandleTimeout, null, timeout, Timeout.Infinite /* disable periodic signaling */); } public TResult End() { if (!_endExecutedGate.TryEnter()) { throw Error.AsyncCommon_AsyncResultAlreadyConsumed(); } if (_timedOut) { throw new TimeoutException(); } WaitForBeginToCompleteAndDestroyTimer(); return CallEndDelegate(_innerAsyncResult); } private void ExecuteAsynchronousCallback(bool timedOut) { WaitForBeginToCompleteAndDestroyTimer(); if (_handleCallbackGate.TryEnter()) { _timedOut = timedOut; if (_originalCallback != null) { if (_callbackSyncContext != null) { CallbackUsingSyncContext(); } else { _originalCallback(this); } } } } private void HandleAsynchronousCompletion(IAsyncResult asyncResult) { // Transition the async state to CALLBACK_FIRED. If the Begin* method hasn't yet unwound, // then we can no-op here since the Begin method will query the _asyncState field and // treat this as a regular synchronous completion. int originalState = Interlocked.Exchange(ref _asyncState, AsyncStateCallbackFired); if (originalState != AsyncStateBeginUnwound) { return; } ExecuteAsynchronousCallback(timedOut: false); } private void HandleTimeout(object state) { ExecuteAsynchronousCallback(timedOut: true); } private void WaitForBeginToCompleteAndDestroyTimer() { lock (_beginDelegateLockObj) { // Wait for the target Begin() method to complete, as it might be performing // post-processing. This also forces a memory barrier, so _innerAsyncResult // is guaranteed to be non-null at this point. if (_timer != null) { _timer.Dispose(); } _timer = null; } } } private sealed class WrappedAsyncResult : WrappedAsyncResultBase { private readonly BeginInvokeDelegate _beginDelegate; private readonly EndInvokeDelegate _endDelegate; public WrappedAsyncResult(BeginInvokeDelegate beginDelegate, EndInvokeDelegate endDelegate, object tag, SynchronizationContext callbackSyncContext) : base(tag, callbackSyncContext) { _beginDelegate = beginDelegate; _endDelegate = endDelegate; } protected override IAsyncResult CallBeginDelegate(AsyncCallback callback, object callbackState) { return _beginDelegate(callback, callbackState); } protected override TResult CallEndDelegate(IAsyncResult asyncResult) { return _endDelegate(asyncResult); } } private sealed class WrappedAsyncResult : WrappedAsyncResultBase { private readonly BeginInvokeDelegate _beginDelegate; private readonly EndInvokeDelegate _endDelegate; private readonly TState _state; public WrappedAsyncResult(BeginInvokeDelegate beginDelegate, EndInvokeDelegate endDelegate, TState state, object tag, SynchronizationContext callbackSyncContext) : base(tag, callbackSyncContext) { _beginDelegate = beginDelegate; _endDelegate = endDelegate; _state = state; } protected override TResult CallEndDelegate(IAsyncResult asyncResult) { return _endDelegate(asyncResult, _state); } protected override IAsyncResult CallBeginDelegate(AsyncCallback callback, object callbackState) { return _beginDelegate(callback, callbackState, _state); } } private sealed class WrappedAsyncVoid : WrappedAsyncResultBase { private readonly BeginInvokeDelegate _beginDelegate; private readonly EndInvokeVoidDelegate _endDelegate; private readonly TState _state; public WrappedAsyncVoid(BeginInvokeDelegate beginDelegate, EndInvokeVoidDelegate endDelegate, TState state, object tag, SynchronizationContext callbackSyncContext) : base(tag, callbackSyncContext) { _beginDelegate = beginDelegate; _endDelegate = endDelegate; _state = state; } protected override AsyncVoid CallEndDelegate(IAsyncResult asyncResult) { _endDelegate(asyncResult, _state); return default(AsyncVoid); } protected override IAsyncResult CallBeginDelegate(AsyncCallback callback, object callbackState) { return _beginDelegate(callback, callbackState, _state); } } } }