diff options
Diffstat (limited to 'src/mscorlib/src/System/Threading')
68 files changed, 41635 insertions, 0 deletions
diff --git a/src/mscorlib/src/System/Threading/AbandonedMutexException.cs b/src/mscorlib/src/System/Threading/AbandonedMutexException.cs new file mode 100644 index 0000000000..6b4977fbc5 --- /dev/null +++ b/src/mscorlib/src/System/Threading/AbandonedMutexException.cs @@ -0,0 +1,85 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +// +// +// AbandonedMutexException +// Thrown when a wait completes because one or more mutexes was abandoned. +// AbandonedMutexs indicate serious error in user code or machine state. +//////////////////////////////////////////////////////////////////////////////// + +namespace System.Threading { + + using System; + using System.Runtime.Serialization; + using System.Threading; + using System.Runtime.InteropServices; + + [Serializable] + [ComVisibleAttribute(false)] + public class AbandonedMutexException : SystemException { + + private int m_MutexIndex = -1; + private Mutex m_Mutex = null; + + public AbandonedMutexException() + : base(Environment.GetResourceString("Threading.AbandonedMutexException")) { + SetErrorCode(__HResults.COR_E_ABANDONEDMUTEX); + } + + public AbandonedMutexException(String message) + : base(message) { + SetErrorCode(__HResults.COR_E_ABANDONEDMUTEX); + } + + public AbandonedMutexException(String message, Exception inner ) + : base(message, inner) { + SetErrorCode(__HResults.COR_E_ABANDONEDMUTEX); + } + + public AbandonedMutexException(int location, WaitHandle handle) + : base(Environment.GetResourceString("Threading.AbandonedMutexException")) { + SetErrorCode(__HResults.COR_E_ABANDONEDMUTEX); + SetupException(location,handle); + } + + public AbandonedMutexException(String message,int location, WaitHandle handle) + : base(message) { + SetErrorCode(__HResults.COR_E_ABANDONEDMUTEX); + SetupException(location,handle); + } + + public AbandonedMutexException(String message, Exception inner,int location, WaitHandle handle ) + : base(message, inner) { + SetErrorCode(__HResults.COR_E_ABANDONEDMUTEX); + SetupException(location,handle); + } + + private void SetupException(int location, WaitHandle handle) + { + m_MutexIndex = location; + if(handle != null) + m_Mutex = handle as Mutex; + } + + protected AbandonedMutexException(SerializationInfo info, StreamingContext context) : base(info, context) { + } + + public Mutex Mutex + { + get { + return m_Mutex; + } + } + + public int MutexIndex + { + get{ + return m_MutexIndex; + } + } + + } +} + diff --git a/src/mscorlib/src/System/Threading/ApartmentState.cs b/src/mscorlib/src/System/Threading/ApartmentState.cs new file mode 100644 index 0000000000..844f85e9e7 --- /dev/null +++ b/src/mscorlib/src/System/Threading/ApartmentState.cs @@ -0,0 +1,28 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +// +/*============================================================================= +** +** +** +** Purpose: Enum to represent the different threading models +** +** +=============================================================================*/ + +namespace System.Threading { + + [Serializable] +[System.Runtime.InteropServices.ComVisible(true)] + public enum ApartmentState + { + /*========================================================================= + ** Constants for thread apartment states. + =========================================================================*/ + STA = 0, + MTA = 1, + Unknown = 2 + } +} diff --git a/src/mscorlib/src/System/Threading/AsyncLocal.cs b/src/mscorlib/src/System/Threading/AsyncLocal.cs new file mode 100644 index 0000000000..264f2a6ff7 --- /dev/null +++ b/src/mscorlib/src/System/Threading/AsyncLocal.cs @@ -0,0 +1,116 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Diagnostics.Contracts; +using System.Security; + +namespace System.Threading +{ + // + // AsyncLocal<T> represents "ambient" data that is local to a given asynchronous control flow, such as an + // async method. For example, say you want to associate a culture with a given async flow: + // + // static AsyncLocal<Culture> s_currentCulture = new AsyncLocal<Culture>(); + // + // static async Task SomeOperationAsync(Culture culture) + // { + // s_currentCulture.Value = culture; + // + // await FooAsync(); + // } + // + // static async Task FooAsync() + // { + // PrintStringWithCulture(s_currentCulture.Value); + // } + // + // AsyncLocal<T> also provides optional notifications when the value associated with the current thread + // changes, either because it was explicitly changed by setting the Value property, or implicitly changed + // when the thread encountered an "await" or other context transition. For example, we might want our + // current culture to be communicated to the OS as well: + // + // static AsyncLocal<Culture> s_currentCulture = new AsyncLocal<Culture>( + // args => + // { + // NativeMethods.SetThreadCulture(args.CurrentValue.LCID); + // }); + // + public sealed class AsyncLocal<T> : IAsyncLocal + { + [SecurityCritical] // critical because this action will terminate the process if it throws. + private readonly Action<AsyncLocalValueChangedArgs<T>> m_valueChangedHandler; + + // + // Constructs an AsyncLocal<T> that does not receive change notifications. + // + public AsyncLocal() + { + } + + // + // Constructs an AsyncLocal<T> with a delegate that is called whenever the current value changes + // on any thread. + // + [SecurityCritical] + public AsyncLocal(Action<AsyncLocalValueChangedArgs<T>> valueChangedHandler) + { + m_valueChangedHandler = valueChangedHandler; + } + + public T Value + { + [SecuritySafeCritical] + get + { + object obj = ExecutionContext.GetLocalValue(this); + return (obj == null) ? default(T) : (T)obj; + } + [SecuritySafeCritical] + set + { + ExecutionContext.SetLocalValue(this, value, m_valueChangedHandler != null); + } + } + + [SecurityCritical] + void IAsyncLocal.OnValueChanged(object previousValueObj, object currentValueObj, bool contextChanged) + { + Contract.Assert(m_valueChangedHandler != null); + T previousValue = previousValueObj == null ? default(T) : (T)previousValueObj; + T currentValue = currentValueObj == null ? default(T) : (T)currentValueObj; + m_valueChangedHandler(new AsyncLocalValueChangedArgs<T>(previousValue, currentValue, contextChanged)); + } + } + + // + // Interface to allow non-generic code in ExecutionContext to call into the generic AsyncLocal<T> type. + // + internal interface IAsyncLocal + { + [SecurityCritical] + void OnValueChanged(object previousValue, object currentValue, bool contextChanged); + } + + public struct AsyncLocalValueChangedArgs<T> + { + public T PreviousValue { get; private set; } + public T CurrentValue { get; private set; } + + // + // If the value changed because we changed to a different ExecutionContext, this is true. If it changed + // because someone set the Value property, this is false. + // + public bool ThreadContextChanged { get; private set; } + + internal AsyncLocalValueChangedArgs(T previousValue, T currentValue, bool contextChanged) + : this() + { + PreviousValue = previousValue; + CurrentValue = currentValue; + ThreadContextChanged = contextChanged; + } + } +} diff --git a/src/mscorlib/src/System/Threading/AutoResetEvent.cs b/src/mscorlib/src/System/Threading/AutoResetEvent.cs new file mode 100644 index 0000000000..6fe6c06232 --- /dev/null +++ b/src/mscorlib/src/System/Threading/AutoResetEvent.cs @@ -0,0 +1,27 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +// +/*============================================================================= +** +** +** +** Purpose: An example of a WaitHandle class +** +** +=============================================================================*/ +namespace System.Threading { + + using System; + using System.Security.Permissions; + using System.Runtime.InteropServices; + + [HostProtection(Synchronization=true, ExternalThreading=true)] + [System.Runtime.InteropServices.ComVisible(true)] + public sealed class AutoResetEvent : EventWaitHandle + { + public AutoResetEvent(bool initialState) : base(initialState,EventResetMode.AutoReset){ } + } +} + diff --git a/src/mscorlib/src/System/Threading/CancellationToken.cs b/src/mscorlib/src/System/Threading/CancellationToken.cs new file mode 100644 index 0000000000..48a2344c31 --- /dev/null +++ b/src/mscorlib/src/System/Threading/CancellationToken.cs @@ -0,0 +1,499 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +// +//////////////////////////////////////////////////////////////////////////////// + +#pragma warning disable 0420 // turn off 'a reference to a volatile field will not be treated as volatile' during CAS. + +using System; +using System.Diagnostics; +using System.Runtime.InteropServices; +using System.Security.Permissions; +using System.Diagnostics.Contracts; +using System.Runtime; +using System.Runtime.CompilerServices; +using System.Security; + +namespace System.Threading +{ + /// <summary> + /// Propagates notification that operations should be canceled. + /// </summary> + /// <remarks> + /// <para> + /// A <see cref="CancellationToken"/> may be created directly in an unchangeable canceled or non-canceled state + /// using the CancellationToken's constructors. However, to have a CancellationToken that can change + /// from a non-canceled to a canceled state, + /// <see cref="System.Threading.CancellationTokenSource">CancellationTokenSource</see> must be used. + /// CancellationTokenSource exposes the associated CancellationToken that may be canceled by the source through its + /// <see cref="System.Threading.CancellationTokenSource.Token">Token</see> property. + /// </para> + /// <para> + /// Once canceled, a token may not transition to a non-canceled state, and a token whose + /// <see cref="CanBeCanceled"/> is false will never change to one that can be canceled. + /// </para> + /// <para> + /// All members of this struct are thread-safe and may be used concurrently from multiple threads. + /// </para> + /// </remarks> + [ComVisible(false)] + [HostProtection(Synchronization = true, ExternalThreading = true)] + [DebuggerDisplay("IsCancellationRequested = {IsCancellationRequested}")] + public struct CancellationToken + { + // The backing TokenSource. + // if null, it implicitly represents the same thing as new CancellationToken(false). + // When required, it will be instantiated to reflect this. + private CancellationTokenSource m_source; + //!! warning. If more fields are added, the assumptions in CreateLinkedToken may no longer be valid + + /* Properties */ + + /// <summary> + /// Returns an empty CancellationToken value. + /// </summary> + /// <remarks> + /// The <see cref="CancellationToken"/> value returned by this property will be non-cancelable by default. + /// </remarks> + public static CancellationToken None + { + get { return default(CancellationToken); } + } + + /// <summary> + /// Gets whether cancellation has been requested for this token. + /// </summary> + /// <value>Whether cancellation has been requested for this token.</value> + /// <remarks> + /// <para> + /// This property indicates whether cancellation has been requested for this token, + /// either through the token initially being construted in a canceled state, or through + /// calling <see cref="System.Threading.CancellationTokenSource.Cancel()">Cancel</see> + /// on the token's associated <see cref="CancellationTokenSource"/>. + /// </para> + /// <para> + /// If this property is true, it only guarantees that cancellation has been requested. + /// It does not guarantee that every registered handler + /// has finished executing, nor that cancellation requests have finished propagating + /// to all registered handlers. Additional synchronization may be required, + /// particularly in situations where related objects are being canceled concurrently. + /// </para> + /// </remarks> + public bool IsCancellationRequested + { + get + { + return m_source != null && m_source.IsCancellationRequested; + } + } + + /// <summary> + /// Gets whether this token is capable of being in the canceled state. + /// </summary> + /// <remarks> + /// If CanBeCanceled returns false, it is guaranteed that the token will never transition + /// into a canceled state, meaning that <see cref="IsCancellationRequested"/> will never + /// return true. + /// </remarks> + public bool CanBeCanceled + { + get + { + return m_source != null && m_source.CanBeCanceled; + } + } + + /// <summary> + /// Gets a <see cref="T:System.Threading.WaitHandle"/> that is signaled when the token is canceled.</summary> + /// <remarks> + /// Accessing this property causes a <see cref="T:System.Threading.WaitHandle">WaitHandle</see> + /// to be instantiated. It is preferable to only use this property when necessary, and to then + /// dispose the associated <see cref="CancellationTokenSource"/> instance at the earliest opportunity (disposing + /// the source will dispose of this allocated handle). The handle should not be closed or disposed directly. + /// </remarks> + /// <exception cref="T:System.ObjectDisposedException">The associated <see + /// cref="T:System.Threading.CancellationTokenSource">CancellationTokenSource</see> has been disposed.</exception> + public WaitHandle WaitHandle + { + get + { + if (m_source == null) + { + InitializeDefaultSource(); + } + + return m_source.WaitHandle; + } + } + + // public CancellationToken() + // this constructor is implicit for structs + // -> this should behaves exactly as for new CancellationToken(false) + + /// <summary> + /// Internal constructor only a CancellationTokenSource should create a CancellationToken + /// </summary> + internal CancellationToken(CancellationTokenSource source) + { + m_source = source; + } + + /// <summary> + /// Initializes the <see cref="T:System.Threading.CancellationToken">CancellationToken</see>. + /// </summary> + /// <param name="canceled"> + /// The canceled state for the token. + /// </param> + /// <remarks> + /// Tokens created with this constructor will remain in the canceled state specified + /// by the <paramref name="canceled"/> parameter. If <paramref name="canceled"/> is false, + /// both <see cref="CanBeCanceled"/> and <see cref="IsCancellationRequested"/> will be false. + /// If <paramref name="canceled"/> is true, + /// both <see cref="CanBeCanceled"/> and <see cref="IsCancellationRequested"/> will be true. + /// </remarks> + public CancellationToken(bool canceled) : + this() + { + if(canceled) + m_source = CancellationTokenSource.InternalGetStaticSource(canceled); + } + + /* Methods */ + + + private readonly static Action<Object> s_ActionToActionObjShunt = new Action<Object>(ActionToActionObjShunt); + private static void ActionToActionObjShunt(object obj) + { + Action action = obj as Action; + Contract.Assert(action != null, "Expected an Action here"); + action(); + } + + /// <summary> + /// Registers a delegate that will be called when this <see cref="T:System.Threading.CancellationToken">CancellationToken</see> is canceled. + /// </summary> + /// <remarks> + /// <para> + /// If this token is already in the canceled state, the + /// delegate will be run immediately and synchronously. Any exception the delegate generates will be + /// propagated out of this method call. + /// </para> + /// <para> + /// The current <see cref="System.Threading.ExecutionContext">ExecutionContext</see>, if one exists, will be captured + /// along with the delegate and will be used when executing it. + /// </para> + /// </remarks> + /// <param name="callback">The delegate to be executed when the <see cref="T:System.Threading.CancellationToken">CancellationToken</see> is canceled.</param> + /// <returns>The <see cref="T:System.Threading.CancellationTokenRegistration"/> instance that can + /// be used to deregister the callback.</returns> + /// <exception cref="T:System.ArgumentNullException"><paramref name="callback"/> is null.</exception> + public CancellationTokenRegistration Register(Action callback) + { + if (callback == null) + throw new ArgumentNullException("callback"); + + return Register( + s_ActionToActionObjShunt, + callback, + false, // useSync=false + true // useExecutionContext=true + ); + } + + /// <summary> + /// Registers a delegate that will be called when this + /// <see cref="T:System.Threading.CancellationToken">CancellationToken</see> is canceled. + /// </summary> + /// <remarks> + /// <para> + /// If this token is already in the canceled state, the + /// delegate will be run immediately and synchronously. Any exception the delegate generates will be + /// propagated out of this method call. + /// </para> + /// <para> + /// The current <see cref="System.Threading.ExecutionContext">ExecutionContext</see>, if one exists, will be captured + /// along with the delegate and will be used when executing it. + /// </para> + /// </remarks> + /// <param name="callback">The delegate to be executed when the <see cref="T:System.Threading.CancellationToken">CancellationToken</see> is canceled.</param> + /// <param name="useSynchronizationContext">A Boolean value that indicates whether to capture + /// the current <see cref="T:System.Threading.SynchronizationContext">SynchronizationContext</see> and use it + /// when invoking the <paramref name="callback"/>.</param> + /// <returns>The <see cref="T:System.Threading.CancellationTokenRegistration"/> instance that can + /// be used to deregister the callback.</returns> + /// <exception cref="T:System.ArgumentNullException"><paramref name="callback"/> is null.</exception> + public CancellationTokenRegistration Register(Action callback, bool useSynchronizationContext) + { + if (callback == null) + throw new ArgumentNullException("callback"); + + return Register( + s_ActionToActionObjShunt, + callback, + useSynchronizationContext, + true // useExecutionContext=true + ); + } + + /// <summary> + /// Registers a delegate that will be called when this + /// <see cref="T:System.Threading.CancellationToken">CancellationToken</see> is canceled. + /// </summary> + /// <remarks> + /// <para> + /// If this token is already in the canceled state, the + /// delegate will be run immediately and synchronously. Any exception the delegate generates will be + /// propagated out of this method call. + /// </para> + /// <para> + /// The current <see cref="System.Threading.ExecutionContext">ExecutionContext</see>, if one exists, will be captured + /// along with the delegate and will be used when executing it. + /// </para> + /// </remarks> + /// <param name="callback">The delegate to be executed when the <see cref="T:System.Threading.CancellationToken">CancellationToken</see> is canceled.</param> + /// <param name="state">The state to pass to the <paramref name="callback"/> when the delegate is invoked. This may be null.</param> + /// <returns>The <see cref="T:System.Threading.CancellationTokenRegistration"/> instance that can + /// be used to deregister the callback.</returns> + /// <exception cref="T:System.ArgumentNullException"><paramref name="callback"/> is null.</exception> + public CancellationTokenRegistration Register(Action<Object> callback, Object state) + { + if (callback == null) + throw new ArgumentNullException("callback"); + + return Register( + callback, + state, + false, // useSync=false + true // useExecutionContext=true + ); + } + + /// <summary> + /// Registers a delegate that will be called when this + /// <see cref="T:System.Threading.CancellationToken">CancellationToken</see> is canceled. + /// </summary> + /// <remarks> + /// <para> + /// If this token is already in the canceled state, the + /// delegate will be run immediately and synchronously. Any exception the delegate generates will be + /// propagated out of this method call. + /// </para> + /// <para> + /// The current <see cref="System.Threading.ExecutionContext">ExecutionContext</see>, if one exists, + /// will be captured along with the delegate and will be used when executing it. + /// </para> + /// </remarks> + /// <param name="callback">The delegate to be executed when the <see cref="T:System.Threading.CancellationToken">CancellationToken</see> is canceled.</param> + /// <param name="state">The state to pass to the <paramref name="callback"/> when the delegate is invoked. This may be null.</param> + /// <param name="useSynchronizationContext">A Boolean value that indicates whether to capture + /// the current <see cref="T:System.Threading.SynchronizationContext">SynchronizationContext</see> and use it + /// when invoking the <paramref name="callback"/>.</param> + /// <returns>The <see cref="T:System.Threading.CancellationTokenRegistration"/> instance that can + /// be used to deregister the callback.</returns> + /// <exception cref="T:System.ArgumentNullException"><paramref name="callback"/> is null.</exception> + /// <exception cref="T:System.ObjectDisposedException">The associated <see + /// cref="T:System.Threading.CancellationTokenSource">CancellationTokenSource</see> has been disposed.</exception> + public CancellationTokenRegistration Register(Action<Object> callback, Object state, bool useSynchronizationContext) + { + return Register( + callback, + state, + useSynchronizationContext, + true // useExecutionContext=true + ); + } + + // helper for internal registration needs that don't require an EC capture (e.g. creating linked token sources, or registering unstarted TPL tasks) + // has a handy signature, and skips capturing execution context. + internal CancellationTokenRegistration InternalRegisterWithoutEC(Action<object> callback, Object state) + { + return Register( + callback, + state, + false, // useSyncContext=false + false // useExecutionContext=false + ); + } + + // the real work.. + [SecuritySafeCritical] + [MethodImpl(MethodImplOptions.NoInlining)] + private CancellationTokenRegistration Register(Action<Object> callback, Object state, bool useSynchronizationContext, bool useExecutionContext) + { + StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller; + + if (callback == null) + throw new ArgumentNullException("callback"); + + if (CanBeCanceled == false) + { + return new CancellationTokenRegistration(); // nothing to do for tokens than can never reach the canceled state. Give them a dummy registration. + } + + // Capture sync/execution contexts if required. + // Note: Only capture sync/execution contexts if IsCancellationRequested = false + // as we know that if it is true that the callback will just be called synchronously. + + SynchronizationContext capturedSyncContext = null; + ExecutionContext capturedExecutionContext = null; + if (!IsCancellationRequested) + { + if (useSynchronizationContext) + capturedSyncContext = SynchronizationContext.Current; + if (useExecutionContext) + capturedExecutionContext = ExecutionContext.Capture( + ref stackMark, ExecutionContext.CaptureOptions.OptimizeDefaultCase); // ideally we'd also use IgnoreSyncCtx, but that could break compat + } + + // Register the callback with the source. + return m_source.InternalRegister(callback, state, capturedSyncContext, capturedExecutionContext); + } + + /// <summary> + /// Determines whether the current <see cref="T:System.Threading.CancellationToken">CancellationToken</see> instance is equal to the + /// specified token. + /// </summary> + /// <param name="other">The other <see cref="T:System.Threading.CancellationToken">CancellationToken</see> to which to compare this + /// instance.</param> + /// <returns>True if the instances are equal; otherwise, false. Two tokens are equal if they are associated + /// with the same <see cref="T:System.Threading.CancellationTokenSource">CancellationTokenSource</see> or if they were both constructed + /// from public CancellationToken constructors and their <see cref="IsCancellationRequested"/> values are equal.</returns> + public bool Equals(CancellationToken other) + { + //if both sources are null, then both tokens represent the Empty token. + if (m_source == null && other.m_source == null) + { + return true; + } + + // one is null but other has inflated the default source + // these are only equal if the inflated one is the staticSource(false) + if (m_source == null) + { + return other.m_source == CancellationTokenSource.InternalGetStaticSource(false); + } + + if (other.m_source == null) + { + return m_source == CancellationTokenSource.InternalGetStaticSource(false); + } + + // general case, we check if the sources are identical + + return m_source == other.m_source; + } + + /// <summary> + /// Determines whether the current <see cref="T:System.Threading.CancellationToken">CancellationToken</see> instance is equal to the + /// specified <see cref="T:System.Object"/>. + /// </summary> + /// <param name="other">The other object to which to compare this instance.</param> + /// <returns>True if <paramref name="other"/> is a <see cref="T:System.Threading.CancellationToken">CancellationToken</see> + /// and if the two instances are equal; otherwise, false. Two tokens are equal if they are associated + /// with the same <see cref="T:System.Threading.CancellationTokenSource">CancellationTokenSource</see> or if they were both constructed + /// from public CancellationToken constructors and their <see cref="IsCancellationRequested"/> values are equal.</returns> + /// <exception cref="T:System.ObjectDisposedException">An associated <see + /// cref="T:System.Threading.CancellationTokenSource">CancellationTokenSource</see> has been disposed.</exception> + public override bool Equals(Object other) + { + if (other is CancellationToken) + { + return Equals((CancellationToken) other); + } + + return false; + } + + /// <summary> + /// Serves as a hash function for a <see cref="T:System.Threading.CancellationToken">CancellationToken</see>. + /// </summary> + /// <returns>A hash code for the current <see cref="T:System.Threading.CancellationToken">CancellationToken</see> instance.</returns> + public override Int32 GetHashCode() + { + if (m_source == null) + { + // link to the common source so that we have a source to interrogate. + return CancellationTokenSource.InternalGetStaticSource(false).GetHashCode(); + } + + return m_source.GetHashCode(); + } + + /// <summary> + /// Determines whether two <see cref="T:System.Threading.CancellationToken">CancellationToken</see> instances are equal. + /// </summary> + /// <param name="left">The first instance.</param> + /// <param name="right">The second instance.</param> + /// <returns>True if the instances are equal; otherwise, false.</returns> + /// <exception cref="T:System.ObjectDisposedException">An associated <see + /// cref="T:System.Threading.CancellationTokenSource">CancellationTokenSource</see> has been disposed.</exception> + public static bool operator ==(CancellationToken left, CancellationToken right) + { + return left.Equals(right); + } + + /// <summary> + /// Determines whether two <see cref="T:System.Threading.CancellationToken">CancellationToken</see> instances are not equal. + /// </summary> + /// <param name="left">The first instance.</param> + /// <param name="right">The second instance.</param> + /// <returns>True if the instances are not equal; otherwise, false.</returns> + /// <exception cref="T:System.ObjectDisposedException">An associated <see + /// cref="T:System.Threading.CancellationTokenSource">CancellationTokenSource</see> has been disposed.</exception> + public static bool operator !=(CancellationToken left, CancellationToken right) + { + return !left.Equals(right); + } + + /// <summary> + /// Throws a <see cref="T:System.OperationCanceledException">OperationCanceledException</see> if + /// this token has had cancellation requested. + /// </summary> + /// <remarks> + /// This method provides functionality equivalent to: + /// <code> + /// if (token.IsCancellationRequested) + /// throw new OperationCanceledException(token); + /// </code> + /// </remarks> + /// <exception cref="System.OperationCanceledException">The token has had cancellation requested.</exception> + /// <exception cref="T:System.ObjectDisposedException">The associated <see + /// cref="T:System.Threading.CancellationTokenSource">CancellationTokenSource</see> has been disposed.</exception> + public void ThrowIfCancellationRequested() + { + if (IsCancellationRequested) + ThrowOperationCanceledException(); + } + + // Throw an ODE if this CancellationToken's source is disposed. + internal void ThrowIfSourceDisposed() + { + if ((m_source != null) && m_source.IsDisposed) + ThrowObjectDisposedException(); + } + + // Throws an OCE; separated out to enable better inlining of ThrowIfCancellationRequested + private void ThrowOperationCanceledException() + { + throw new OperationCanceledException(Environment.GetResourceString("OperationCanceled"), this); + } + + private static void ThrowObjectDisposedException() + { + throw new ObjectDisposedException(null, Environment.GetResourceString("CancellationToken_SourceDisposed")); + } + + // ----------------------------------- + // Private helpers + + private void InitializeDefaultSource() + { + // Lazy is slower, and although multiple threads may try and set m_source repeatedly, the race condition is benign. + // Alternative: LazyInititalizer.EnsureInitialized(ref m_source, ()=>CancellationTokenSource.InternalGetStaticSource(false)); + + m_source = CancellationTokenSource.InternalGetStaticSource(false); + } + } +} diff --git a/src/mscorlib/src/System/Threading/CancellationTokenRegistration.cs b/src/mscorlib/src/System/Threading/CancellationTokenRegistration.cs new file mode 100644 index 0000000000..34e0bb0aba --- /dev/null +++ b/src/mscorlib/src/System/Threading/CancellationTokenRegistration.cs @@ -0,0 +1,161 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +// +//////////////////////////////////////////////////////////////////////////////// + +using System.Diagnostics.Contracts; +using System.Security.Permissions; +using System.Runtime.CompilerServices; + +namespace System.Threading +{ + /// <summary> + /// Represents a callback delegate that has been registered with a <see cref="T:System.Threading.CancellationToken">CancellationToken</see>. + /// </summary> + /// <remarks> + /// To unregister a callback, dispose the corresponding Registration instance. + /// </remarks> + [HostProtection(Synchronization = true, ExternalThreading = true)] + public struct CancellationTokenRegistration : IEquatable<CancellationTokenRegistration>, IDisposable + { + private readonly CancellationCallbackInfo m_callbackInfo; + private readonly SparselyPopulatedArrayAddInfo<CancellationCallbackInfo> m_registrationInfo; + + internal CancellationTokenRegistration( + CancellationCallbackInfo callbackInfo, + SparselyPopulatedArrayAddInfo<CancellationCallbackInfo> registrationInfo) + { + m_callbackInfo = callbackInfo; + m_registrationInfo = registrationInfo; + } + + /// <summary> + /// Attempts to deregister the item. If it's already being run, this may fail. + /// Entails a full memory fence. + /// </summary> + /// <returns>True if the callback was found and deregistered, false otherwise.</returns> + [FriendAccessAllowed] + internal bool TryDeregister() + { + if (m_registrationInfo.Source == null) //can be null for dummy registrations. + return false; + + // Try to remove the callback info from the array. + // It is possible the callback info is missing (removed for run, or removed by someone else) + // It is also possible there is info in the array but it doesn't match our current registration's callback info. + CancellationCallbackInfo prevailingCallbackInfoInSlot = m_registrationInfo.Source.SafeAtomicRemove(m_registrationInfo.Index, m_callbackInfo); + + if (prevailingCallbackInfoInSlot != m_callbackInfo) + return false; //the callback in the slot wasn't us. + + return true; + } + + /// <summary> + /// Disposes of the registration and unregisters the target callback from the associated + /// <see cref="T:System.Threading.CancellationToken">CancellationToken</see>. + /// If the target callback is currently executing this method will wait until it completes, except + /// in the degenerate cases where a callback method deregisters itself. + /// </summary> + public void Dispose() + { + // Remove the entry from the array. + // This call includes a full memory fence which prevents potential reorderings of the reads below + bool deregisterOccurred = TryDeregister(); + + // We guarantee that we will not return if the callback is being executed (assuming we are not currently called by the callback itself) + // We achieve this by the following rules: + // 1. if we are called in the context of an executing callback, no need to wait (determined by tracking callback-executor threadID) + // - if the currently executing callback is this CTR, then waiting would deadlock. (We choose to return rather than deadlock) + // - if not, then this CTR cannot be the one executing, hence no need to wait + // + // 2. if deregistration failed, and we are on a different thread, then the callback may be running under control of cts.Cancel() + // => poll until cts.ExecutingCallback is not the one we are trying to deregister. + + var callbackInfo = m_callbackInfo; + if (callbackInfo != null) + { + var tokenSource = callbackInfo.CancellationTokenSource; + if (tokenSource.IsCancellationRequested && //running callbacks has commenced. + !tokenSource.IsCancellationCompleted && //running callbacks hasn't finished + !deregisterOccurred && //deregistration failed (ie the callback is missing from the list) + tokenSource.ThreadIDExecutingCallbacks != Thread.CurrentThread.ManagedThreadId) //the executingThreadID is not this threadID. + { + // Callback execution is in progress, the executing thread is different to us and has taken the callback for execution + // so observe and wait until this target callback is no longer the executing callback. + tokenSource.WaitForCallbackToComplete(m_callbackInfo); + } + } + } + + /// <summary> + /// Determines whether two <see + /// cref="T:System.Threading.CancellationTokenRegistration">CancellationTokenRegistration</see> + /// instances are equal. + /// </summary> + /// <param name="left">The first instance.</param> + /// <param name="right">The second instance.</param> + /// <returns>True if the instances are equal; otherwise, false.</returns> + public static bool operator ==(CancellationTokenRegistration left, CancellationTokenRegistration right) + { + return left.Equals(right); + } + + /// <summary> + /// Determines whether two <see cref="T:System.Threading.CancellationTokenRegistration">CancellationTokenRegistration</see> instances are not equal. + /// </summary> + /// <param name="left">The first instance.</param> + /// <param name="right">The second instance.</param> + /// <returns>True if the instances are not equal; otherwise, false.</returns> + public static bool operator !=(CancellationTokenRegistration left, CancellationTokenRegistration right) + { + return !left.Equals(right); + } + + /// <summary> + /// Determines whether the current <see cref="T:System.Threading.CancellationTokenRegistration">CancellationTokenRegistration</see> instance is equal to the + /// specified <see cref="T:System.Object"/>. + /// </summary> + /// <param name="obj">The other object to which to compare this instance.</param> + /// <returns>True, if both this and <paramref name="obj"/> are equal. False, otherwise. + /// Two <see cref="T:System.Threading.CancellationTokenRegistration">CancellationTokenRegistration</see> instances are equal if + /// they both refer to the output of a single call to the same Register method of a + /// <see cref="T:System.Threading.CancellationToken">CancellationToken</see>. + /// </returns> + public override bool Equals(object obj) + { + return ((obj is CancellationTokenRegistration) && Equals((CancellationTokenRegistration) obj)); + } + + /// <summary> + /// Determines whether the current <see cref="T:System.Threading.CancellationToken">CancellationToken</see> instance is equal to the + /// specified <see cref="T:System.Object"/>. + /// </summary> + /// <param name="other">The other <see cref="T:System.Threading.CancellationTokenRegistration">CancellationTokenRegistration</see> to which to compare this instance.</param> + /// <returns>True, if both this and <paramref name="other"/> are equal. False, otherwise. + /// Two <see cref="T:System.Threading.CancellationTokenRegistration">CancellationTokenRegistration</see> instances are equal if + /// they both refer to the output of a single call to the same Register method of a + /// <see cref="T:System.Threading.CancellationToken">CancellationToken</see>. + /// </returns> + public bool Equals(CancellationTokenRegistration other) + { + return m_callbackInfo == other.m_callbackInfo && + m_registrationInfo.Source == other.m_registrationInfo.Source && + m_registrationInfo.Index == other.m_registrationInfo.Index; + } + + /// <summary> + /// Serves as a hash function for a <see cref="T:System.Threading.CancellationTokenRegistration">CancellationTokenRegistration.</see>. + /// </summary> + /// <returns>A hash code for the current <see cref="T:System.Threading.CancellationTokenRegistration">CancellationTokenRegistration</see> instance.</returns> + public override int GetHashCode() + { + if (m_registrationInfo.Source != null) + return m_registrationInfo.Source.GetHashCode() ^ m_registrationInfo.Index.GetHashCode(); + + return m_registrationInfo.Index.GetHashCode(); + } + } +} diff --git a/src/mscorlib/src/System/Threading/CancellationTokenSource.cs b/src/mscorlib/src/System/Threading/CancellationTokenSource.cs new file mode 100644 index 0000000000..954cd38344 --- /dev/null +++ b/src/mscorlib/src/System/Threading/CancellationTokenSource.cs @@ -0,0 +1,1269 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. +#pragma warning disable 0420 + +// +//////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Security; +using System.Collections.Generic; +using System.Runtime.InteropServices; +using System.Security.Permissions; +using System.Diagnostics.Contracts; +using System.Runtime; + +namespace System.Threading +{ + /// <summary> + /// Signals to a <see cref="System.Threading.CancellationToken"/> that it should be canceled. + /// </summary> + /// <remarks> + /// <para> + /// <see cref="T:System.Threading.CancellationTokenSource"/> is used to instantiate a <see + /// cref="T:System.Threading.CancellationToken"/> + /// (via the source's <see cref="System.Threading.CancellationTokenSource.Token">Token</see> property) + /// that can be handed to operations that wish to be notified of cancellation or that can be used to + /// register asynchronous operations for cancellation. That token may have cancellation requested by + /// calling to the source's <see cref="System.Threading.CancellationTokenSource.Cancel()">Cancel</see> + /// method. + /// </para> + /// <para> + /// All members of this class, except <see cref="Dispose">Dispose</see>, are thread-safe and may be used + /// concurrently from multiple threads. + /// </para> + /// </remarks> + [ComVisible(false)] + [HostProtection(Synchronization = true, ExternalThreading = true)] + + public class CancellationTokenSource : IDisposable + { + //static sources that can be used as the backing source for 'fixed' CancellationTokens that never change state. + private static readonly CancellationTokenSource _staticSource_Set = new CancellationTokenSource(true); + private static readonly CancellationTokenSource _staticSource_NotCancelable = new CancellationTokenSource(false); + + //Note: the callback lists array is only created on first registration. + // the actual callback lists are only created on demand. + // Storing a registered callback costs around >60bytes, hence some overhead for the lists array is OK + // At most 24 lists seems reasonable, and caps the cost of the listsArray to 96bytes(32-bit,24-way) or 192bytes(64-bit,24-way). + private static readonly int s_nLists = (PlatformHelper.ProcessorCount > 24) ? 24 : PlatformHelper.ProcessorCount; + + private volatile ManualResetEvent m_kernelEvent; //lazily initialized if required. + + private volatile SparselyPopulatedArray<CancellationCallbackInfo>[] m_registeredCallbacksLists; + + // legal values for m_state + private const int CANNOT_BE_CANCELED = 0; + private const int NOT_CANCELED = 1; + private const int NOTIFYING = 2; + private const int NOTIFYINGCOMPLETE = 3; + + //m_state uses the pattern "volatile int32 reads, with cmpxch writes" which is safe for updates and cannot suffer torn reads. + private volatile int m_state; + + + /// The ID of the thread currently executing the main body of CTS.Cancel() + /// this helps us to know if a call to ctr.Dispose() is running 'within' a cancellation callback. + /// This is updated as we move between the main thread calling cts.Cancel() and any syncContexts that are used to + /// actually run the callbacks. + private volatile int m_threadIDExecutingCallbacks = -1; + + private bool m_disposed; + + // we track the running callback to assist ctr.Dispose() to wait for the target callback to complete. + private volatile CancellationCallbackInfo m_executingCallback; + + // provided for CancelAfter and timer-related constructors + private volatile Timer m_timer; + + // ---------------------- + // ** public properties + + /// <summary> + /// Gets whether cancellation has been requested for this <see + /// cref="System.Threading.CancellationTokenSource">CancellationTokenSource</see>. + /// </summary> + /// <value>Whether cancellation has been requested for this <see + /// cref="System.Threading.CancellationTokenSource">CancellationTokenSource</see>.</value> + /// <remarks> + /// <para> + /// This property indicates whether cancellation has been requested for this token source, such as + /// due to a call to its + /// <see cref="System.Threading.CancellationTokenSource.Cancel()">Cancel</see> method. + /// </para> + /// <para> + /// If this property returns true, it only guarantees that cancellation has been requested. It does not + /// guarantee that every handler registered with the corresponding token has finished executing, nor + /// that cancellation requests have finished propagating to all registered handlers. Additional + /// synchronization may be required, particularly in situations where related objects are being + /// canceled concurrently. + /// </para> + /// </remarks> + public bool IsCancellationRequested + { + get { return m_state >= NOTIFYING; } + } + + /// <summary> + /// A simple helper to determine whether cancellation has finished. + /// </summary> + internal bool IsCancellationCompleted + { + get { return m_state == NOTIFYINGCOMPLETE; } + } + + /// <summary> + /// A simple helper to determine whether disposal has occurred. + /// </summary> + internal bool IsDisposed + { + get { return m_disposed; } + } + + /// <summary> + /// The ID of the thread that is running callbacks. + /// </summary> + internal int ThreadIDExecutingCallbacks + { + set { m_threadIDExecutingCallbacks = value; } + get { return m_threadIDExecutingCallbacks; } + } + + /// <summary> + /// Gets the <see cref="System.Threading.CancellationToken">CancellationToken</see> + /// associated with this <see cref="CancellationTokenSource"/>. + /// </summary> + /// <value>The <see cref="System.Threading.CancellationToken">CancellationToken</see> + /// associated with this <see cref="CancellationTokenSource"/>.</value> + /// <exception cref="T:System.ObjectDisposedException">The token source has been + /// disposed.</exception> + public CancellationToken Token + { + get + { + ThrowIfDisposed(); + return new CancellationToken(this); + } + } + + // ---------------------- + // ** internal and private properties. + + /// <summary> + /// + /// </summary> + internal bool CanBeCanceled + { + get { return m_state != CANNOT_BE_CANCELED; } + } + + /// <summary> + /// + /// </summary> + internal WaitHandle WaitHandle + { + get + { + ThrowIfDisposed(); + + // fast path if already allocated. + if (m_kernelEvent != null) + return m_kernelEvent; + + // lazy-init the mre. + ManualResetEvent mre = new ManualResetEvent(false); + if (Interlocked.CompareExchange(ref m_kernelEvent, mre, null) != null) + { + ((IDisposable)mre).Dispose(); + } + + // There is a race condition between checking IsCancellationRequested and setting the event. + // However, at this point, the kernel object definitely exists and the cases are: + // 1. if IsCancellationRequested = true, then we will call Set() + // 2. if IsCancellationRequested = false, then NotifyCancellation will see that the event exists, and will call Set(). + if (IsCancellationRequested) + m_kernelEvent.Set(); + + return m_kernelEvent; + } + } + + + /// <summary> + /// The currently executing callback + /// </summary> + internal CancellationCallbackInfo ExecutingCallback + { + get { return m_executingCallback; } + } + +#if DEBUG + /// <summary> + /// Used by the dev unit tests to check the number of outstanding registrations. + /// They use private reflection to gain access. Because this would be dead retail + /// code, however, it is ifdef'd out to work only in debug builds. + /// </summary> + private int CallbackCount + { + get + { + SparselyPopulatedArray<CancellationCallbackInfo>[] callbackLists = m_registeredCallbacksLists; + if (callbackLists == null) + return 0; + + int count = 0; + foreach(SparselyPopulatedArray<CancellationCallbackInfo> sparseArray in callbackLists) + { + if(sparseArray != null) + { + SparselyPopulatedArrayFragment<CancellationCallbackInfo> currCallbacks = sparseArray.Head; + while (currCallbacks != null) + { + for (int i = 0; i < currCallbacks.Length; i++) + if (currCallbacks[i] != null) + count++; + + currCallbacks = currCallbacks.Next; + } + } + } + return count; + } + } +#endif + + // ** Public Constructors + + /// <summary> + /// Initializes the <see cref="T:System.Threading.CancellationTokenSource"/>. + /// </summary> + public CancellationTokenSource() + { + m_state = NOT_CANCELED; + } + + // ** Private constructors for static sources. + // set=false ==> cannot be canceled. + // set=true ==> is canceled. + private CancellationTokenSource(bool set) + { + m_state = set ? NOTIFYINGCOMPLETE : CANNOT_BE_CANCELED; + } + + /// <summary> + /// Constructs a <see cref="T:System.Threading.CancellationTokenSource"/> that will be canceled after a specified time span. + /// </summary> + /// <param name="delay">The time span to wait before canceling this <see cref="T:System.Threading.CancellationTokenSource"/></param> + /// <exception cref="T:System.ArgumentOutOfRangeException"> + /// The exception that is thrown when <paramref name="delay"/> is less than -1 or greater than Int32.MaxValue. + /// </exception> + /// <remarks> + /// <para> + /// The countdown for the delay starts during the call to the constructor. When the delay expires, + /// the constructed <see cref="T:System.Threading.CancellationTokenSource"/> is canceled, if it has + /// not been canceled already. + /// </para> + /// <para> + /// Subsequent calls to CancelAfter will reset the delay for the constructed + /// <see cref="T:System.Threading.CancellationTokenSource"/>, if it has not been + /// canceled already. + /// </para> + /// </remarks> + public CancellationTokenSource(TimeSpan delay) + { + long totalMilliseconds = (long)delay.TotalMilliseconds; + if (totalMilliseconds < -1 || totalMilliseconds > Int32.MaxValue) + { + throw new ArgumentOutOfRangeException("delay"); + } + + InitializeWithTimer((int)totalMilliseconds); + } + + /// <summary> + /// Constructs a <see cref="T:System.Threading.CancellationTokenSource"/> that will be canceled after a specified time span. + /// </summary> + /// <param name="millisecondsDelay">The time span to wait before canceling this <see cref="T:System.Threading.CancellationTokenSource"/></param> + /// <exception cref="T:System.ArgumentOutOfRangeException"> + /// The exception that is thrown when <paramref name="millisecondsDelay"/> is less than -1. + /// </exception> + /// <remarks> + /// <para> + /// The countdown for the millisecondsDelay starts during the call to the constructor. When the millisecondsDelay expires, + /// the constructed <see cref="T:System.Threading.CancellationTokenSource"/> is canceled (if it has + /// not been canceled already). + /// </para> + /// <para> + /// Subsequent calls to CancelAfter will reset the millisecondsDelay for the constructed + /// <see cref="T:System.Threading.CancellationTokenSource"/>, if it has not been + /// canceled already. + /// </para> + /// </remarks> + public CancellationTokenSource(Int32 millisecondsDelay) + { + if (millisecondsDelay < -1) + { + throw new ArgumentOutOfRangeException("millisecondsDelay"); + } + + InitializeWithTimer(millisecondsDelay); + } + + // Common initialization logic when constructing a CTS with a delay parameter + private void InitializeWithTimer(Int32 millisecondsDelay) + { + m_state = NOT_CANCELED; + m_timer = new Timer(s_timerCallback, this, millisecondsDelay, -1); + } + + // ** Public Methods + + /// <summary> + /// Communicates a request for cancellation. + /// </summary> + /// <remarks> + /// <para> + /// The associated <see cref="T:System.Threading.CancellationToken" /> will be + /// notified of the cancellation and will transition to a state where + /// <see cref="System.Threading.CancellationToken.IsCancellationRequested">IsCancellationRequested</see> returns true. + /// Any callbacks or cancelable operations + /// registered with the <see cref="T:System.Threading.CancellationToken"/> will be executed. + /// </para> + /// <para> + /// Cancelable operations and callbacks registered with the token should not throw exceptions. + /// However, this overload of Cancel will aggregate any exceptions thrown into a <see cref="System.AggregateException"/>, + /// such that one callback throwing an exception will not prevent other registered callbacks from being executed. + /// </para> + /// <para> + /// The <see cref="T:System.Threading.ExecutionContext"/> that was captured when each callback was registered + /// will be reestablished when the callback is invoked. + /// </para> + /// </remarks> + /// <exception cref="T:System.AggregateException">An aggregate exception containing all the exceptions thrown + /// by the registered callbacks on the associated <see cref="T:System.Threading.CancellationToken"/>.</exception> + /// <exception cref="T:System.ObjectDisposedException">This <see + /// cref="T:System.Threading.CancellationTokenSource"/> has been disposed.</exception> + public void Cancel() + { + Cancel(false); + } + + /// <summary> + /// Communicates a request for cancellation. + /// </summary> + /// <remarks> + /// <para> + /// The associated <see cref="T:System.Threading.CancellationToken" /> will be + /// notified of the cancellation and will transition to a state where + /// <see cref="System.Threading.CancellationToken.IsCancellationRequested">IsCancellationRequested</see> returns true. + /// Any callbacks or cancelable operations + /// registered with the <see cref="T:System.Threading.CancellationToken"/> will be executed. + /// </para> + /// <para> + /// Cancelable operations and callbacks registered with the token should not throw exceptions. + /// If <paramref name="throwOnFirstException"/> is true, an exception will immediately propagate out of the + /// call to Cancel, preventing the remaining callbacks and cancelable operations from being processed. + /// If <paramref name="throwOnFirstException"/> is false, this overload will aggregate any + /// exceptions thrown into a <see cref="System.AggregateException"/>, + /// such that one callback throwing an exception will not prevent other registered callbacks from being executed. + /// </para> + /// <para> + /// The <see cref="T:System.Threading.ExecutionContext"/> that was captured when each callback was registered + /// will be reestablished when the callback is invoked. + /// </para> + /// </remarks> + /// <param name="throwOnFirstException">Specifies whether exceptions should immediately propagate.</param> + /// <exception cref="T:System.AggregateException">An aggregate exception containing all the exceptions thrown + /// by the registered callbacks on the associated <see cref="T:System.Threading.CancellationToken"/>.</exception> + /// <exception cref="T:System.ObjectDisposedException">This <see + /// cref="T:System.Threading.CancellationTokenSource"/> has been disposed.</exception> + public void Cancel(bool throwOnFirstException) + { + ThrowIfDisposed(); + NotifyCancellation(throwOnFirstException); + } + + /// <summary> + /// Schedules a Cancel operation on this <see cref="T:System.Threading.CancellationTokenSource"/>. + /// </summary> + /// <param name="delay">The time span to wait before canceling this <see + /// cref="T:System.Threading.CancellationTokenSource"/>. + /// </param> + /// <exception cref="T:System.ObjectDisposedException">The exception thrown when this <see + /// cref="T:System.Threading.CancellationTokenSource"/> has been disposed. + /// </exception> + /// <exception cref="T:System.ArgumentOutOfRangeException"> + /// The exception thrown when <paramref name="delay"/> is less than -1 or + /// greater than Int32.MaxValue. + /// </exception> + /// <remarks> + /// <para> + /// The countdown for the delay starts during this call. When the delay expires, + /// this <see cref="T:System.Threading.CancellationTokenSource"/> is canceled, if it has + /// not been canceled already. + /// </para> + /// <para> + /// Subsequent calls to CancelAfter will reset the delay for this + /// <see cref="T:System.Threading.CancellationTokenSource"/>, if it has not been + /// canceled already. + /// </para> + /// </remarks> + public void CancelAfter(TimeSpan delay) + { + long totalMilliseconds = (long)delay.TotalMilliseconds; + if (totalMilliseconds < -1 || totalMilliseconds > Int32.MaxValue) + { + throw new ArgumentOutOfRangeException("delay"); + } + + CancelAfter((int)totalMilliseconds); + } + + /// <summary> + /// Schedules a Cancel operation on this <see cref="T:System.Threading.CancellationTokenSource"/>. + /// </summary> + /// <param name="millisecondsDelay">The time span to wait before canceling this <see + /// cref="T:System.Threading.CancellationTokenSource"/>. + /// </param> + /// <exception cref="T:System.ObjectDisposedException">The exception thrown when this <see + /// cref="T:System.Threading.CancellationTokenSource"/> has been disposed. + /// </exception> + /// <exception cref="T:System.ArgumentOutOfRangeException"> + /// The exception thrown when <paramref name="millisecondsDelay"/> is less than -1. + /// </exception> + /// <remarks> + /// <para> + /// The countdown for the millisecondsDelay starts during this call. When the millisecondsDelay expires, + /// this <see cref="T:System.Threading.CancellationTokenSource"/> is canceled, if it has + /// not been canceled already. + /// </para> + /// <para> + /// Subsequent calls to CancelAfter will reset the millisecondsDelay for this + /// <see cref="T:System.Threading.CancellationTokenSource"/>, if it has not been + /// canceled already. + /// </para> + /// </remarks> + public void CancelAfter(Int32 millisecondsDelay) + { + ThrowIfDisposed(); + + if (millisecondsDelay < -1) + { + throw new ArgumentOutOfRangeException("millisecondsDelay"); + } + + if (IsCancellationRequested) return; + + // There is a race condition here as a Cancel could occur between the check of + // IsCancellationRequested and the creation of the timer. This is benign; in the + // worst case, a timer will be created that has no effect when it expires. + + // Also, if Dispose() is called right here (after ThrowIfDisposed(), before timer + // creation), it would result in a leaked Timer object (at least until the timer + // expired and Disposed itself). But this would be considered bad behavior, as + // Dispose() is not thread-safe and should not be called concurrently with CancelAfter(). + + if (m_timer == null) + { + // Lazily initialize the timer in a thread-safe fashion. + // Initially set to "never go off" because we don't want to take a + // chance on a timer "losing" the initialization and then + // cancelling the token before it (the timer) can be disposed. + Timer newTimer = new Timer(s_timerCallback, this, -1, -1); + if (Interlocked.CompareExchange(ref m_timer, newTimer, null) != null) + { + // We did not initialize the timer. Dispose the new timer. + newTimer.Dispose(); + } + } + + + // It is possible that m_timer has already been disposed, so we must do + // the following in a try/catch block. + try + { + m_timer.Change(millisecondsDelay, -1); + } + catch (ObjectDisposedException) + { + // Just eat the exception. There is no other way to tell that + // the timer has been disposed, and even if there were, there + // would not be a good way to deal with the observe/dispose + // race condition. + } + + } + + private static readonly TimerCallback s_timerCallback = new TimerCallback(TimerCallbackLogic); + + // Common logic for a timer delegate + private static void TimerCallbackLogic(object obj) + { + CancellationTokenSource cts = (CancellationTokenSource)obj; + + // Cancel the source; handle a race condition with cts.Dispose() + if (!cts.IsDisposed) + { + // There is a small window for a race condition where a cts.Dispose can sneak + // in right here. I'll wrap the cts.Cancel() in a try/catch to proof us + // against this race condition. + try + { + cts.Cancel(); // will take care of disposing of m_timer + } + catch (ObjectDisposedException) + { + // If the ODE was not due to the target cts being disposed, then propagate the ODE. + if (!cts.IsDisposed) throw; + } + } + } + + /// <summary> + /// Releases the resources used by this <see cref="T:System.Threading.CancellationTokenSource" />. + /// </summary> + /// <remarks> + /// This method is not thread-safe for any other concurrent calls. + /// </remarks> + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + /// <summary> + /// Releases the unmanaged resources used by the <see cref="T:System.Threading.CancellationTokenSource" /> class and optionally releases the managed resources. + /// </summary> + /// <param name="disposing">true to release both managed and unmanaged resources; false to release only unmanaged resources.</param> + protected virtual void Dispose(bool disposing) + { + // There is nothing to do if disposing=false because the CancellationTokenSource holds no unmanaged resources. + + if (disposing && !m_disposed) + { + //NOTE: We specifically tolerate that a callback can be deregistered + // after the CTS has been disposed and/or concurrently with cts.Dispose(). + // This is safe without locks because the reg.Dispose() only + // mutates a sparseArrayFragment and then reads from properties of the CTS that are not + // invalidated by cts.Dispose(). + // + // We also tolerate that a callback can be registered after the CTS has been + // disposed. This is safe without locks because InternalRegister is tolerant + // of m_registeredCallbacksLists becoming null during its execution. However, + // we run the acceptable risk of m_registeredCallbacksLists getting reinitialized + // to non-null if there is a race between Dispose and Register, in which case this + // instance may unnecessarily hold onto a registered callback. But that's no worse + // than if Dispose wasn't safe to use concurrently, as Dispose would never be called, + // and thus no handlers would be dropped. + // + // And, we tolerate Dispose being used concurrently with Cancel. This is necessary + // to properly support LinkedCancellationTokenSource, where, due to common usage patterns, + // it's possible for this pairing to occur with valid usage (e.g. a component accepts + // an external CancellationToken and uses CreateLinkedTokenSource to combine it with an + // internal source of cancellation, then Disposes of that linked source, which could + // happen at the same time the external entity is requesting cancellation). + + m_timer?.Dispose(); // Timer.Dispose is thread-safe + + // registered callbacks are now either complete or will never run, due to guarantees made by ctr.Dispose() + // so we can now perform main disposal work without risk of linking callbacks trying to use this CTS. + + m_registeredCallbacksLists = null; // free for GC; Cancel correctly handles a null field + + // If a kernel event was created via WaitHandle, we'd like to Dispose of it. However, + // we only want to do so if it's not being used by Cancel concurrently. First, we + // interlocked exchange it to be null, and then we check whether cancellation is currently + // in progress. NotifyCancellation will only try to set the event if it exists after it's + // transitioned to and while it's in the NOTIFYING state. + if (m_kernelEvent != null) + { + ManualResetEvent mre = Interlocked.Exchange(ref m_kernelEvent, null); + if (mre != null && m_state != NOTIFYING) + { + mre.Dispose(); + } + } + + m_disposed = true; + } + } + + // -- Internal methods. + + /// <summary> + /// Throws an exception if the source has been disposed. + /// </summary> + internal void ThrowIfDisposed() + { + if (m_disposed) + ThrowObjectDisposedException(); + } + + // separation enables inlining of ThrowIfDisposed + private static void ThrowObjectDisposedException() + { + throw new ObjectDisposedException(null, Environment.GetResourceString("CancellationTokenSource_Disposed")); + } + + /// <summary> + /// InternalGetStaticSource() + /// </summary> + /// <param name="set">Whether the source should be set.</param> + /// <returns>A static source to be shared among multiple tokens.</returns> + internal static CancellationTokenSource InternalGetStaticSource(bool set) + { + return set ? _staticSource_Set : _staticSource_NotCancelable; + } + + /// <summary> + /// Registers a callback object. If cancellation has already occurred, the + /// callback will have been run by the time this method returns. + /// </summary> + internal CancellationTokenRegistration InternalRegister( + Action<object> callback, object stateForCallback, SynchronizationContext targetSyncContext, ExecutionContext executionContext) + { + if (AppContextSwitches.ThrowExceptionIfDisposedCancellationTokenSource) + { + ThrowIfDisposed(); + } + + // the CancellationToken has already checked that the token is cancelable before calling this method. + Contract.Assert(CanBeCanceled, "Cannot register for uncancelable token src"); + + // if not canceled, register the event handlers + // if canceled already, run the callback synchronously + // Apart from the semantics of late-enlistment, this also ensures that during ExecuteCallbackHandlers() there + // will be no mutation of the _registeredCallbacks list + + if (!IsCancellationRequested) + { + // In order to enable code to not leak too many handlers, we allow Dispose to be called concurrently + // with Register. While this is not a recommended practice, consumers can and do use it this way. + // We don't make any guarantees about whether the CTS will hold onto the supplied callback + // if the CTS has already been disposed when the callback is registered, but we try not to + // while at the same time not paying any non-negligible overhead. The simple compromise + // is to check whether we're disposed (not volatile), and if we see we are, to return an empty + // registration, just as if CanBeCanceled was false for the check made in CancellationToken.Register. + // If there's a race and m_disposed is false even though it's been disposed, or if the disposal request + // comes in after this line, we simply run the minor risk of having m_registeredCallbacksLists reinitialized + // (after it was cleared to null during Dispose). + + if (m_disposed && !AppContextSwitches.ThrowExceptionIfDisposedCancellationTokenSource) + return new CancellationTokenRegistration(); + + int myIndex = Thread.CurrentThread.ManagedThreadId % s_nLists; + + CancellationCallbackInfo callbackInfo = targetSyncContext != null ? + new CancellationCallbackInfo.WithSyncContext(callback, stateForCallback, executionContext, this, targetSyncContext) : + new CancellationCallbackInfo(callback, stateForCallback, executionContext, this); + + //allocate the callback list array + var registeredCallbacksLists = m_registeredCallbacksLists; + if (registeredCallbacksLists == null) + { + SparselyPopulatedArray<CancellationCallbackInfo>[] list = new SparselyPopulatedArray<CancellationCallbackInfo>[s_nLists]; + registeredCallbacksLists = Interlocked.CompareExchange(ref m_registeredCallbacksLists, list, null); + if (registeredCallbacksLists == null) registeredCallbacksLists = list; + } + + //allocate the actual lists on-demand to save mem in low-use situations, and to avoid false-sharing. + var callbacks = Volatile.Read<SparselyPopulatedArray<CancellationCallbackInfo>>(ref registeredCallbacksLists[myIndex]); + if (callbacks == null) + { + SparselyPopulatedArray<CancellationCallbackInfo> callBackArray = new SparselyPopulatedArray<CancellationCallbackInfo>(4); + Interlocked.CompareExchange(ref (registeredCallbacksLists[myIndex]), callBackArray, null); + callbacks = registeredCallbacksLists[myIndex]; + } + + // Now add the registration to the list. + SparselyPopulatedArrayAddInfo<CancellationCallbackInfo> addInfo = callbacks.Add(callbackInfo); + CancellationTokenRegistration registration = new CancellationTokenRegistration(callbackInfo, addInfo); + + if (!IsCancellationRequested) + return registration; + + // If a cancellation has since come in, we will try to undo the registration and run the callback ourselves. + // (this avoids leaving the callback orphaned) + bool deregisterOccurred = registration.TryDeregister(); + + if (!deregisterOccurred) + { + // The thread that is running Cancel() snagged our callback for execution. + // So we don't need to run it, but we do return the registration so that + // ctr.Dispose() will wait for callback completion. + return registration; + } + } + + // If cancellation already occurred, we run the callback on this thread and return an empty registration. + callback(stateForCallback); + return new CancellationTokenRegistration(); + } + + /// <summary> + /// + /// </summary> + private void NotifyCancellation(bool throwOnFirstException) + { + // fast-path test to check if Notify has been called previously + if (IsCancellationRequested) + return; + + // If we're the first to signal cancellation, do the main extra work. + if (Interlocked.CompareExchange(ref m_state, NOTIFYING, NOT_CANCELED) == NOT_CANCELED) + { + // Dispose of the timer, if any. Dispose may be running concurrently here, but Timer.Dispose is thread-safe. + m_timer?.Dispose(); + + // Record the threadID being used for running the callbacks. + ThreadIDExecutingCallbacks = Thread.CurrentThread.ManagedThreadId; + + // Set the event if it's been lazily initialized and hasn't yet been disposed of. Dispose may + // be running concurrently, in which case either it'll have set m_kernelEvent back to null and + // we won't see it here, or it'll see that we've transitioned to NOTIFYING and will skip disposing it, + // leaving cleanup to finalization. + m_kernelEvent?.Set(); // update the MRE value. + + // - late enlisters to the Canceled event will have their callbacks called immediately in the Register() methods. + // - Callbacks are not called inside a lock. + // - After transition, no more delegates will be added to the + // - list of handlers, and hence it can be consumed and cleared at leisure by ExecuteCallbackHandlers. + ExecuteCallbackHandlers(throwOnFirstException); + Contract.Assert(IsCancellationCompleted, "Expected cancellation to have finished"); + } + } + + /// <summary> + /// Invoke the Canceled event. + /// </summary> + /// <remarks> + /// The handlers are invoked synchronously in LIFO order. + /// </remarks> + private void ExecuteCallbackHandlers(bool throwOnFirstException) + { + Contract.Assert(IsCancellationRequested, "ExecuteCallbackHandlers should only be called after setting IsCancellationRequested->true"); + Contract.Assert(ThreadIDExecutingCallbacks != -1, "ThreadIDExecutingCallbacks should have been set."); + + // Design decision: call the delegates in LIFO order so that callbacks fire 'deepest first'. + // This is intended to help with nesting scenarios so that child enlisters cancel before their parents. + List<Exception> exceptionList = null; + SparselyPopulatedArray<CancellationCallbackInfo>[] callbackLists = m_registeredCallbacksLists; + + // If there are no callbacks to run, we can safely exit. Any race conditions to lazy initialize it + // will see IsCancellationRequested and will then run the callback themselves. + if (callbackLists == null) + { + Interlocked.Exchange(ref m_state, NOTIFYINGCOMPLETE); + return; + } + + try + { + for (int index = 0; index < callbackLists.Length; index++) + { + SparselyPopulatedArray<CancellationCallbackInfo> list = Volatile.Read<SparselyPopulatedArray<CancellationCallbackInfo>>(ref callbackLists[index]); + if (list != null) + { + SparselyPopulatedArrayFragment<CancellationCallbackInfo> currArrayFragment = list.Tail; + + while (currArrayFragment != null) + { + for (int i = currArrayFragment.Length - 1; i >= 0; i--) + { + // 1a. publish the indended callback, to ensure ctr.Dipose can tell if a wait is necessary. + // 1b. transition to the target syncContext and continue there.. + // On the target SyncContext. + // 2. actually remove the callback + // 3. execute the callback + // re:#2 we do the remove on the syncCtx so that we can be sure we have control of the syncCtx before + // grabbing the callback. This prevents a deadlock if ctr.Dispose() might run on the syncCtx too. + m_executingCallback = currArrayFragment[i]; + if (m_executingCallback != null) + { + //Transition to the target sync context (if necessary), and continue our work there. + CancellationCallbackCoreWorkArguments args = new CancellationCallbackCoreWorkArguments(currArrayFragment, i); + + // marshal exceptions: either aggregate or perform an immediate rethrow + // We assume that syncCtx.Send() has forwarded on user exceptions when appropriate. + try + { + var wsc = m_executingCallback as CancellationCallbackInfo.WithSyncContext; + if (wsc != null) + { + Contract.Assert(wsc.TargetSyncContext != null, "Should only have derived CCI if non-null SyncCtx"); + wsc.TargetSyncContext.Send(CancellationCallbackCoreWork_OnSyncContext, args); + // CancellationCallbackCoreWork_OnSyncContext may have altered ThreadIDExecutingCallbacks, so reset it. + ThreadIDExecutingCallbacks = Thread.CurrentThread.ManagedThreadId; + } + else + { + CancellationCallbackCoreWork(args); + } + } + catch(Exception ex) + { + if (throwOnFirstException) + throw; + + // Otherwise, log it and proceed. + if(exceptionList == null) + exceptionList = new List<Exception>(); + exceptionList.Add(ex); + } + } + } + + currArrayFragment = currArrayFragment.Prev; + } + } + } + } + finally + { + m_state = NOTIFYINGCOMPLETE; + m_executingCallback = null; + Thread.MemoryBarrier(); // for safety, prevent reorderings crossing this point and seeing inconsistent state. + } + + if (exceptionList != null) + { + Contract.Assert(exceptionList.Count > 0, "Expected exception count > 0"); + throw new AggregateException(exceptionList); + } + } + + // The main callback work that executes on the target synchronization context + private void CancellationCallbackCoreWork_OnSyncContext(object obj) + { + CancellationCallbackCoreWork((CancellationCallbackCoreWorkArguments)obj); + } + + private void CancellationCallbackCoreWork(CancellationCallbackCoreWorkArguments args) + { + // remove the intended callback..and ensure that it worked. + // otherwise the callback has disappeared in the interim and we can immediately return. + CancellationCallbackInfo callback = args.m_currArrayFragment.SafeAtomicRemove(args.m_currArrayIndex, m_executingCallback); + if (callback == m_executingCallback) + { + if (callback.TargetExecutionContext != null) + { + // we are running via a custom sync context, so update the executing threadID + callback.CancellationTokenSource.ThreadIDExecutingCallbacks = Thread.CurrentThread.ManagedThreadId; + } + callback.ExecuteCallback(); + } + } + + + /// <summary> + /// Creates a <see cref="T:System.Threading.CancellationTokenSource">CancellationTokenSource</see> that will be in the canceled state + /// when any of the source tokens are in the canceled state. + /// </summary> + /// <param name="token1">The first <see cref="T:System.Threading.CancellationToken">CancellationToken</see> to observe.</param> + /// <param name="token2">The second <see cref="T:System.Threading.CancellationToken">CancellationToken</see> to observe.</param> + /// <returns>A <see cref="T:System.Threading.CancellationTokenSource">CancellationTokenSource</see> that is linked + /// to the source tokens.</returns> + public static CancellationTokenSource CreateLinkedTokenSource(CancellationToken token1, CancellationToken token2) + { + return token1.CanBeCanceled || token2.CanBeCanceled ? + new LinkedCancellationTokenSource(token1, token2) : + new CancellationTokenSource(); + } + + /// <summary> + /// Creates a <see cref="T:System.Threading.CancellationTokenSource">CancellationTokenSource</see> that will be in the canceled state + /// when any of the source tokens are in the canceled state. + /// </summary> + /// <param name="tokens">The <see cref="T:System.Threading.CancellationToken">CancellationToken</see> instances to observe.</param> + /// <returns>A <see cref="T:System.Threading.CancellationTokenSource">CancellationTokenSource</see> that is linked + /// to the source tokens.</returns> + /// <exception cref="T:System.ArgumentNullException"><paramref name="tokens"/> is null.</exception> + public static CancellationTokenSource CreateLinkedTokenSource(params CancellationToken[] tokens) + { + if (tokens == null) + throw new ArgumentNullException("tokens"); + + if (tokens.Length == 0) + throw new ArgumentException(Environment.GetResourceString("CancellationToken_CreateLinkedToken_TokensIsEmpty")); + + // a defensive copy is not required as the array has value-items that have only a single IntPtr field, + // hence each item cannot be null itself, and reads of the payloads cannot be torn. + Contract.EndContractBlock(); + + return new LinkedCancellationTokenSource(tokens); + } + + + // Wait for a single callback to complete (or, more specifically, to not be running). + // It is ok to call this method if the callback has already finished. + // Calling this method before the target callback has been selected for execution would be an error. + internal void WaitForCallbackToComplete(CancellationCallbackInfo callbackInfo) + { + SpinWait sw = new SpinWait(); + while (ExecutingCallback == callbackInfo) + { + sw.SpinOnce(); //spin as we assume callback execution is fast and that this situation is rare. + } + } + + private sealed class LinkedCancellationTokenSource : CancellationTokenSource + { + private static readonly Action<object> s_linkedTokenCancelDelegate = + s => ((CancellationTokenSource)s).NotifyCancellation(throwOnFirstException: false); // skip ThrowIfDisposed() check in Cancel() + private CancellationTokenRegistration[] m_linkingRegistrations; + + internal LinkedCancellationTokenSource(CancellationToken token1, CancellationToken token2) + { + bool token2CanBeCanceled = token2.CanBeCanceled; + + if (token1.CanBeCanceled) + { + m_linkingRegistrations = new CancellationTokenRegistration[token2CanBeCanceled ? 2 : 1]; // there will be at least 1 and at most 2 linkings + m_linkingRegistrations[0] = token1.InternalRegisterWithoutEC(s_linkedTokenCancelDelegate, this); + } + + if (token2CanBeCanceled) + { + int index = 1; + if (m_linkingRegistrations == null) + { + m_linkingRegistrations = new CancellationTokenRegistration[1]; // this will be the only linking + index = 0; + } + m_linkingRegistrations[index] = token2.InternalRegisterWithoutEC(s_linkedTokenCancelDelegate, this); + } + } + + internal LinkedCancellationTokenSource(params CancellationToken[] tokens) + { + m_linkingRegistrations = new CancellationTokenRegistration[tokens.Length]; + + for (int i = 0; i < tokens.Length; i++) + { + if (tokens[i].CanBeCanceled) + { + m_linkingRegistrations[i] = tokens[i].InternalRegisterWithoutEC(s_linkedTokenCancelDelegate, this); + } + // Empty slots in the array will be default(CancellationTokenRegistration), which are nops to Dispose. + // Based on usage patterns, such occurrences should also be rare, such that it's not worth resizing + // the array and incurring the related costs. + } + } + + protected override void Dispose(bool disposing) + { + if (!disposing || m_disposed) + return; + + CancellationTokenRegistration[] linkingRegistrations = m_linkingRegistrations; + if (linkingRegistrations != null) + { + m_linkingRegistrations = null; // release for GC once we're done enumerating + for (int i = 0; i < linkingRegistrations.Length; i++) + { + linkingRegistrations[i].Dispose(); + } + } + + base.Dispose(disposing); + } + + } + } + + // ---------------------------------------------------------- + // -- CancellationCallbackCoreWorkArguments -- + // ---------------------------------------------------------- + // Helper struct for passing data to the target sync context + internal struct CancellationCallbackCoreWorkArguments + { + internal SparselyPopulatedArrayFragment<CancellationCallbackInfo> m_currArrayFragment; + internal int m_currArrayIndex; + + public CancellationCallbackCoreWorkArguments(SparselyPopulatedArrayFragment<CancellationCallbackInfo> currArrayFragment, int currArrayIndex) + { + m_currArrayFragment = currArrayFragment; + m_currArrayIndex = currArrayIndex; + } + } + + // ---------------------------------------------------------- + // -- CancellationCallbackInfo -- + // ---------------------------------------------------------- + + /// <summary> + /// A helper class for collating the various bits of information required to execute + /// cancellation callbacks. + /// </summary> + internal class CancellationCallbackInfo + { + internal readonly Action<object> Callback; + internal readonly object StateForCallback; + internal readonly ExecutionContext TargetExecutionContext; + internal readonly CancellationTokenSource CancellationTokenSource; + + internal sealed class WithSyncContext : CancellationCallbackInfo + { + // Very rarely used, and as such it is separated out into a + // a derived type so that the space for it is pay-for-play. + internal readonly SynchronizationContext TargetSyncContext; + + internal WithSyncContext( + Action<object> callback, object stateForCallback, ExecutionContext targetExecutionContext, CancellationTokenSource cancellationTokenSource, + SynchronizationContext targetSyncContext) : + base(callback, stateForCallback, targetExecutionContext, cancellationTokenSource) + { + TargetSyncContext = targetSyncContext; + } + + } + + internal CancellationCallbackInfo( + Action<object> callback, object stateForCallback, ExecutionContext targetExecutionContext, CancellationTokenSource cancellationTokenSource) + { + Callback = callback; + StateForCallback = stateForCallback; + TargetExecutionContext = targetExecutionContext; + CancellationTokenSource = cancellationTokenSource; + } + + // Cached callback delegate that's lazily initialized due to ContextCallback being SecurityCritical + [SecurityCritical] + private static ContextCallback s_executionContextCallback; + + /// <summary> + /// InternalExecuteCallbackSynchronously_GeneralPath + /// This will be called on the target synchronization context, however, we still need to restore the required execution context + /// </summary> + [SecuritySafeCritical] + internal void ExecuteCallback() + { + if (TargetExecutionContext != null) + { + // Lazily initialize the callback delegate; benign race condition + var callback = s_executionContextCallback; + if (callback == null) s_executionContextCallback = callback = new ContextCallback(ExecutionContextCallback); + + ExecutionContext.Run( + TargetExecutionContext, + callback, + this); + } + else + { + //otherwise run directly + ExecutionContextCallback(this); + } + } + + // the worker method to actually run the callback + // The signature is such that it can be used as a 'ContextCallback' + [SecurityCritical] + private static void ExecutionContextCallback(object obj) + { + CancellationCallbackInfo callbackInfo = obj as CancellationCallbackInfo; + Contract.Assert(callbackInfo != null); + callbackInfo.Callback(callbackInfo.StateForCallback); + } + } + + + // ---------------------------------------------------------- + // -- SparselyPopulatedArray -- + // ---------------------------------------------------------- + + /// <summary> + /// A sparsely populated array. Elements can be sparse and some null, but this allows for + /// lock-free additions and growth, and also for constant time removal (by nulling out). + /// </summary> + /// <typeparam name="T">The kind of elements contained within.</typeparam> + internal class SparselyPopulatedArray<T> where T : class + { + private readonly SparselyPopulatedArrayFragment<T> m_head; + private volatile SparselyPopulatedArrayFragment<T> m_tail; + + /// <summary> + /// Allocates a new array with the given initial size. + /// </summary> + /// <param name="initialSize">How many array slots to pre-allocate.</param> + internal SparselyPopulatedArray(int initialSize) + { + m_head = m_tail = new SparselyPopulatedArrayFragment<T>(initialSize); + } + +#if DEBUG + // Used in DEBUG mode by CancellationTokenSource.CallbackCount + /// <summary> + /// The head of the doubly linked list. + /// </summary> + internal SparselyPopulatedArrayFragment<T> Head + { + get { return m_head; } + } +#endif + + /// <summary> + /// The tail of the doubly linked list. + /// </summary> + internal SparselyPopulatedArrayFragment<T> Tail + { + get { return m_tail; } + } + + /// <summary> + /// Adds an element in the first available slot, beginning the search from the tail-to-head. + /// If no slots are available, the array is grown. The method doesn't return until successful. + /// </summary> + /// <param name="element">The element to add.</param> + /// <returns>Information about where the add happened, to enable O(1) deregistration.</returns> + internal SparselyPopulatedArrayAddInfo<T> Add(T element) + { + while (true) + { + // Get the tail, and ensure it's up to date. + SparselyPopulatedArrayFragment<T> tail = m_tail; + while (tail.m_next != null) + m_tail = (tail = tail.m_next); + + // Search for a free index, starting from the tail. + SparselyPopulatedArrayFragment<T> curr = tail; + while (curr != null) + { + const int RE_SEARCH_THRESHOLD = -10; // Every 10 skips, force a search. + if (curr.m_freeCount < 1) + --curr.m_freeCount; + + if (curr.m_freeCount > 0 || curr.m_freeCount < RE_SEARCH_THRESHOLD) + { + int c = curr.Length; + + // We'll compute a start offset based on how many free slots we think there + // are. This optimizes for ordinary the LIFO deregistration pattern, and is + // far from perfect due to the non-threadsafe ++ and -- of the free counter. + int start = ((c - curr.m_freeCount) % c); + if (start < 0) + { + start = 0; + curr.m_freeCount--; // Too many free elements; fix up. + } + Contract.Assert(start >= 0 && start < c, "start is outside of bounds"); + + // Now walk the array until we find a free slot (or reach the end). + for (int i = 0; i < c; i++) + { + // If the slot is null, try to CAS our element into it. + int tryIndex = (start + i) % c; + Contract.Assert(tryIndex >= 0 && tryIndex < curr.m_elements.Length, "tryIndex is outside of bounds"); + + if (curr.m_elements[tryIndex] == null && Interlocked.CompareExchange(ref curr.m_elements[tryIndex], element, null) == null) + { + // We adjust the free count by --. Note: if this drops to 0, we will skip + // the fragment on the next search iteration. Searching threads will -- the + // count and force a search every so often, just in case fragmentation occurs. + int newFreeCount = curr.m_freeCount - 1; + curr.m_freeCount = newFreeCount > 0 ? newFreeCount : 0; + return new SparselyPopulatedArrayAddInfo<T>(curr, tryIndex); + } + } + } + + curr = curr.m_prev; + } + + // If we got here, we need to add a new chunk to the tail and try again. + SparselyPopulatedArrayFragment<T> newTail = new SparselyPopulatedArrayFragment<T>( + tail.m_elements.Length == 4096 ? 4096 : tail.m_elements.Length * 2, tail); + if (Interlocked.CompareExchange(ref tail.m_next, newTail, null) == null) + { + m_tail = newTail; + } + } + } + } + + /// <summary> + /// A struct to hold a link to the exact spot in an array an element was inserted, enabling + /// constant time removal later on. + /// </summary> + internal struct SparselyPopulatedArrayAddInfo<T> where T : class + { + private SparselyPopulatedArrayFragment<T> m_source; + private int m_index; + + internal SparselyPopulatedArrayAddInfo(SparselyPopulatedArrayFragment<T> source, int index) + { + Contract.Assert(source != null); + Contract.Assert(index >= 0 && index < source.Length); + m_source = source; + m_index = index; + } + + internal SparselyPopulatedArrayFragment<T> Source + { + get { return m_source; } + } + + internal int Index + { + get { return m_index; } + } + } + + /// <summary> + /// A fragment of a sparsely populated array, doubly linked. + /// </summary> + /// <typeparam name="T">The kind of elements contained within.</typeparam> + internal class SparselyPopulatedArrayFragment<T> where T : class + { + internal readonly T[] m_elements; // The contents, sparsely populated (with nulls). + internal volatile int m_freeCount; // A hint of the number of free elements. + internal volatile SparselyPopulatedArrayFragment<T> m_next; // The next fragment in the chain. + internal volatile SparselyPopulatedArrayFragment<T> m_prev; // The previous fragment in the chain. + + internal SparselyPopulatedArrayFragment(int size) : this(size, null) + { + } + + internal SparselyPopulatedArrayFragment(int size, SparselyPopulatedArrayFragment<T> prev) + { + m_elements = new T[size]; + m_freeCount = size; + m_prev = prev; + } + + internal T this[int index] + { + get { return Volatile.Read<T>(ref m_elements[index]); } + } + + internal int Length + { + get { return m_elements.Length; } + } + +#if DEBUG + // Used in DEBUG mode by CancellationTokenSource.CallbackCount + internal SparselyPopulatedArrayFragment<T> Next + { + get { return m_next; } + } +#endif + internal SparselyPopulatedArrayFragment<T> Prev + { + get { return m_prev; } + } + + // only removes the item at the specified index if it is still the expected one. + // Returns the prevailing value. + // The remove occurred successfully if the return value == expected element + // otherwise the remove did not occur. + internal T SafeAtomicRemove(int index, T expectedElement) + { + T prevailingValue = Interlocked.CompareExchange(ref m_elements[index], null, expectedElement); + if (prevailingValue != null) + ++m_freeCount; + return prevailingValue; + } + } +} diff --git a/src/mscorlib/src/System/Threading/CountdownEvent.cs b/src/mscorlib/src/System/Threading/CountdownEvent.cs new file mode 100644 index 0000000000..1374766863 --- /dev/null +++ b/src/mscorlib/src/System/Threading/CountdownEvent.cs @@ -0,0 +1,592 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +// =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ +// +// +// +// A simple coordination data structure that we use for fork/join style parallelism. +// +// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + +using System; +using System.Diagnostics; +using System.Security.Permissions; +using System.Runtime.InteropServices; +using System.Threading; +using System.Diagnostics.Contracts; + +namespace System.Threading +{ + + /// <summary> + /// Represents a synchronization primitive that is signaled when its count reaches zero. + /// </summary> + /// <remarks> + /// All public and protected members of <see cref="CountdownEvent"/> are thread-safe and may be used + /// concurrently from multiple threads, with the exception of Dispose, which + /// must only be used when all other operations on the <see cref="CountdownEvent"/> have + /// completed, and Reset, which should only be used when no other threads are + /// accessing the event. + /// </remarks> + [ComVisible(false)] + [DebuggerDisplay("Initial Count={InitialCount}, Current Count={CurrentCount}")] + [HostProtection(Synchronization = true, ExternalThreading = true)] + public class CountdownEvent : IDisposable + { + // CountdownEvent is a simple synchronization primitive used for fork/join parallelism. We create a + // latch with a count of N; threads then signal the latch, which decrements N by 1; other threads can + // wait on the latch at any point; when the latch count reaches 0, all threads are woken and + // subsequent waiters return without waiting. The implementation internally lazily creates a true + // Win32 event as needed. We also use some amount of spinning on MP machines before falling back to a + // wait. + + private int m_initialCount; // The original # of signals the latch was instantiated with. + private volatile int m_currentCount; // The # of outstanding signals before the latch transitions to a signaled state. + private ManualResetEventSlim m_event; // An event used to manage blocking and signaling. + private volatile bool m_disposed; // Whether the latch has been disposed. + + /// <summary> + /// Initializes a new instance of <see cref="T:System.Threading.CountdownEvent"/> class with the + /// specified count. + /// </summary> + /// <param name="initialCount">The number of signals required to set the <see + /// cref="T:System.Threading.CountdownEvent"/>.</param> + /// <exception cref="T:System.ArgumentOutOfRangeException"><paramref name="initialCount"/> is less + /// than 0.</exception> + public CountdownEvent(int initialCount) + { + if (initialCount < 0) + { + throw new ArgumentOutOfRangeException("initialCount"); + } + + m_initialCount = initialCount; + m_currentCount = initialCount; + + // Allocate a thin event, which internally defers creation of an actual Win32 event. + m_event = new ManualResetEventSlim(); + + // If the latch was created with a count of 0, then it's already in the signaled state. + if (initialCount == 0) + { + m_event.Set(); + } + } + + /// <summary> + /// Gets the number of remaining signals required to set the event. + /// </summary> + /// <value> + /// The number of remaining signals required to set the event. + /// </value> + public int CurrentCount + { + get + { + int observedCount = m_currentCount; + return observedCount < 0 ? 0 : observedCount; + } + } + + /// <summary> + /// Gets the numbers of signals initially required to set the event. + /// </summary> + /// <value> + /// The number of signals initially required to set the event. + /// </value> + public int InitialCount + { + get { return m_initialCount; } + } + + /// <summary> + /// Determines whether the event is set. + /// </summary> + /// <value>true if the event is set; otherwise, false.</value> + public bool IsSet + { + get + { + // The latch is "completed" if its current count has reached 0. Note that this is NOT + // the same thing is checking the event's IsCompleted property. There is a tiny window + // of time, after the final decrement of the current count to 0 and before setting the + // event, where the two values are out of sync. + return (m_currentCount <= 0); + } + } + + /// <summary> + /// Gets a <see cref="T:System.Threading.WaitHandle"/> that is used to wait for the event to be set. + /// </summary> + /// <value>A <see cref="T:System.Threading.WaitHandle"/> that is used to wait for the event to be set.</value> + /// <exception cref="T:System.ObjectDisposedException">The current instance has already been disposed.</exception> + /// <remarks> + /// <see cref="WaitHandle"/> should only be used if it's needed for integration with code bases + /// that rely on having a WaitHandle. If all that's needed is to wait for the <see cref="CountdownEvent"/> + /// to be set, the <see cref="Wait()"/> method should be preferred. + /// </remarks> + public WaitHandle WaitHandle + { + get + { + ThrowIfDisposed(); + return m_event.WaitHandle; + } + } + + /// <summary> + /// Releases all resources used by the current instance of <see cref="T:System.Threading.CountdownEvent"/>. + /// </summary> + /// <remarks> + /// Unlike most of the members of <see cref="CountdownEvent"/>, <see cref="Dispose()"/> is not + /// thread-safe and may not be used concurrently with other members of this instance. + /// </remarks> + public void Dispose() + { + // Gets rid of this latch's associated resources. This can consist of a Win32 event + // which is (lazily) allocated by the underlying thin event. This method is not safe to + // call concurrently -- i.e. a caller must coordinate to ensure only one thread is using + // the latch at the time of the call to Dispose. + + Dispose(true); + GC.SuppressFinalize(this); + } + + /// <summary> + /// When overridden in a derived class, releases the unmanaged resources used by the + /// <see cref="T:System.Threading.CountdownEvent"/>, and optionally releases the managed resources. + /// </summary> + /// <param name="disposing">true to release both managed and unmanaged resources; false to release + /// only unmanaged resources.</param> + /// <remarks> + /// Unlike most of the members of <see cref="CountdownEvent"/>, <see cref="Dispose()"/> is not + /// thread-safe and may not be used concurrently with other members of this instance. + /// </remarks> + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + m_event.Dispose(); + m_disposed = true; + } + } + + /// <summary> + /// Registers a signal with the <see cref="T:System.Threading.CountdownEvent"/>, decrementing its + /// count. + /// </summary> + /// <returns>true if the signal caused the count to reach zero and the event was set; otherwise, + /// false.</returns> + /// <exception cref="T:System.InvalidOperationException">The current instance is already set. + /// </exception> + /// <exception cref="T:System.ObjectDisposedException">The current instance has already been + /// disposed.</exception> + public bool Signal() + { + ThrowIfDisposed(); + Contract.Assert(m_event != null); + + if (m_currentCount <= 0) + { + throw new InvalidOperationException(Environment.GetResourceString("CountdownEvent_Decrement_BelowZero")); + } +#pragma warning disable 0420 + int newCount = Interlocked.Decrement(ref m_currentCount); +#pragma warning restore 0420 + if (newCount == 0) + { + m_event.Set(); + return true; + } + else if (newCount < 0) + { + //if the count is decremented below zero, then throw, it's OK to keep the count negative, and we shouldn't set the event here + //because there was a thread already which decremented it to zero and set the event + throw new InvalidOperationException(Environment.GetResourceString("CountdownEvent_Decrement_BelowZero")); + } + + return false; + } + + /// <summary> + /// Registers multiple signals with the <see cref="T:System.Threading.CountdownEvent"/>, + /// decrementing its count by the specified amount. + /// </summary> + /// <param name="signalCount">The number of signals to register.</param> + /// <returns>true if the signals caused the count to reach zero and the event was set; otherwise, + /// false.</returns> + /// <exception cref="T:System.InvalidOperationException"> + /// The current instance is already set. -or- Or <paramref name="signalCount"/> is greater than <see + /// cref="CurrentCount"/>. + /// </exception> + /// <exception cref="T:System.ArgumentOutOfRangeException"><paramref name="signalCount"/> is less + /// than 1.</exception> + /// <exception cref="T:System.ObjectDisposedException">The current instance has already been + /// disposed.</exception> + public bool Signal(int signalCount) + { + if (signalCount <= 0) + { + throw new ArgumentOutOfRangeException("signalCount"); + } + + ThrowIfDisposed(); + Contract.Assert(m_event != null); + + int observedCount; + SpinWait spin = new SpinWait(); + while (true) + { + observedCount = m_currentCount; + + // If the latch is already signaled, we will fail. + if (observedCount < signalCount) + { + throw new InvalidOperationException(Environment.GetResourceString("CountdownEvent_Decrement_BelowZero")); + } + + // This disables the "CS0420: a reference to a volatile field will not be treated as volatile" warning + // for this statement. This warning is clearly senseless for Interlocked operations. +#pragma warning disable 0420 + if (Interlocked.CompareExchange(ref m_currentCount, observedCount - signalCount, observedCount) == observedCount) +#pragma warning restore 0420 + { + break; + } + + // The CAS failed. Spin briefly and try again. + spin.SpinOnce(); + } + + // If we were the last to signal, set the event. + if (observedCount == signalCount) + { + m_event.Set(); + return true; + } + + Contract.Assert(m_currentCount >= 0, "latch was decremented below zero"); + return false; + } + + /// <summary> + /// Increments the <see cref="T:System.Threading.CountdownEvent"/>'s current count by one. + /// </summary> + /// <exception cref="T:System.InvalidOperationException">The current instance is already + /// set.</exception> + /// <exception cref="T:System.InvalidOperationException"><see cref="CurrentCount"/> is equal to <see + /// cref="T:System.Int32.MaxValue"/>.</exception> + /// <exception cref="T:System.ObjectDisposedException"> + /// The current instance has already been disposed. + /// </exception> + public void AddCount() + { + AddCount(1); + } + + /// <summary> + /// Attempts to increment the <see cref="T:System.Threading.CountdownEvent"/>'s current count by one. + /// </summary> + /// <returns>true if the increment succeeded; otherwise, false. If <see cref="CurrentCount"/> is + /// already at zero. this will return false.</returns> + /// <exception cref="T:System.InvalidOperationException"><see cref="CurrentCount"/> is equal to <see + /// cref="T:System.Int32.MaxValue"/>.</exception> + /// <exception cref="T:System.ObjectDisposedException">The current instance has already been + /// disposed.</exception> + public bool TryAddCount() + { + return TryAddCount(1); + } + + /// <summary> + /// Increments the <see cref="T:System.Threading.CountdownEvent"/>'s current count by a specified + /// value. + /// </summary> + /// <param name="signalCount">The value by which to increase <see cref="CurrentCount"/>.</param> + /// <exception cref="T:System.ArgumentOutOfRangeException"><paramref name="signalCount"/> is less than + /// 0.</exception> + /// <exception cref="T:System.InvalidOperationException">The current instance is already + /// set.</exception> + /// <exception cref="T:System.InvalidOperationException"><see cref="CurrentCount"/> is equal to <see + /// cref="T:System.Int32.MaxValue"/>.</exception> + /// <exception cref="T:System.ObjectDisposedException">The current instance has already been + /// disposed.</exception> + public void AddCount(int signalCount) + { + if (!TryAddCount(signalCount)) + { + throw new InvalidOperationException(Environment.GetResourceString("CountdownEvent_Increment_AlreadyZero")); + } + } + + /// <summary> + /// Attempts to increment the <see cref="T:System.Threading.CountdownEvent"/>'s current count by a + /// specified value. + /// </summary> + /// <param name="signalCount">The value by which to increase <see cref="CurrentCount"/>.</param> + /// <returns>true if the increment succeeded; otherwise, false. If <see cref="CurrentCount"/> is + /// already at zero this will return false.</returns> + /// <exception cref="T:System.ArgumentOutOfRangeException"><paramref name="signalCount"/> is less + /// than 0.</exception> + /// <exception cref="T:System.InvalidOperationException">The current instance is already + /// set.</exception> + /// <exception cref="T:System.InvalidOperationException"><see cref="CurrentCount"/> is equal to <see + /// cref="T:System.Int32.MaxValue"/>.</exception> + /// <exception cref="T:System.ObjectDisposedException">The current instance has already been + /// disposed.</exception> + public bool TryAddCount(int signalCount) + { + if (signalCount <= 0) + { + throw new ArgumentOutOfRangeException("signalCount"); + } + + ThrowIfDisposed(); + + // Loop around until we successfully increment the count. + int observedCount; + SpinWait spin = new SpinWait(); + while (true) + { + observedCount = m_currentCount; + + if (observedCount <= 0) + { + return false; + } + else if (observedCount > (Int32.MaxValue - signalCount)) + { + throw new InvalidOperationException(Environment.GetResourceString("CountdownEvent_Increment_AlreadyMax")); + } + + // This disables the "CS0420: a reference to a volatile field will not be treated as volatile" warning + // for this statement. This warning is clearly senseless for Interlocked operations. +#pragma warning disable 0420 + if (Interlocked.CompareExchange(ref m_currentCount, observedCount + signalCount, observedCount) == observedCount) +#pragma warning restore 0420 + { + break; + } + + // The CAS failed. Spin briefly and try again. + spin.SpinOnce(); + } + + return true; + } + + /// <summary> + /// Resets the <see cref="CurrentCount"/> to the value of <see cref="InitialCount"/>. + /// </summary> + /// <remarks> + /// Unlike most of the members of <see cref="CountdownEvent"/>, Reset is not + /// thread-safe and may not be used concurrently with other members of this instance. + /// </remarks> + /// <exception cref="T:System.ObjectDisposedException">The current instance has already been + /// disposed..</exception> + public void Reset() + { + Reset(m_initialCount); + } + + /// <summary> + /// Resets the <see cref="CurrentCount"/> to a specified value. + /// </summary> + /// <param name="count">The number of signals required to set the <see + /// cref="T:System.Threading.CountdownEvent"/>.</param> + /// <remarks> + /// Unlike most of the members of <see cref="CountdownEvent"/>, Reset is not + /// thread-safe and may not be used concurrently with other members of this instance. + /// </remarks> + /// <exception cref="T:System.ArgumentOutOfRangeException"><paramref name="count"/> is + /// less than 0.</exception> + /// <exception cref="T:System.ObjectDisposedException">The current instance has alread been disposed.</exception> + public void Reset(int count) + { + ThrowIfDisposed(); + + if (count < 0) + { + throw new ArgumentOutOfRangeException("count"); + } + + m_currentCount = count; + m_initialCount = count; + + if (count == 0) + { + m_event.Set(); + } + else + { + m_event.Reset(); + } + } + + /// <summary> + /// Blocks the current thread until the <see cref="T:System.Threading.CountdownEvent"/> is set. + /// </summary> + /// <remarks> + /// The caller of this method blocks indefinitely until the current instance is set. The caller will + /// return immediately if the event is currently in a set state. + /// </remarks> + /// <exception cref="T:System.ObjectDisposedException">The current instance has already been + /// disposed.</exception> + public void Wait() + { + Wait(Timeout.Infinite, new CancellationToken()); + } + + + /// <summary> + /// Blocks the current thread until the <see cref="T:System.Threading.CountdownEvent"/> is set, while + /// observing a <see cref="T:System.Threading.CancellationToken"/>. + /// </summary> + /// <param name="cancellationToken">The <see cref="T:System.Threading.CancellationToken"/> to + /// observe.</param> + /// <remarks> + /// The caller of this method blocks indefinitely until the current instance is set. The caller will + /// return immediately if the event is currently in a set state. If the + /// <see cref="T:System.Threading.CancellationToken">CancellationToken</see> being observed + /// is canceled during the wait operation, an <see cref="T:System.OperationCanceledException"/> + /// will be thrown. + /// </remarks> + /// <exception cref="T:System.OperationCanceledException"><paramref name="cancellationToken"/> has been + /// canceled.</exception> + /// <exception cref="T:System.ObjectDisposedException">The current instance has already been + /// disposed.</exception> + public void Wait(CancellationToken cancellationToken) + { + Wait(Timeout.Infinite, cancellationToken); + } + + /// <summary> + /// Blocks the current thread until the <see cref="T:System.Threading.CountdownEvent"/> is set, using a + /// <see cref="T:System.TimeSpan"/> to measure the time interval. + /// </summary> + /// <param name="timeout">A <see cref="T:System.TimeSpan"/> that represents the number of + /// milliseconds to wait, or a <see cref="T:System.TimeSpan"/> that represents -1 milliseconds to + /// wait indefinitely.</param> + /// <returns>true if the <see cref="System.Threading.CountdownEvent"/> was set; otherwise, + /// false.</returns> + /// <exception cref="T:System.ArgumentOutOfRangeException"><paramref name="timeout"/> is a negative + /// number other than -1 milliseconds, which represents an infinite time-out -or- timeout is greater + /// than <see cref="System.Int32.MaxValue"/>.</exception> + /// <exception cref="T:System.ObjectDisposedException">The current instance has already been + /// disposed.</exception> + public bool Wait(TimeSpan timeout) + { + long totalMilliseconds = (long)timeout.TotalMilliseconds; + if (totalMilliseconds < -1 || totalMilliseconds > int.MaxValue) + { + throw new ArgumentOutOfRangeException("timeout"); + } + + return Wait((int)totalMilliseconds, new CancellationToken()); + } + + /// <summary> + /// Blocks the current thread until the <see cref="T:System.Threading.CountdownEvent"/> is set, using + /// a <see cref="T:System.TimeSpan"/> to measure the time interval, while observing a + /// <see cref="T:System.Threading.CancellationToken"/>. + /// </summary> + /// <param name="timeout">A <see cref="T:System.TimeSpan"/> that represents the number of + /// milliseconds to wait, or a <see cref="T:System.TimeSpan"/> that represents -1 milliseconds to + /// wait indefinitely.</param> + /// <param name="cancellationToken">The <see cref="T:System.Threading.CancellationToken"/> to + /// observe.</param> + /// <returns>true if the <see cref="System.Threading.CountdownEvent"/> was set; otherwise, + /// false.</returns> + /// <exception cref="T:System.ArgumentOutOfRangeException"><paramref name="timeout"/> is a negative + /// number other than -1 milliseconds, which represents an infinite time-out -or- timeout is greater + /// than <see cref="System.Int32.MaxValue"/>.</exception> + /// <exception cref="T:System.ObjectDisposedException">The current instance has already been + /// disposed.</exception> + /// <exception cref="T:System.OperationCanceledException"><paramref name="cancellationToken"/> has + /// been canceled.</exception> + public bool Wait(TimeSpan timeout, CancellationToken cancellationToken) + { + long totalMilliseconds = (long)timeout.TotalMilliseconds; + if (totalMilliseconds < -1 || totalMilliseconds > int.MaxValue) + { + throw new ArgumentOutOfRangeException("timeout"); + } + + return Wait((int)totalMilliseconds, cancellationToken); + } + + /// <summary> + /// Blocks the current thread until the <see cref="T:System.Threading.CountdownEvent"/> is set, using a + /// 32-bit signed integer to measure the time interval. + /// </summary> + /// <param name="millisecondsTimeout">The number of milliseconds to wait, or <see + /// cref="Timeout.Infinite"/>(-1) to wait indefinitely.</param> + /// <returns>true if the <see cref="System.Threading.CountdownEvent"/> was set; otherwise, + /// false.</returns> + /// <exception cref="ArgumentOutOfRangeException"><paramref name="millisecondsTimeout"/> is a + /// negative number other than -1, which represents an infinite time-out.</exception> + /// <exception cref="T:System.ObjectDisposedException">The current instance has already been + /// disposed.</exception> + public bool Wait(int millisecondsTimeout) + { + return Wait(millisecondsTimeout, new CancellationToken()); + } + + /// <summary> + /// Blocks the current thread until the <see cref="T:System.Threading.CountdownEvent"/> is set, using a + /// 32-bit signed integer to measure the time interval, while observing a + /// <see cref="T:System.Threading.CancellationToken"/>. + /// </summary> + /// <param name="millisecondsTimeout">The number of milliseconds to wait, or <see + /// cref="Timeout.Infinite"/>(-1) to wait indefinitely.</param> + /// <param name="cancellationToken">The <see cref="T:System.Threading.CancellationToken"/> to + /// observe.</param> + /// <returns>true if the <see cref="System.Threading.CountdownEvent"/> was set; otherwise, + /// false.</returns> + /// <exception cref="ArgumentOutOfRangeException"><paramref name="millisecondsTimeout"/> is a + /// negative number other than -1, which represents an infinite time-out.</exception> + /// <exception cref="T:System.ObjectDisposedException">The current instance has already been + /// disposed.</exception> + /// <exception cref="T:System.OperationCanceledException"><paramref name="cancellationToken"/> has + /// been canceled.</exception> + public bool Wait(int millisecondsTimeout, CancellationToken cancellationToken) + { + if (millisecondsTimeout < -1) + { + throw new ArgumentOutOfRangeException("millisecondsTimeout"); + } + + ThrowIfDisposed(); + cancellationToken.ThrowIfCancellationRequested(); + + bool returnValue = IsSet; + + // If not completed yet, wait on the event. + if (!returnValue) + { + // ** the actual wait + returnValue = m_event.Wait(millisecondsTimeout, cancellationToken); + //the Wait will throw OCE itself if the token is canceled. + } + + return returnValue; + } + + // -------------------------------------- + // Private methods + + + /// <summary> + /// Throws an exception if the latch has been disposed. + /// </summary> + private void ThrowIfDisposed() + { + if (m_disposed) + { + throw new ObjectDisposedException("CountdownEvent"); + } + } + } +} diff --git a/src/mscorlib/src/System/Threading/EventResetMode.cs b/src/mscorlib/src/System/Threading/EventResetMode.cs new file mode 100644 index 0000000000..edafab9bb5 --- /dev/null +++ b/src/mscorlib/src/System/Threading/EventResetMode.cs @@ -0,0 +1,26 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +// +/*============================================================================= +** +** Enum: EventResetMode +** +** +** Purpose: Enum to determine the Event type to create +** +** +=============================================================================*/ + + +namespace System.Threading +{ + using System.Runtime.InteropServices; + [ComVisibleAttribute(false)] + public enum EventResetMode + { + AutoReset = 0, + ManualReset = 1 + } +} diff --git a/src/mscorlib/src/System/Threading/EventWaitHandle.cs b/src/mscorlib/src/System/Threading/EventWaitHandle.cs new file mode 100644 index 0000000000..f56da1fa26 --- /dev/null +++ b/src/mscorlib/src/System/Threading/EventWaitHandle.cs @@ -0,0 +1,297 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +// +/*============================================================================= +** +** +** +** Purpose: Base class for representing Events +** +** +=============================================================================*/ + + +#if !FEATURE_MACL +namespace System.Security.AccessControl +{ + public class EventWaitHandleSecurity + { + } + public enum EventWaitHandleRights + { + } +} +#endif + +namespace System.Threading +{ + using System; + using System.Threading; + using System.Runtime.CompilerServices; + using System.Security.Permissions; + using System.IO; + using Microsoft.Win32; + using Microsoft.Win32.SafeHandles; + using System.Runtime.InteropServices; + using System.Runtime.Versioning; + using System.Security.AccessControl; + using System.Diagnostics.Contracts; + + [HostProtection(Synchronization=true, ExternalThreading=true)] + [ComVisibleAttribute(true)] + public class EventWaitHandle : WaitHandle + { + [System.Security.SecuritySafeCritical] // auto-generated + public EventWaitHandle(bool initialState, EventResetMode mode) : this(initialState,mode,null) { } + + [System.Security.SecurityCritical] // auto-generated_required + public EventWaitHandle(bool initialState, EventResetMode mode, string name) + { + if(name != null) + { +#if PLATFORM_UNIX + throw new PlatformNotSupportedException(Environment.GetResourceString("PlatformNotSupported_NamedSynchronizationPrimitives")); +#else + if (System.IO.Path.MaxPath < name.Length) + { + throw new ArgumentException(Environment.GetResourceString("Argument_WaitHandleNameTooLong", Path.MaxPath), "name"); + } +#endif + } + Contract.EndContractBlock(); + + SafeWaitHandle _handle = null; + switch(mode) + { + case EventResetMode.ManualReset: + _handle = Win32Native.CreateEvent(null, true, initialState, name); + break; + case EventResetMode.AutoReset: + _handle = Win32Native.CreateEvent(null, false, initialState, name); + break; + + default: + throw new ArgumentException(Environment.GetResourceString("Argument_InvalidFlag",name)); + }; + + if (_handle.IsInvalid) + { + int errorCode = Marshal.GetLastWin32Error(); + + _handle.SetHandleAsInvalid(); + if(null != name && 0 != name.Length && Win32Native.ERROR_INVALID_HANDLE == errorCode) + throw new WaitHandleCannotBeOpenedException(Environment.GetResourceString("Threading.WaitHandleCannotBeOpenedException_InvalidHandle",name)); + + __Error.WinIOError(errorCode, name); + } + SetHandleInternal(_handle); + } + + [System.Security.SecurityCritical] // auto-generated_required + public EventWaitHandle(bool initialState, EventResetMode mode, string name, out bool createdNew) + : this(initialState, mode, name, out createdNew, null) + { + } + + [System.Security.SecurityCritical] // auto-generated_required + public unsafe EventWaitHandle(bool initialState, EventResetMode mode, string name, out bool createdNew, EventWaitHandleSecurity eventSecurity) + { + if(name != null) + { +#if PLATFORM_UNIX + throw new PlatformNotSupportedException(Environment.GetResourceString("PlatformNotSupported_NamedSynchronizationPrimitives")); +#else + if (System.IO.Path.MaxPath < name.Length) + { + throw new ArgumentException(Environment.GetResourceString("Argument_WaitHandleNameTooLong", Path.MaxPath), "name"); + } +#endif + } + Contract.EndContractBlock(); + Win32Native.SECURITY_ATTRIBUTES secAttrs = null; +#if FEATURE_MACL + // For ACL's, get the security descriptor from the EventWaitHandleSecurity. + if (eventSecurity != null) { + secAttrs = new Win32Native.SECURITY_ATTRIBUTES(); + secAttrs.nLength = (int)Marshal.SizeOf(secAttrs); + + byte[] sd = eventSecurity.GetSecurityDescriptorBinaryForm(); + byte* pSecDescriptor = stackalloc byte[sd.Length]; + Buffer.Memcpy(pSecDescriptor, 0, sd, 0, sd.Length); + secAttrs.pSecurityDescriptor = pSecDescriptor; + } +#endif + + SafeWaitHandle _handle = null; + Boolean isManualReset; + switch(mode) + { + case EventResetMode.ManualReset: + isManualReset = true; + break; + case EventResetMode.AutoReset: + isManualReset = false; + break; + + default: + throw new ArgumentException(Environment.GetResourceString("Argument_InvalidFlag",name)); + }; + + _handle = Win32Native.CreateEvent(secAttrs, isManualReset, initialState, name); + int errorCode = Marshal.GetLastWin32Error(); + + if (_handle.IsInvalid) + { + + _handle.SetHandleAsInvalid(); + if(null != name && 0 != name.Length && Win32Native.ERROR_INVALID_HANDLE == errorCode) + throw new WaitHandleCannotBeOpenedException(Environment.GetResourceString("Threading.WaitHandleCannotBeOpenedException_InvalidHandle",name)); + + __Error.WinIOError(errorCode, name); + } + createdNew = errorCode != Win32Native.ERROR_ALREADY_EXISTS; + SetHandleInternal(_handle); + } + + [System.Security.SecurityCritical] // auto-generated + private EventWaitHandle(SafeWaitHandle handle) + { + SetHandleInternal(handle); + } + + [System.Security.SecurityCritical] // auto-generated_required + public static EventWaitHandle OpenExisting(string name) + { +#if !FEATURE_MACL + return OpenExisting(name, (EventWaitHandleRights)0); +#else + return OpenExisting(name, EventWaitHandleRights.Modify | EventWaitHandleRights.Synchronize); +#endif + } + + [System.Security.SecurityCritical] // auto-generated_required + public static EventWaitHandle OpenExisting(string name, EventWaitHandleRights rights) + { + EventWaitHandle result; + switch (OpenExistingWorker(name, rights, out result)) + { + case OpenExistingResult.NameNotFound: + throw new WaitHandleCannotBeOpenedException(); + + case OpenExistingResult.NameInvalid: + throw new WaitHandleCannotBeOpenedException(Environment.GetResourceString("Threading.WaitHandleCannotBeOpenedException_InvalidHandle", name)); + + case OpenExistingResult.PathNotFound: + __Error.WinIOError(Win32Native.ERROR_PATH_NOT_FOUND, ""); + return result; //never executes + + default: + return result; + } + } + + [System.Security.SecurityCritical] // auto-generated_required + public static bool TryOpenExisting(string name, out EventWaitHandle result) + { +#if !FEATURE_MACL + return OpenExistingWorker(name, (EventWaitHandleRights)0, out result) == OpenExistingResult.Success; +#else + return OpenExistingWorker(name, EventWaitHandleRights.Modify | EventWaitHandleRights.Synchronize, out result) == OpenExistingResult.Success; +#endif + } + + [System.Security.SecurityCritical] // auto-generated_required + public static bool TryOpenExisting(string name, EventWaitHandleRights rights, out EventWaitHandle result) + { + return OpenExistingWorker(name, rights, out result) == OpenExistingResult.Success; + } + + [System.Security.SecurityCritical] // auto-generated_required + private static OpenExistingResult OpenExistingWorker(string name, EventWaitHandleRights rights, out EventWaitHandle result) + { +#if PLATFORM_UNIX + throw new PlatformNotSupportedException(Environment.GetResourceString("PlatformNotSupported_NamedSynchronizationPrimitives")); +#else + if (name == null) + { + throw new ArgumentNullException("name", Environment.GetResourceString("ArgumentNull_WithParamName")); + } + + if(name.Length == 0) + { + throw new ArgumentException(Environment.GetResourceString("Argument_EmptyName"), "name"); + } + + if(null != name && System.IO.Path.MaxPath < name.Length) + { + throw new ArgumentException(Environment.GetResourceString("Argument_WaitHandleNameTooLong", Path.MaxPath), "name"); + } + + Contract.EndContractBlock(); + + result = null; + +#if FEATURE_MACL + SafeWaitHandle myHandle = Win32Native.OpenEvent((int) rights, false, name); +#else + SafeWaitHandle myHandle = Win32Native.OpenEvent(Win32Native.EVENT_MODIFY_STATE | Win32Native.SYNCHRONIZE, false, name); +#endif + + if (myHandle.IsInvalid) + { + int errorCode = Marshal.GetLastWin32Error(); + + if(Win32Native.ERROR_FILE_NOT_FOUND == errorCode || Win32Native.ERROR_INVALID_NAME == errorCode) + return OpenExistingResult.NameNotFound; + if (Win32Native.ERROR_PATH_NOT_FOUND == errorCode) + return OpenExistingResult.PathNotFound; + if(null != name && 0 != name.Length && Win32Native.ERROR_INVALID_HANDLE == errorCode) + return OpenExistingResult.NameInvalid; + //this is for passed through Win32Native Errors + __Error.WinIOError(errorCode,""); + } + result = new EventWaitHandle(myHandle); + return OpenExistingResult.Success; +#endif + } + [System.Security.SecuritySafeCritical] // auto-generated + public bool Reset() + { + bool res = Win32Native.ResetEvent(safeWaitHandle); + if (!res) + __Error.WinIOError(); + return res; + } + [System.Security.SecuritySafeCritical] // auto-generated + public bool Set() + { + bool res = Win32Native.SetEvent(safeWaitHandle); + + if (!res) + __Error.WinIOError(); + + return res; + } + +#if FEATURE_MACL + [System.Security.SecuritySafeCritical] // auto-generated + public EventWaitHandleSecurity GetAccessControl() + { + return new EventWaitHandleSecurity(safeWaitHandle, AccessControlSections.Access | AccessControlSections.Owner | AccessControlSections.Group); + } + + [System.Security.SecuritySafeCritical] // auto-generated + public void SetAccessControl(EventWaitHandleSecurity eventSecurity) + { + if (eventSecurity == null) + throw new ArgumentNullException("eventSecurity"); + Contract.EndContractBlock(); + + eventSecurity.Persist(safeWaitHandle); + } +#endif + } +} + diff --git a/src/mscorlib/src/System/Threading/ExecutionContext.cs b/src/mscorlib/src/System/Threading/ExecutionContext.cs new file mode 100644 index 0000000000..0440368608 --- /dev/null +++ b/src/mscorlib/src/System/Threading/ExecutionContext.cs @@ -0,0 +1,1398 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. +/*============================================================ +** +** +** +** Purpose: Capture execution context for a thread +** +** +===========================================================*/ +namespace System.Threading +{ + using System; + using System.Security; + using System.Runtime.Remoting; +#if FEATURE_IMPERSONATION + using System.Security.Principal; +#endif + using System.Collections; + using System.Collections.Generic; + using System.Reflection; + using System.Runtime.ExceptionServices; + using System.Runtime.Serialization; + using System.Security.Permissions; +#if FEATURE_REMOTING + using System.Runtime.Remoting.Messaging; +#endif // FEATURE_REMOTING + using System.Runtime.InteropServices; + using System.Runtime.CompilerServices; + using System.Runtime.ConstrainedExecution; + using System.Diagnostics.Contracts; + using System.Diagnostics.CodeAnalysis; + +#if FEATURE_CORECLR + [System.Security.SecurityCritical] // auto-generated +#endif + [System.Runtime.InteropServices.ComVisible(true)] + public delegate void ContextCallback(Object state); + +#if FEATURE_CORECLR + + [SecurityCritical] + internal struct ExecutionContextSwitcher + { + internal ExecutionContext m_ec; + internal SynchronizationContext m_sc; + + internal void Undo(Thread currentThread) + { + Contract.Assert(currentThread == Thread.CurrentThread); + + // The common case is that these have not changed, so avoid the cost of a write if not needed. + if (currentThread.SynchronizationContext != m_sc) + { + currentThread.SynchronizationContext = m_sc; + } + + if (currentThread.ExecutionContext != m_ec) + { + ExecutionContext.Restore(currentThread, m_ec); + } + } + } + + public sealed class ExecutionContext : IDisposable + { + private static readonly ExecutionContext Default = new ExecutionContext(); + + private readonly Dictionary<IAsyncLocal, object> m_localValues; + private readonly IAsyncLocal[] m_localChangeNotifications; + + private ExecutionContext() + { + m_localValues = new Dictionary<IAsyncLocal, object>(); + m_localChangeNotifications = Array.Empty<IAsyncLocal>(); + } + + private ExecutionContext(Dictionary<IAsyncLocal, object> localValues, IAsyncLocal[] localChangeNotifications) + { + m_localValues = localValues; + m_localChangeNotifications = localChangeNotifications; + } + + [SecuritySafeCritical] + public static ExecutionContext Capture() + { + return Thread.CurrentThread.ExecutionContext ?? ExecutionContext.Default; + } + + [SecurityCritical] + [HandleProcessCorruptedStateExceptions] + public static void Run(ExecutionContext executionContext, ContextCallback callback, Object state) + { + if (executionContext == null) + throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_NullContext")); + + Thread currentThread = Thread.CurrentThread; + ExecutionContextSwitcher ecsw = default(ExecutionContextSwitcher); + try + { + EstablishCopyOnWriteScope(currentThread, ref ecsw); + ExecutionContext.Restore(currentThread, executionContext); + callback(state); + } + catch + { + // Note: we have a "catch" rather than a "finally" because we want + // to stop the first pass of EH here. That way we can restore the previous + // context before any of our callers' EH filters run. That means we need to + // end the scope separately in the non-exceptional case below. + ecsw.Undo(currentThread); + throw; + } + ecsw.Undo(currentThread); + } + + [SecurityCritical] + internal static void Restore(Thread currentThread, ExecutionContext executionContext) + { + Contract.Assert(currentThread == Thread.CurrentThread); + + ExecutionContext previous = currentThread.ExecutionContext ?? Default; + currentThread.ExecutionContext = executionContext; + + // New EC could be null if that's what ECS.Undo saved off. + // For the purposes of dealing with context change, treat this as the default EC + executionContext = executionContext ?? Default; + + if (previous != executionContext) + { + OnContextChanged(previous, executionContext); + } + } + + [SecurityCritical] + static internal void EstablishCopyOnWriteScope(Thread currentThread, ref ExecutionContextSwitcher ecsw) + { + Contract.Assert(currentThread == Thread.CurrentThread); + + ecsw.m_ec = currentThread.ExecutionContext; + ecsw.m_sc = currentThread.SynchronizationContext; + } + + [SecurityCritical] + [HandleProcessCorruptedStateExceptions] + private static void OnContextChanged(ExecutionContext previous, ExecutionContext current) + { + Contract.Assert(previous != null); + Contract.Assert(current != null); + Contract.Assert(previous != current); + + foreach (IAsyncLocal local in previous.m_localChangeNotifications) + { + object previousValue; + object currentValue; + previous.m_localValues.TryGetValue(local, out previousValue); + current.m_localValues.TryGetValue(local, out currentValue); + + if (previousValue != currentValue) + local.OnValueChanged(previousValue, currentValue, true); + } + + if (current.m_localChangeNotifications != previous.m_localChangeNotifications) + { + try + { + foreach (IAsyncLocal local in current.m_localChangeNotifications) + { + // If the local has a value in the previous context, we already fired the event for that local + // in the code above. + object previousValue; + if (!previous.m_localValues.TryGetValue(local, out previousValue)) + { + object currentValue; + current.m_localValues.TryGetValue(local, out currentValue); + + if (previousValue != currentValue) + local.OnValueChanged(previousValue, currentValue, true); + } + } + } + catch (Exception ex) + { + Environment.FailFast( + Environment.GetResourceString("ExecutionContext_ExceptionInAsyncLocalNotification"), + ex); + } + } + } + + [SecurityCritical] + internal static object GetLocalValue(IAsyncLocal local) + { + ExecutionContext current = Thread.CurrentThread.ExecutionContext; + if (current == null) + return null; + + object value; + current.m_localValues.TryGetValue(local, out value); + return value; + } + + [SecurityCritical] + internal static void SetLocalValue(IAsyncLocal local, object newValue, bool needChangeNotifications) + { + ExecutionContext current = Thread.CurrentThread.ExecutionContext ?? ExecutionContext.Default; + + object previousValue; + bool hadPreviousValue = current.m_localValues.TryGetValue(local, out previousValue); + + if (previousValue == newValue) + return; + + // + // Allocate a new Dictionary containing a copy of the old values, plus the new value. We have to do this manually to + // minimize allocations of IEnumerators, etc. + // + Dictionary<IAsyncLocal, object> newValues = new Dictionary<IAsyncLocal, object>(current.m_localValues.Count + (hadPreviousValue ? 0 : 1)); + + foreach (KeyValuePair<IAsyncLocal, object> pair in current.m_localValues) + newValues.Add(pair.Key, pair.Value); + + newValues[local] = newValue; + + // + // Either copy the change notification array, or create a new one, depending on whether we need to add a new item. + // + IAsyncLocal[] newChangeNotifications = current.m_localChangeNotifications; + if (needChangeNotifications) + { + if (hadPreviousValue) + { + Contract.Assert(Array.IndexOf(newChangeNotifications, local) >= 0); + } + else + { + int newNotificationIndex = newChangeNotifications.Length; + Array.Resize(ref newChangeNotifications, newNotificationIndex + 1); + newChangeNotifications[newNotificationIndex] = local; + } + } + + Thread.CurrentThread.ExecutionContext = new ExecutionContext(newValues, newChangeNotifications); + + if (needChangeNotifications) + { + local.OnValueChanged(previousValue, newValue, false); + } + } + + #region Wrappers for CLR compat, to avoid ifdefs all over the BCL + + [Flags] + internal enum CaptureOptions + { + None = 0x00, + IgnoreSyncCtx = 0x01, + OptimizeDefaultCase = 0x02, + } + + [SecurityCritical] + internal static ExecutionContext Capture(ref StackCrawlMark stackMark, CaptureOptions captureOptions) + { + return Capture(); + } + + [SecuritySafeCritical] + [FriendAccessAllowed] + internal static ExecutionContext FastCapture() + { + return Capture(); + } + + [SecurityCritical] + [FriendAccessAllowed] + internal static void Run(ExecutionContext executionContext, ContextCallback callback, Object state, bool preserveSyncCtx) + { + Run(executionContext, callback, state); + } + + [SecurityCritical] + internal bool IsDefaultFTContext(bool ignoreSyncCtx) + { + return this == Default; + } + + [SecuritySafeCritical] + public ExecutionContext CreateCopy() + { + return this; // since CoreCLR's ExecutionContext is immutable, we don't need to create copies. + } + + public void Dispose() + { + // For CLR compat only + } + + public static bool IsFlowSuppressed() + { + return false; + } + + internal static ExecutionContext PreAllocatedDefault + { + [SecuritySafeCritical] + get { return ExecutionContext.Default; } + } + + internal bool IsPreAllocatedDefault + { + get { return this == ExecutionContext.Default; } + } + + #endregion + } + +#else // FEATURE_CORECLR + + // Legacy desktop ExecutionContext implementation + + internal struct ExecutionContextSwitcher + { + internal ExecutionContext.Reader outerEC; // previous EC we need to restore on Undo + internal bool outerECBelongsToScope; +#if FEATURE_IMPERSONATION || FEATURE_COMPRESSEDSTACK + internal SecurityContextSwitcher scsw; +#endif // #if FEATURE_IMPERSONATION || FEATURE_COMPRESSEDSTACK + internal Object hecsw; +#if FEATURE_IMPERSONATION + internal WindowsIdentity wi; + internal bool cachedAlwaysFlowImpersonationPolicy; + internal bool wiIsValid; +#endif + internal Thread thread; + + [System.Security.SecurityCritical] // auto-generated + [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)] +#if FEATURE_CORRUPTING_EXCEPTIONS + [HandleProcessCorruptedStateExceptions] +#endif // FEATURE_CORRUPTING_EXCEPTIONS + internal bool UndoNoThrow(Thread currentThread) + { + try + { + Undo(currentThread); + } + catch + { + return false; + } + return true; + } + + [System.Security.SecurityCritical] // auto-generated + [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)] + internal void Undo(Thread currentThread) + { + // + // Don't use an uninitialized switcher, or one that's already been used. + // + if (thread == null) + return; // Don't do anything + + Contract.Assert(Thread.CurrentThread == this.thread); + + // + // Restore the HostExecutionContext before restoring the ExecutionContext. + // +#if FEATURE_CAS_POLICY + if (hecsw != null) + HostExecutionContextSwitcher.Undo(hecsw); +#endif // FEATURE_CAS_POLICY + + // + // restore the saved Execution Context. Note that this will also restore the + // SynchronizationContext, Logical/IllogicalCallContext, etc. + // + ExecutionContext.Reader innerEC = currentThread.GetExecutionContextReader(); + currentThread.SetExecutionContext(outerEC, outerECBelongsToScope); + +#if DEBUG + try + { + currentThread.ForbidExecutionContextMutation = true; +#endif + + // + // Tell the SecurityContext to do the side-effects of restoration. + // +#if FEATURE_IMPERSONATION || FEATURE_COMPRESSEDSTACK + if (scsw.currSC != null) + { + // Any critical failure inside scsw will cause FailFast + scsw.Undo(); + } +#endif // #if FEATURE_IMPERSONATION || FEATURE_COMPRESSEDSTACK + +#if FEATURE_IMPERSONATION + if (wiIsValid) + SecurityContext.RestoreCurrentWI(outerEC, innerEC, wi, cachedAlwaysFlowImpersonationPolicy); +#endif + + thread = null; // this will prevent the switcher object being used again +#if DEBUG + } + finally + { + currentThread.ForbidExecutionContextMutation = false; + } +#endif + ExecutionContext.OnAsyncLocalContextChanged(innerEC.DangerousGetRawExecutionContext(), outerEC.DangerousGetRawExecutionContext()); + } + } + + + public struct AsyncFlowControl: IDisposable + { + private bool useEC; + private ExecutionContext _ec; +#if FEATURE_IMPERSONATION || FEATURE_COMPRESSEDSTACK + private SecurityContext _sc; +#endif // #if FEATURE_IMPERSONATION || FEATURE_COMPRESSEDSTACK + private Thread _thread; +#if FEATURE_IMPERSONATION || FEATURE_COMPRESSEDSTACK + [SecurityCritical] + internal void Setup(SecurityContextDisableFlow flags) + { + useEC = false; + Thread currentThread = Thread.CurrentThread; + _sc = currentThread.GetMutableExecutionContext().SecurityContext; + _sc._disableFlow = flags; + _thread = currentThread; + } +#endif // #if FEATURE_IMPERSONATION || FEATURE_COMPRESSEDSTACK + [SecurityCritical] + internal void Setup() + { + useEC = true; + Thread currentThread = Thread.CurrentThread; + _ec = currentThread.GetMutableExecutionContext(); + _ec.isFlowSuppressed = true; + _thread = currentThread; + } + + public void Dispose() + { + Undo(); + } + + [SecuritySafeCritical] + public void Undo() + { + if (_thread == null) + { + throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_CannotUseAFCMultiple")); + } + if (_thread != Thread.CurrentThread) + { + throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_CannotUseAFCOtherThread")); + } + if (useEC) + { + if (Thread.CurrentThread.GetMutableExecutionContext() != _ec) + { + throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_AsyncFlowCtrlCtxMismatch")); + } + ExecutionContext.RestoreFlow(); + } +#if FEATURE_IMPERSONATION || FEATURE_COMPRESSEDSTACK + else + { + if (!Thread.CurrentThread.GetExecutionContextReader().SecurityContext.IsSame(_sc)) + { + throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_AsyncFlowCtrlCtxMismatch")); + } + SecurityContext.RestoreFlow(); + } +#endif // #if FEATURE_IMPERSONATION || FEATURE_COMPRESSEDSTACK + _thread = null; + } + + public override int GetHashCode() + { + return _thread == null ? ToString().GetHashCode() : _thread.GetHashCode(); + } + + public override bool Equals(Object obj) + { + if (obj is AsyncFlowControl) + return Equals((AsyncFlowControl)obj); + else + return false; + } + + public bool Equals(AsyncFlowControl obj) + { + return obj.useEC == useEC && obj._ec == _ec && +#if FEATURE_IMPERSONATION || FEATURE_COMPRESSEDSTACK + obj._sc == _sc && +#endif // #if FEATURE_IMPERSONATION || FEATURE_COMPRESSEDSTACK + obj._thread == _thread; + } + + public static bool operator ==(AsyncFlowControl a, AsyncFlowControl b) + { + return a.Equals(b); + } + + public static bool operator !=(AsyncFlowControl a, AsyncFlowControl b) + { + return !(a == b); + } + + } + + +#if FEATURE_SERIALIZATION + [Serializable] +#endif + public sealed class ExecutionContext : IDisposable, ISerializable + { + /*========================================================================= + ** Data accessed from managed code that needs to be defined in + ** ExecutionContextObject to maintain alignment between the two classes. + ** DON'T CHANGE THESE UNLESS YOU MODIFY ExecutionContextObject in vm\object.h + =========================================================================*/ +#if FEATURE_CAS_POLICY + private HostExecutionContext _hostExecutionContext; +#endif // FEATURE_CAS_POLICY + private SynchronizationContext _syncContext; + private SynchronizationContext _syncContextNoFlow; +#if FEATURE_IMPERSONATION || FEATURE_COMPRESSEDSTACK + private SecurityContext _securityContext; +#endif // #if FEATURE_IMPERSONATION || FEATURE_COMPRESSEDSTACK +#if FEATURE_REMOTING + [System.Security.SecurityCritical] // auto-generated + private LogicalCallContext _logicalCallContext; + private IllogicalCallContext _illogicalCallContext; // this call context follows the physical thread +#endif // #if FEATURE_REMOTING + + enum Flags + { + None = 0x0, + IsNewCapture = 0x1, + IsFlowSuppressed = 0x2, + IsPreAllocatedDefault = 0x4 + } + private Flags _flags; + + private Dictionary<IAsyncLocal, object> _localValues; + private List<IAsyncLocal> _localChangeNotifications; + + internal bool isNewCapture + { + get + { + return (_flags & (Flags.IsNewCapture | Flags.IsPreAllocatedDefault)) != Flags.None; + } + set + { + Contract.Assert(!IsPreAllocatedDefault); + if (value) + _flags |= Flags.IsNewCapture; + else + _flags &= ~Flags.IsNewCapture; + } + } + internal bool isFlowSuppressed + { + get + { + return (_flags & Flags.IsFlowSuppressed) != Flags.None; + } + set + { + Contract.Assert(!IsPreAllocatedDefault); + if (value) + _flags |= Flags.IsFlowSuppressed; + else + _flags &= ~Flags.IsFlowSuppressed; + } + } + + + private static readonly ExecutionContext s_dummyDefaultEC = new ExecutionContext(isPreAllocatedDefault: true); + + static internal ExecutionContext PreAllocatedDefault + { + [SecuritySafeCritical] + get { return s_dummyDefaultEC; } + } + + internal bool IsPreAllocatedDefault + { + get + { + // we use _flags instead of a direct comparison w/ s_dummyDefaultEC to avoid the static access on + // hot code paths. + if ((_flags & Flags.IsPreAllocatedDefault) != Flags.None) + { + Contract.Assert(this == s_dummyDefaultEC); + return true; + } + else + { + return false; + } + } + } + + [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] + internal ExecutionContext() + { + } + + [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] + internal ExecutionContext(bool isPreAllocatedDefault) + { + if (isPreAllocatedDefault) + _flags = Flags.IsPreAllocatedDefault; + } + + // Read-only wrapper around ExecutionContext. This enables safe reading of an ExecutionContext without accidentally modifying it. + internal struct Reader + { + ExecutionContext m_ec; + + public Reader(ExecutionContext ec) { m_ec = ec; } + + public ExecutionContext DangerousGetRawExecutionContext() { return m_ec; } + + public bool IsNull { get { return m_ec == null; } } + [SecurityCritical] + public bool IsDefaultFTContext(bool ignoreSyncCtx) { return m_ec.IsDefaultFTContext(ignoreSyncCtx); } + public bool IsFlowSuppressed + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get { return IsNull ? false : m_ec.isFlowSuppressed; } + } + //public Thread Thread { get { return m_ec._thread; } } + public bool IsSame(ExecutionContext.Reader other) { return m_ec == other.m_ec; } + + public SynchronizationContext SynchronizationContext { get { return IsNull ? null : m_ec.SynchronizationContext; } } + public SynchronizationContext SynchronizationContextNoFlow { get { return IsNull ? null : m_ec.SynchronizationContextNoFlow; } } + +#if FEATURE_IMPERSONATION || FEATURE_COMPRESSEDSTACK + public SecurityContext.Reader SecurityContext + { + [SecurityCritical] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get { return new SecurityContext.Reader(IsNull ? null : m_ec.SecurityContext); } + } +#endif + +#if FEATURE_REMOTING + public LogicalCallContext.Reader LogicalCallContext + { + [SecurityCritical] + get { return new LogicalCallContext.Reader(IsNull ? null : m_ec.LogicalCallContext); } + } + + public IllogicalCallContext.Reader IllogicalCallContext + { + [SecurityCritical] + get { return new IllogicalCallContext.Reader(IsNull ? null : m_ec.IllogicalCallContext); } + } +#endif + + [SecurityCritical] + public object GetLocalValue(IAsyncLocal local) + { + if (IsNull) + return null; + + if (m_ec._localValues == null) + return null; + + object value; + m_ec._localValues.TryGetValue(local, out value); + return value; + } + + [SecurityCritical] + public bool HasSameLocalValues(ExecutionContext other) + { + var thisLocalValues = IsNull ? null : m_ec._localValues; + var otherLocalValues = other == null ? null : other._localValues; + return thisLocalValues == otherLocalValues; + } + + [SecurityCritical] + public bool HasLocalValues() + { + return !this.IsNull && m_ec._localValues != null; + } + } + + [SecurityCritical] + internal static object GetLocalValue(IAsyncLocal local) + { + return Thread.CurrentThread.GetExecutionContextReader().GetLocalValue(local); + } + + [SecurityCritical] + internal static void SetLocalValue(IAsyncLocal local, object newValue, bool needChangeNotifications) + { + ExecutionContext current = Thread.CurrentThread.GetMutableExecutionContext(); + + object previousValue = null; + bool hadPreviousValue = current._localValues != null && current._localValues.TryGetValue(local, out previousValue); + + if (previousValue == newValue) + return; + + if (current._localValues == null) + current._localValues = new Dictionary<IAsyncLocal, object>(); + else + current._localValues = new Dictionary<IAsyncLocal, object>(current._localValues); + + current._localValues[local] = newValue; + + if (needChangeNotifications) + { + if (hadPreviousValue) + { + Contract.Assert(current._localChangeNotifications != null); + Contract.Assert(current._localChangeNotifications.Contains(local)); + } + else + { + if (current._localChangeNotifications == null) + current._localChangeNotifications = new List<IAsyncLocal>(); + else + current._localChangeNotifications = new List<IAsyncLocal>(current._localChangeNotifications); + + current._localChangeNotifications.Add(local); + } + + local.OnValueChanged(previousValue, newValue, false); + } + } + + [SecurityCritical] + [HandleProcessCorruptedStateExceptions] + internal static void OnAsyncLocalContextChanged(ExecutionContext previous, ExecutionContext current) + { + List<IAsyncLocal> previousLocalChangeNotifications = (previous == null) ? null : previous._localChangeNotifications; + if (previousLocalChangeNotifications != null) + { + foreach (IAsyncLocal local in previousLocalChangeNotifications) + { + object previousValue = null; + if (previous != null && previous._localValues != null) + previous._localValues.TryGetValue(local, out previousValue); + + object currentValue = null; + if (current != null && current._localValues != null) + current._localValues.TryGetValue(local, out currentValue); + + if (previousValue != currentValue) + local.OnValueChanged(previousValue, currentValue, true); + } + } + + List<IAsyncLocal> currentLocalChangeNotifications = (current == null) ? null : current._localChangeNotifications; + if (currentLocalChangeNotifications != null && currentLocalChangeNotifications != previousLocalChangeNotifications) + { + try + { + foreach (IAsyncLocal local in currentLocalChangeNotifications) + { + // If the local has a value in the previous context, we already fired the event for that local + // in the code above. + object previousValue = null; + if (previous == null || + previous._localValues == null || + !previous._localValues.TryGetValue(local, out previousValue)) + { + object currentValue = null; + if (current != null && current._localValues != null) + current._localValues.TryGetValue(local, out currentValue); + + if (previousValue != currentValue) + local.OnValueChanged(previousValue, currentValue, true); + } + } + } + catch (Exception ex) + { + Environment.FailFast( + Environment.GetResourceString("ExecutionContext_ExceptionInAsyncLocalNotification"), + ex); + } + } + } + + +#if FEATURE_REMOTING + internal LogicalCallContext LogicalCallContext + { + [System.Security.SecurityCritical] // auto-generated + get + { + if (_logicalCallContext == null) + { + _logicalCallContext = new LogicalCallContext(); + } + return _logicalCallContext; + } + [System.Security.SecurityCritical] // auto-generated + set + { + Contract.Assert(this != s_dummyDefaultEC); + _logicalCallContext = value; + } + } + + internal IllogicalCallContext IllogicalCallContext + { + get + { + if (_illogicalCallContext == null) + { + _illogicalCallContext = new IllogicalCallContext(); + } + return _illogicalCallContext; + } + set + { + Contract.Assert(this != s_dummyDefaultEC); + _illogicalCallContext = value; + } + } +#endif // #if FEATURE_REMOTING + + internal SynchronizationContext SynchronizationContext + { + [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] + get + { + return _syncContext; + } + [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] + set + { + Contract.Assert(this != s_dummyDefaultEC); + _syncContext = value; + } + } + + internal SynchronizationContext SynchronizationContextNoFlow + { + [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] + get + { + return _syncContextNoFlow; + } + [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] + set + { + Contract.Assert(this != s_dummyDefaultEC); + _syncContextNoFlow = value; + } + } + +#if FEATURE_CAS_POLICY + internal HostExecutionContext HostExecutionContext + { + get + { + return _hostExecutionContext; + } + set + { + Contract.Assert(this != s_dummyDefaultEC); + _hostExecutionContext = value; + } + } +#endif // FEATURE_CAS_POLICY +#if FEATURE_IMPERSONATION || FEATURE_COMPRESSEDSTACK + internal SecurityContext SecurityContext + { + [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] + get + { + return _securityContext; + } + [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] + set + { + Contract.Assert(this != s_dummyDefaultEC); + // store the new security context + _securityContext = value; + // perform the reverse link too + if (value != null) + _securityContext.ExecutionContext = this; + } + } +#endif // #if FEATURE_IMPERSONATION || FEATURE_COMPRESSEDSTACK + + + public void Dispose() + { + if(this.IsPreAllocatedDefault) + return; //Do nothing if this is the default context +#if FEATURE_CAS_POLICY + if (_hostExecutionContext != null) + _hostExecutionContext.Dispose(); +#endif // FEATURE_CAS_POLICY +#if FEATURE_IMPERSONATION || FEATURE_COMPRESSEDSTACK + if (_securityContext != null) + _securityContext.Dispose(); +#endif //FEATURE_IMPERSONATION || FEATURE_COMPRESSEDSTACK + } + + [DynamicSecurityMethod] + [System.Security.SecurityCritical] // auto-generated_required + public static void Run(ExecutionContext executionContext, ContextCallback callback, Object state) + { + if (executionContext == null) + throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_NullContext")); + if (!executionContext.isNewCapture) + throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_NotNewCaptureContext")); + + Run(executionContext, callback, state, false); + } + + // This method is special from a security perspective - the VM will not allow a stack walk to + // continue past the call to ExecutionContext.Run. If you change the signature to this method, make + // sure to update SecurityStackWalk::IsSpecialRunFrame in the VM to search for the new signature. + [DynamicSecurityMethod] + [SecurityCritical] + [FriendAccessAllowed] + internal static void Run(ExecutionContext executionContext, ContextCallback callback, Object state, bool preserveSyncCtx) + { + RunInternal(executionContext, callback, state, preserveSyncCtx); + } + + // Actual implementation of Run is here, in a non-DynamicSecurityMethod, because the JIT seems to refuse to inline callees into + // a DynamicSecurityMethod. + [SecurityCritical] + [SuppressMessage("Microsoft.Concurrency", "CA8001", Justification = "Reviewed for thread safety")] + [HandleProcessCorruptedStateExceptions] + internal static void RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, bool preserveSyncCtx) + { + Contract.Assert(executionContext != null); + if (executionContext.IsPreAllocatedDefault) + { + Contract.Assert(executionContext.IsDefaultFTContext(preserveSyncCtx)); + } + else + { + Contract.Assert(executionContext.isNewCapture); + executionContext.isNewCapture = false; + } + + Thread currentThread = Thread.CurrentThread; + ExecutionContextSwitcher ecsw = default(ExecutionContextSwitcher); + + RuntimeHelpers.PrepareConstrainedRegions(); + try + { + ExecutionContext.Reader ec = currentThread.GetExecutionContextReader(); + if ( (ec.IsNull || ec.IsDefaultFTContext(preserveSyncCtx)) && + #if FEATURE_IMPERSONATION || FEATURE_COMPRESSEDSTACK + SecurityContext.CurrentlyInDefaultFTSecurityContext(ec) && + #endif // #if FEATURE_IMPERSONATION || FEATURE_COMPRESSEDSTACK + executionContext.IsDefaultFTContext(preserveSyncCtx) && + ec.HasSameLocalValues(executionContext) + ) + { + // Neither context is interesting, so we don't need to set the context. + // We do need to reset any changes made by the user's callback, + // so here we establish a "copy-on-write scope". Any changes will + // result in a copy of the context being made, preserving the original + // context. + EstablishCopyOnWriteScope(currentThread, true, ref ecsw); + } + else + { + if (executionContext.IsPreAllocatedDefault) + executionContext = new ExecutionContext(); + ecsw = SetExecutionContext(executionContext, preserveSyncCtx); + } + + // + // Call the user's callback + // + callback(state); + } + finally + { + ecsw.Undo(currentThread); + } + } + + [SecurityCritical] + static internal void EstablishCopyOnWriteScope(Thread currentThread, ref ExecutionContextSwitcher ecsw) + { + EstablishCopyOnWriteScope(currentThread, false, ref ecsw); + } + + [SecurityCritical] + static private void EstablishCopyOnWriteScope(Thread currentThread, bool knownNullWindowsIdentity, ref ExecutionContextSwitcher ecsw) + { + Contract.Assert(currentThread == Thread.CurrentThread); + + ecsw.outerEC = currentThread.GetExecutionContextReader(); + ecsw.outerECBelongsToScope = currentThread.ExecutionContextBelongsToCurrentScope; + +#if FEATURE_IMPERSONATION + ecsw.cachedAlwaysFlowImpersonationPolicy = SecurityContext.AlwaysFlowImpersonationPolicy; + if (knownNullWindowsIdentity) + Contract.Assert(SecurityContext.GetCurrentWI(ecsw.outerEC, ecsw.cachedAlwaysFlowImpersonationPolicy) == null); + else + ecsw.wi = SecurityContext.GetCurrentWI(ecsw.outerEC, ecsw.cachedAlwaysFlowImpersonationPolicy); + ecsw.wiIsValid = true; +#endif + currentThread.ExecutionContextBelongsToCurrentScope = false; + ecsw.thread = currentThread; + } + + + // Sets the given execution context object on the thread. + // Returns the previous one. + [System.Security.SecurityCritical] // auto-generated + [DynamicSecurityMethodAttribute()] + [MethodImplAttribute(MethodImplOptions.NoInlining)] // Methods containing StackCrawlMark local var has to be marked non-inlineable +#if FEATURE_CORRUPTING_EXCEPTIONS + [HandleProcessCorruptedStateExceptions] +#endif // FEATURE_CORRUPTING_EXCEPTIONS + internal static ExecutionContextSwitcher SetExecutionContext(ExecutionContext executionContext, bool preserveSyncCtx) + { +#if FEATURE_IMPERSONATION || FEATURE_COMPRESSEDSTACK + StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller; +#endif // #if FEATURE_IMPERSONATION || FEATURE_COMPRESSEDSTACK + + Contract.Assert(executionContext != null); + Contract.Assert(executionContext != s_dummyDefaultEC); + + // Set up the switcher object to return; + ExecutionContextSwitcher ecsw = new ExecutionContextSwitcher(); + + Thread currentThread = Thread.CurrentThread; + ExecutionContext.Reader outerEC = currentThread.GetExecutionContextReader(); + + ecsw.thread = currentThread; + ecsw.outerEC = outerEC; + ecsw.outerECBelongsToScope = currentThread.ExecutionContextBelongsToCurrentScope; + + if (preserveSyncCtx) + executionContext.SynchronizationContext = outerEC.SynchronizationContext; + executionContext.SynchronizationContextNoFlow = outerEC.SynchronizationContextNoFlow; + + currentThread.SetExecutionContext(executionContext, belongsToCurrentScope: true); + + RuntimeHelpers.PrepareConstrainedRegions(); + try + { + OnAsyncLocalContextChanged(outerEC.DangerousGetRawExecutionContext(), executionContext); + +#if FEATURE_IMPERSONATION || FEATURE_COMPRESSEDSTACK + //set the security context + SecurityContext sc = executionContext.SecurityContext; + if (sc != null) + { + // non-null SC: needs to be set + SecurityContext.Reader prevSeC = outerEC.SecurityContext; + ecsw.scsw = SecurityContext.SetSecurityContext(sc, prevSeC, false, ref stackMark); + } + else if (!SecurityContext.CurrentlyInDefaultFTSecurityContext(ecsw.outerEC)) + { + // null incoming SC, but we're currently not in FT: use static FTSC to set + SecurityContext.Reader prevSeC = outerEC.SecurityContext; + ecsw.scsw = SecurityContext.SetSecurityContext(SecurityContext.FullTrustSecurityContext, prevSeC, false, ref stackMark); + } +#endif // #if FEATURE_IMPERSONATION || FEATURE_COMPRESSEDSTACK +#if FEATURE_CAS_POLICY + // set the Host Context + HostExecutionContext hostContext = executionContext.HostExecutionContext; + if (hostContext != null) + { + ecsw.hecsw = HostExecutionContextManager.SetHostExecutionContextInternal(hostContext); + } +#endif // FEATURE_CAS_POLICY + } + catch + { + ecsw.UndoNoThrow(currentThread); + throw; + } + return ecsw; + } + + // + // Public CreateCopy. Used to copy captured ExecutionContexts so they can be reused multiple times. + // This should only copy the portion of the context that we actually capture. + // + [SecuritySafeCritical] + public ExecutionContext CreateCopy() + { + if (!isNewCapture) + { + throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_CannotCopyUsedContext")); + } + ExecutionContext ec = new ExecutionContext(); + ec.isNewCapture = true; + ec._syncContext = _syncContext == null ? null : _syncContext.CreateCopy(); + ec._localValues = _localValues; + ec._localChangeNotifications = _localChangeNotifications; +#if FEATURE_CAS_POLICY + // capture the host execution context + ec._hostExecutionContext = _hostExecutionContext == null ? null : _hostExecutionContext.CreateCopy(); +#endif // FEATURE_CAS_POLICY +#if FEATURE_IMPERSONATION || FEATURE_COMPRESSEDSTACK + if (_securityContext != null) + { + ec._securityContext = _securityContext.CreateCopy(); + ec._securityContext.ExecutionContext = ec; + } +#endif // #if FEATURE_IMPERSONATION || FEATURE_COMPRESSEDSTACK + +#if FEATURE_REMOTING + if (this._logicalCallContext != null) + ec.LogicalCallContext = (LogicalCallContext)this.LogicalCallContext.Clone(); + + Contract.Assert(this._illogicalCallContext == null); +#endif // #if FEATURE_REMOTING + + return ec; + } + + // + // Creates a complete copy, used for copy-on-write. + // + [SecuritySafeCritical] + internal ExecutionContext CreateMutableCopy() + { + Contract.Assert(!this.isNewCapture); + + ExecutionContext ec = new ExecutionContext(); + + // We don't deep-copy the SyncCtx, since we're still in the same context after copy-on-write. + ec._syncContext = this._syncContext; + ec._syncContextNoFlow = this._syncContextNoFlow; + +#if FEATURE_CAS_POLICY + // capture the host execution context + ec._hostExecutionContext = this._hostExecutionContext == null ? null : _hostExecutionContext.CreateCopy(); +#endif // FEATURE_CAS_POLICY + +#if FEATURE_IMPERSONATION || FEATURE_COMPRESSEDSTACK + if (_securityContext != null) + { + ec._securityContext = this._securityContext.CreateMutableCopy(); + ec._securityContext.ExecutionContext = ec; + } +#endif // #if FEATURE_IMPERSONATION || FEATURE_COMPRESSEDSTACK + +#if FEATURE_REMOTING + if (this._logicalCallContext != null) + ec.LogicalCallContext = (LogicalCallContext)this.LogicalCallContext.Clone(); + + if (this._illogicalCallContext != null) + ec.IllogicalCallContext = (IllogicalCallContext)this.IllogicalCallContext.CreateCopy(); +#endif // #if FEATURE_REMOTING + + ec._localValues = this._localValues; + ec._localChangeNotifications = this._localChangeNotifications; + ec.isFlowSuppressed = this.isFlowSuppressed; + + return ec; + } + + [System.Security.SecurityCritical] // auto-generated_required + public static AsyncFlowControl SuppressFlow() + { + if (IsFlowSuppressed()) + { + throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_CannotSupressFlowMultipleTimes")); + } + Contract.EndContractBlock(); + AsyncFlowControl afc = new AsyncFlowControl(); + afc.Setup(); + return afc; + } + + [SecuritySafeCritical] + public static void RestoreFlow() + { + ExecutionContext ec = Thread.CurrentThread.GetMutableExecutionContext(); + if (!ec.isFlowSuppressed) + { + throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_CannotRestoreUnsupressedFlow")); + } + ec.isFlowSuppressed = false; + } + + [Pure] + public static bool IsFlowSuppressed() + { + return Thread.CurrentThread.GetExecutionContextReader().IsFlowSuppressed; + } + + [System.Security.SecuritySafeCritical] // auto-generated + [MethodImplAttribute(MethodImplOptions.NoInlining)] // Methods containing StackCrawlMark local var has to be marked non-inlineable + public static ExecutionContext Capture() + { + // set up a stack mark for finding the caller + StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller; + return ExecutionContext.Capture(ref stackMark, CaptureOptions.None); + } + + // + // Captures an ExecutionContext with optimization for the "default" case, and captures a "null" synchronization context. + // When calling ExecutionContext.Run on the returned context, specify ignoreSyncCtx = true + // + [System.Security.SecuritySafeCritical] // auto-generated + [MethodImplAttribute(MethodImplOptions.NoInlining)] // Methods containing StackCrawlMark local var has to be marked non-inlineable + [FriendAccessAllowed] + internal static ExecutionContext FastCapture() + { + // set up a stack mark for finding the caller + StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller; + return ExecutionContext.Capture(ref stackMark, CaptureOptions.IgnoreSyncCtx | CaptureOptions.OptimizeDefaultCase); + } + + + [Flags] + internal enum CaptureOptions + { + None = 0x00, + + IgnoreSyncCtx = 0x01, //Don't flow SynchronizationContext + + OptimizeDefaultCase = 0x02, //Faster in the typical case, but can't show the result to users + // because they could modify the shared default EC. + // Use this only if you won't be exposing the captured EC to users. + } + + // internal helper to capture the current execution context using a passed in stack mark + [System.Security.SecurityCritical] // auto-generated + static internal ExecutionContext Capture(ref StackCrawlMark stackMark, CaptureOptions options) + { + ExecutionContext.Reader ecCurrent = Thread.CurrentThread.GetExecutionContextReader(); + + // check to see if Flow is suppressed + if (ecCurrent.IsFlowSuppressed) + return null; + + // + // Attempt to capture context. There may be nothing to capture... + // + +#if FEATURE_IMPERSONATION || FEATURE_COMPRESSEDSTACK + // capture the security context + SecurityContext secCtxNew = SecurityContext.Capture(ecCurrent, ref stackMark); +#endif // #if FEATURE_IMPERSONATION || FEATURE_COMPRESSEDSTACK +#if FEATURE_CAS_POLICY + // capture the host execution context + HostExecutionContext hostCtxNew = HostExecutionContextManager.CaptureHostExecutionContext(); +#endif // FEATURE_CAS_POLICY + + SynchronizationContext syncCtxNew = null; + +#if FEATURE_REMOTING + LogicalCallContext logCtxNew = null; +#endif + + if (!ecCurrent.IsNull) + { + // capture the sync context + if (0 == (options & CaptureOptions.IgnoreSyncCtx)) + syncCtxNew = (ecCurrent.SynchronizationContext == null) ? null : ecCurrent.SynchronizationContext.CreateCopy(); + +#if FEATURE_REMOTING + // copy over the Logical Call Context + if (ecCurrent.LogicalCallContext.HasInfo) + logCtxNew = ecCurrent.LogicalCallContext.Clone(); +#endif // #if FEATURE_REMOTING + } + + Dictionary<IAsyncLocal, object> localValues = null; + List<IAsyncLocal> localChangeNotifications = null; + if (!ecCurrent.IsNull) + { + localValues = ecCurrent.DangerousGetRawExecutionContext()._localValues; + localChangeNotifications = ecCurrent.DangerousGetRawExecutionContext()._localChangeNotifications; + } + + // + // If we didn't get anything but defaults, and we're allowed to return the + // dummy default EC, don't bother allocating a new context. + // + if (0 != (options & CaptureOptions.OptimizeDefaultCase) && +#if FEATURE_IMPERSONATION || FEATURE_COMPRESSEDSTACK + secCtxNew == null && +#endif +#if FEATURE_CAS_POLICY + hostCtxNew == null && +#endif // FEATURE_CAS_POLICY + syncCtxNew == null && +#if FEATURE_REMOTING + (logCtxNew == null || !logCtxNew.HasInfo) && +#endif // #if FEATURE_REMOTING + localValues == null && + localChangeNotifications == null + ) + { + return s_dummyDefaultEC; + } + + // + // Allocate the new context, and fill it in. + // + ExecutionContext ecNew = new ExecutionContext(); +#if FEATURE_IMPERSONATION || FEATURE_COMPRESSEDSTACK + ecNew.SecurityContext = secCtxNew; + if (ecNew.SecurityContext != null) + ecNew.SecurityContext.ExecutionContext = ecNew; +#endif +#if FEATURE_CAS_POLICY + ecNew._hostExecutionContext = hostCtxNew; +#endif // FEATURE_CAS_POLICY + ecNew._syncContext = syncCtxNew; +#if FEATURE_REMOTING + ecNew.LogicalCallContext = logCtxNew; +#endif // #if FEATURE_REMOTING + ecNew._localValues = localValues; + ecNew._localChangeNotifications = localChangeNotifications; + ecNew.isNewCapture = true; + + return ecNew; + } + + // + // Implementation of ISerializable + // + + [System.Security.SecurityCritical] // auto-generated_required + public void GetObjectData(SerializationInfo info, StreamingContext context) + { + if (info==null) + throw new ArgumentNullException("info"); + Contract.EndContractBlock(); + +#if FEATURE_REMOTING + if (_logicalCallContext != null) + { + info.AddValue("LogicalCallContext", _logicalCallContext, typeof(LogicalCallContext)); + } +#endif // #if FEATURE_REMOTING + } + + [System.Security.SecurityCritical] // auto-generated + private ExecutionContext(SerializationInfo info, StreamingContext context) + { + SerializationInfoEnumerator e = info.GetEnumerator(); + while (e.MoveNext()) + { +#if FEATURE_REMOTING + if (e.Name.Equals("LogicalCallContext")) + { + _logicalCallContext = (LogicalCallContext) e.Value; + } +#endif // #if FEATURE_REMOTING + } + } // ObjRef .ctor + + + [System.Security.SecurityCritical] // auto-generated + internal bool IsDefaultFTContext(bool ignoreSyncCtx) + { +#if FEATURE_CAS_POLICY + if (_hostExecutionContext != null) + return false; +#endif // FEATURE_CAS_POLICY + if (!ignoreSyncCtx && _syncContext != null) + return false; +#if FEATURE_IMPERSONATION || FEATURE_COMPRESSEDSTACK + if (_securityContext != null && !_securityContext.IsDefaultFTSecurityContext()) + return false; +#endif //#if FEATURE_IMPERSONATION || FEATURE_COMPRESSEDSTACK +#if FEATURE_REMOTING + if (_logicalCallContext != null && _logicalCallContext.HasInfo) + return false; + if (_illogicalCallContext != null && _illogicalCallContext.HasUserData) + return false; +#endif //#if FEATURE_REMOTING + return true; + } + } // class ExecutionContext + +#endif //FEATURE_CORECLR +} + + diff --git a/src/mscorlib/src/System/Threading/IObjectHandle.cs b/src/mscorlib/src/System/Threading/IObjectHandle.cs new file mode 100644 index 0000000000..464f06e52d --- /dev/null +++ b/src/mscorlib/src/System/Threading/IObjectHandle.cs @@ -0,0 +1,31 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +/*============================================================ +** +** +** +** IObjectHandle defines the interface for unwrapping objects. +** Objects that are marshal by value object can be returned through +** an indirection allowing the caller to control when the +** object is loaded into their domain. The caller can unwrap +** the object from the indirection through this interface. +** +** +===========================================================*/ +namespace System.Runtime.Remoting { + + using System; + using System.Runtime.InteropServices; + + [ InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown), + GuidAttribute("C460E2B4-E199-412a-8456-84DC3E4838C3") ] + [System.Runtime.InteropServices.ComVisible(true)] + public interface IObjectHandle { + // Unwrap the object. Implementers of this interface + // typically have an indirect referece to another object. + Object Unwrap(); + } +} + diff --git a/src/mscorlib/src/System/Threading/Interlocked.cs b/src/mscorlib/src/System/Threading/Interlocked.cs new file mode 100644 index 0000000000..50cc766d61 --- /dev/null +++ b/src/mscorlib/src/System/Threading/Interlocked.cs @@ -0,0 +1,233 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +// +namespace System.Threading +{ + using System; + using System.Security.Permissions; + using System.Runtime.CompilerServices; + using System.Runtime.ConstrainedExecution; + using System.Runtime.Versioning; + using System.Runtime; + + // After much discussion, we decided the Interlocked class doesn't need + // any HPA's for synchronization or external threading. They hurt C#'s + // codegen for the yield keyword, and arguably they didn't protect much. + // Instead, they penalized people (and compilers) for writing threadsafe + // code. + public static class Interlocked + { + /****************************** + * Increment + * Implemented: int + * long + *****************************/ + + [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] + public static int Increment(ref int location) + { + return Add(ref location, 1); + } + + [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] + public static long Increment(ref long location) + { + return Add(ref location, 1); + } + + /****************************** + * Decrement + * Implemented: int + * long + *****************************/ + + [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] + public static int Decrement(ref int location) + { + return Add(ref location, -1); + } + + public static long Decrement(ref long location) + { + return Add(ref location, -1); + } + + /****************************** + * Exchange + * Implemented: int + * long + * float + * double + * Object + * IntPtr + *****************************/ + + [MethodImplAttribute(MethodImplOptions.InternalCall)] + [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] + [System.Security.SecuritySafeCritical] + public static extern int Exchange(ref int location1, int value); + + [MethodImplAttribute(MethodImplOptions.InternalCall)] + [System.Security.SecuritySafeCritical] + public static extern long Exchange(ref long location1, long value); + + [MethodImplAttribute(MethodImplOptions.InternalCall)] + [System.Security.SecuritySafeCritical] + public static extern float Exchange(ref float location1, float value); + + [MethodImplAttribute(MethodImplOptions.InternalCall)] + [System.Security.SecuritySafeCritical] + public static extern double Exchange(ref double location1, double value); + + [MethodImplAttribute(MethodImplOptions.InternalCall)] + [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] + [System.Security.SecuritySafeCritical] + public static extern Object Exchange(ref Object location1, Object value); + + [MethodImplAttribute(MethodImplOptions.InternalCall)] + [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] + [System.Security.SecuritySafeCritical] + public static extern IntPtr Exchange(ref IntPtr location1, IntPtr value); + + [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] + [System.Runtime.InteropServices.ComVisible(false)] + [System.Security.SecuritySafeCritical] + public static T Exchange<T>(ref T location1, T value) where T : class + { + _Exchange(__makeref(location1), __makeref(value)); + //Since value is a local we use trash its data on return + // The Exchange replaces the data with new data + // so after the return "value" contains the original location1 + //See ExchangeGeneric for more details + return value; + } + + [MethodImplAttribute(MethodImplOptions.InternalCall)] + [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] + [System.Security.SecuritySafeCritical] + private static extern void _Exchange(TypedReference location1, TypedReference value); + + /****************************** + * CompareExchange + * Implemented: int + * long + * float + * double + * Object + * IntPtr + *****************************/ + + [MethodImplAttribute(MethodImplOptions.InternalCall)] + [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] + [System.Security.SecuritySafeCritical] + public static extern int CompareExchange(ref int location1, int value, int comparand); + + [MethodImplAttribute(MethodImplOptions.InternalCall)] + [System.Security.SecuritySafeCritical] + public static extern long CompareExchange(ref long location1, long value, long comparand); + + [MethodImplAttribute(MethodImplOptions.InternalCall)] + [System.Security.SecuritySafeCritical] + public static extern float CompareExchange(ref float location1, float value, float comparand); + + [MethodImplAttribute(MethodImplOptions.InternalCall)] + [System.Security.SecuritySafeCritical] + public static extern double CompareExchange(ref double location1, double value, double comparand); + + [MethodImplAttribute(MethodImplOptions.InternalCall)] + [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] + [System.Security.SecuritySafeCritical] + public static extern Object CompareExchange(ref Object location1, Object value, Object comparand); + + [MethodImplAttribute(MethodImplOptions.InternalCall)] + [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] + [System.Security.SecuritySafeCritical] + public static extern IntPtr CompareExchange(ref IntPtr location1, IntPtr value, IntPtr comparand); + + /***************************************************************** + * CompareExchange<T> + * + * Notice how CompareExchange<T>() uses the __makeref keyword + * to create two TypedReferences before calling _CompareExchange(). + * This is horribly slow. Ideally we would like CompareExchange<T>() + * to simply call CompareExchange(ref Object, Object, Object); + * however, this would require casting a "ref T" into a "ref Object", + * which is not legal in C#. + * + * Thus we opted to implement this in the JIT so that when it reads + * the method body for CompareExchange<T>() it gets back the + * following IL: + * + * ldarg.0 + * ldarg.1 + * ldarg.2 + * call System.Threading.Interlocked::CompareExchange(ref Object, Object, Object) + * ret + * + * See getILIntrinsicImplementationForInterlocked() in VM\JitInterface.cpp + * for details. + *****************************************************************/ + + [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] + [System.Runtime.InteropServices.ComVisible(false)] + [System.Security.SecuritySafeCritical] + public static T CompareExchange<T>(ref T location1, T value, T comparand) where T : class + { + // _CompareExchange() passes back the value read from location1 via local named 'value' + _CompareExchange(__makeref(location1), __makeref(value), comparand); + return value; + } + + [MethodImplAttribute(MethodImplOptions.InternalCall)] + [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] + [System.Security.SecuritySafeCritical] + private static extern void _CompareExchange(TypedReference location1, TypedReference value, Object comparand); + + // BCL-internal overload that returns success via a ref bool param, useful for reliable spin locks. + [MethodImplAttribute(MethodImplOptions.InternalCall)] + [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] + [System.Security.SecuritySafeCritical] + internal static extern int CompareExchange(ref int location1, int value, int comparand, ref bool succeeded); + + /****************************** + * Add + * Implemented: int + * long + *****************************/ + + [MethodImplAttribute(MethodImplOptions.InternalCall)] + [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] + internal static extern int ExchangeAdd(ref int location1, int value); + + [MethodImplAttribute(MethodImplOptions.InternalCall)] + internal static extern long ExchangeAdd(ref long location1, long value); + + [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] + public static int Add(ref int location1, int value) + { + return ExchangeAdd(ref location1, value) + value; + } + + [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] + public static long Add(ref long location1, long value) + { + return ExchangeAdd(ref location1, value) + value; + } + + /****************************** + * Read + *****************************/ + public static long Read(ref long location) + { + return Interlocked.CompareExchange(ref location,0,0); + } + + + public static void MemoryBarrier() + { + Thread.MemoryBarrier(); + } + } +} diff --git a/src/mscorlib/src/System/Threading/LazyInitializer.cs b/src/mscorlib/src/System/Threading/LazyInitializer.cs new file mode 100644 index 0000000000..c8e74e30e3 --- /dev/null +++ b/src/mscorlib/src/System/Threading/LazyInitializer.cs @@ -0,0 +1,265 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +// =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ +// +// +// +// a set of lightweight static helpers for lazy initialization. +// +// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + + +using System.Security.Permissions; +using System.Diagnostics.Contracts; +namespace System.Threading +{ + + /// <summary> + /// Specifies how a <see cref="T:System.Threading.Lazy{T}"/> instance should synchronize access among multiple threads. + /// </summary> + public enum LazyThreadSafetyMode + { + /// <summary> + /// This mode makes no guarantees around the thread-safety of the <see cref="T:System.Threading.Lazy{T}"/> instance. If used from multiple threads, the behavior of the <see cref="T:System.Threading.Lazy{T}"/> is undefined. + /// This mode should be used when a <see cref="T:System.Threading.Lazy{T}"/> is guaranteed to never be initialized from more than one thread simultaneously and high performance is crucial. + /// If valueFactory throws an exception when the <see cref="T:System.Threading.Lazy{T}"/> is initialized, the exception will be cached and returned on subsequent accesses to Value. Also, if valueFactory recursively + /// accesses Value on this <see cref="T:System.Threading.Lazy{T}"/> instance, a <see cref="T:System.InvalidOperationException"/> will be thrown. + /// </summary> + None, + + /// <summary> + /// When multiple threads attempt to simultaneously initialize a <see cref="T:System.Threading.Lazy{T}"/> instance, this mode allows each thread to execute the + /// valueFactory but only the first thread to complete initialization will be allowed to set the final value of the <see cref="T:System.Threading.Lazy{T}"/>. + /// Once initialized successfully, any future calls to Value will return the cached result. If valueFactory throws an exception on any thread, that exception will be + /// propagated out of Value. If any thread executes valueFactory without throwing an exception and, therefore, successfully sets the value, that value will be returned on + /// subsequent accesses to Value from any thread. If no thread succeeds in setting the value, IsValueCreated will remain false and subsequent accesses to Value will result in + /// the valueFactory delegate re-executing. Also, if valueFactory recursively accesses Value on this <see cref="T:System.Threading.Lazy{T}"/> instance, an exception will NOT be thrown. + /// </summary> + PublicationOnly, + + /// <summary> + /// This mode uses locks to ensure that only a single thread can initialize a <see cref="T:System.Threading.Lazy{T}"/> instance in a thread-safe manner. In general, + /// taken if this mode is used in conjunction with a <see cref="T:System.Threading.Lazy{T}"/> valueFactory delegate that uses locks internally, a deadlock can occur if not + /// handled carefully. If valueFactory throws an exception when the<see cref="T:System.Threading.Lazy{T}"/> is initialized, the exception will be cached and returned on + /// subsequent accesses to Value. Also, if valueFactory recursively accesses Value on this <see cref="T:System.Threading.Lazy{T}"/> instance, a <see cref="T:System.InvalidOperationException"/> will be thrown. + /// </summary> + ExecutionAndPublication + } + /// <summary> + /// Provides lazy initialization routines. + /// </summary> + /// <remarks> + /// These routines avoid needing to allocate a dedicated, lazy-initialization instance, instead using + /// references to ensure targets have been initialized as they are accessed. + /// </remarks> + [HostProtection(Synchronization = true, ExternalThreading = true)] + public static class LazyInitializer + { + /// <summary> + /// Initializes a target reference type with the type's default constructor if the target has not + /// already been initialized. + /// </summary> + /// <typeparam name="T">The refence type of the reference to be initialized.</typeparam> + /// <param name="target">A reference of type <typeparamref name="T"/> to initialize if it has not + /// already been initialized.</param> + /// <returns>The initialized reference of type <typeparamref name="T"/>.</returns> + /// <exception cref="T:System.MissingMemberException">Type <typeparamref name="T"/> does not have a default + /// constructor.</exception> + /// <exception cref="T:System.MemberAccessException"> + /// Permissions to access the constructor of type <typeparamref name="T"/> were missing. + /// </exception> + /// <remarks> + /// <para> + /// This method may only be used on reference types. To ensure initialization of value + /// types, see other overloads of EnsureInitialized. + /// </para> + /// <para> + /// This method may be used concurrently by multiple threads to initialize <paramref name="target"/>. + /// In the event that multiple threads access this method concurrently, multiple instances of <typeparamref name="T"/> + /// may be created, but only one will be stored into <paramref name="target"/>. In such an occurrence, this method will not dispose of the + /// objects that were not stored. If such objects must be disposed, it is up to the caller to determine + /// if an object was not used and to then dispose of the object appropriately. + /// </para> + /// </remarks> + public static T EnsureInitialized<T>(ref T target) where T : class + { + // Fast path. + if (Volatile.Read<T>(ref target) != null) + { + return target; + } + + return EnsureInitializedCore<T>(ref target, LazyHelpers<T>.s_activatorFactorySelector); + } + + /// <summary> + /// Initializes a target reference type using the specified function if it has not already been + /// initialized. + /// </summary> + /// <typeparam name="T">The reference type of the reference to be initialized.</typeparam> + /// <param name="target">The reference of type <typeparamref name="T"/> to initialize if it has not + /// already been initialized.</param> + /// <param name="valueFactory">The <see cref="T:System.Func{T}"/> invoked to initialize the + /// reference.</param> + /// <returns>The initialized reference of type <typeparamref name="T"/>.</returns> + /// <exception cref="T:System.MissingMemberException">Type <typeparamref name="T"/> does not have a + /// default constructor.</exception> + /// <exception cref="T:System.InvalidOperationException"><paramref name="valueFactory"/> returned + /// null.</exception> + /// <remarks> + /// <para> + /// This method may only be used on reference types, and <paramref name="valueFactory"/> may + /// not return a null reference (Nothing in Visual Basic). To ensure initialization of value types or + /// to allow null reference types, see other overloads of EnsureInitialized. + /// </para> + /// <para> + /// This method may be used concurrently by multiple threads to initialize <paramref name="target"/>. + /// In the event that multiple threads access this method concurrently, multiple instances of <typeparamref name="T"/> + /// may be created, but only one will be stored into <paramref name="target"/>. In such an occurrence, this method will not dispose of the + /// objects that were not stored. If such objects must be disposed, it is up to the caller to determine + /// if an object was not used and to then dispose of the object appropriately. + /// </para> + /// </remarks> + public static T EnsureInitialized<T>(ref T target, Func<T> valueFactory) where T : class + { + // Fast path. + if (Volatile.Read<T>(ref target) != null) + { + return target; + } + + return EnsureInitializedCore<T>(ref target, valueFactory); + } + + /// <summary> + /// Initialize the target using the given delegate (slow path). + /// </summary> + /// <typeparam name="T">The reference type of the reference to be initialized.</typeparam> + /// <param name="target">The variable that need to be initialized</param> + /// <param name="valueFactory">The delegate that will be executed to initialize the target</param> + /// <returns>The initialized variable</returns> + private static T EnsureInitializedCore<T>(ref T target, Func<T> valueFactory) where T : class + { + T value = valueFactory(); + if (value == null) + { + throw new InvalidOperationException(Environment.GetResourceString("Lazy_StaticInit_InvalidOperation")); + } + + Interlocked.CompareExchange(ref target, value, null); + Contract.Assert(target != null); + return target; + } + + + /// <summary> + /// Initializes a target reference or value type with its default constructor if it has not already + /// been initialized. + /// </summary> + /// <typeparam name="T">The type of the reference to be initialized.</typeparam> + /// <param name="target">A reference or value of type <typeparamref name="T"/> to initialize if it + /// has not already been initialized.</param> + /// <param name="initialized">A reference to a boolean that determines whether the target has already + /// been initialized.</param> + /// <param name="syncLock">A reference to an object used as the mutually exclusive lock for initializing + /// <paramref name="target"/>. If <paramref name="syncLock"/> is null, a new object will be instantiated.</param> + /// <returns>The initialized value of type <typeparamref name="T"/>.</returns> + public static T EnsureInitialized<T>(ref T target, ref bool initialized, ref object syncLock) + { + // Fast path. + if (Volatile.Read(ref initialized)) + { + return target; + } + + return EnsureInitializedCore<T>(ref target, ref initialized, ref syncLock, LazyHelpers<T>.s_activatorFactorySelector); + } + + /// <summary> + /// Initializes a target reference or value type with a specified function if it has not already been + /// initialized. + /// </summary> + /// <typeparam name="T">The type of the reference to be initialized.</typeparam> + /// <param name="target">A reference or value of type <typeparamref name="T"/> to initialize if it + /// has not already been initialized.</param> + /// <param name="initialized">A reference to a boolean that determines whether the target has already + /// been initialized.</param> + /// <param name="syncLock">A reference to an object used as the mutually exclusive lock for initializing + /// <paramref name="target"/>. If <paramref name="syncLock"/> is null, a new object will be instantiated.</param> + /// <param name="valueFactory">The <see cref="T:System.Func{T}"/> invoked to initialize the + /// reference or value.</param> + /// <returns>The initialized value of type <typeparamref name="T"/>.</returns> + public static T EnsureInitialized<T>(ref T target, ref bool initialized, ref object syncLock, Func<T> valueFactory) + { + // Fast path. + if (Volatile.Read(ref initialized)) + { + return target; + } + + + return EnsureInitializedCore<T>(ref target, ref initialized, ref syncLock, valueFactory); + } + + /// <summary> + /// Ensure the target is initialized and return the value (slow path). This overload permits nulls + /// and also works for value type targets. Uses the supplied function to create the value. + /// </summary> + /// <typeparam name="T">The type of target.</typeparam> + /// <param name="target">A reference to the target to be initialized.</param> + /// <param name="initialized">A reference to a location tracking whether the target has been initialized.</param> + /// <param name="syncLock">A reference to a location containing a mutual exclusive lock. If <paramref name="syncLock"/> is null, + /// a new object will be instantiated.</param> + /// <param name="valueFactory"> + /// The <see cref="T:System.Func{T}"/> to invoke in order to produce the lazily-initialized value. + /// </param> + /// <returns>The initialized object.</returns> + private static T EnsureInitializedCore<T>(ref T target, ref bool initialized, ref object syncLock, Func<T> valueFactory) + { + // Lazily initialize the lock if necessary. + object slock = syncLock; + if (slock == null) + { + object newLock = new object(); + slock = Interlocked.CompareExchange(ref syncLock, newLock, null); + if (slock == null) + { + slock = newLock; + } + } + + // Now double check that initialization is still required. + lock (slock) + { + if (!Volatile.Read(ref initialized)) + { + target = valueFactory(); + Volatile.Write(ref initialized, true); + } + } + + return target; + } + + } + + // Caches the activation selector function to avoid delegate allocations. + static class LazyHelpers<T> + { + internal static Func<T> s_activatorFactorySelector = new Func<T>(ActivatorFactorySelector); + + private static T ActivatorFactorySelector() + { + try + { + return (T)Activator.CreateInstance(typeof(T)); + } + catch (MissingMethodException) + { + throw new MissingMemberException(Environment.GetResourceString("Lazy_CreateValue_NoParameterlessCtorForT")); + } + } + } +} diff --git a/src/mscorlib/src/System/Threading/LockCookie.cs b/src/mscorlib/src/System/Threading/LockCookie.cs new file mode 100644 index 0000000000..c1fbfd828e --- /dev/null +++ b/src/mscorlib/src/System/Threading/LockCookie.cs @@ -0,0 +1,57 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +// +/*============================================================ +** +** +** +** Purpose: Defines the lock that implements +** single-writer/multiple-reader semantics +** +** +===========================================================*/ + +namespace System.Threading { + + using System; + [System.Runtime.InteropServices.ComVisible(true)] + public struct LockCookie + { + private int _dwFlags; + private int _dwWriterSeqNum; + private int _wReaderAndWriterLevel; + private int _dwThreadID; + + public override int GetHashCode() + { + return _dwFlags + _dwWriterSeqNum + _wReaderAndWriterLevel + _dwThreadID; + } + + public override bool Equals(Object obj) + { + if (obj is LockCookie) + return Equals((LockCookie)obj); + else + return false; + } + + public bool Equals(LockCookie obj) + { + return obj._dwFlags == _dwFlags && obj._dwWriterSeqNum == _dwWriterSeqNum && + obj._wReaderAndWriterLevel == _wReaderAndWriterLevel && obj._dwThreadID == _dwThreadID; + } + + public static bool operator ==(LockCookie a, LockCookie b) + { + return a.Equals(b); + } + + public static bool operator !=(LockCookie a, LockCookie b) + { + return !(a == b); + } + } +} + diff --git a/src/mscorlib/src/System/Threading/LockRecursionException.cs b/src/mscorlib/src/System/Threading/LockRecursionException.cs new file mode 100644 index 0000000000..c5e3146cbc --- /dev/null +++ b/src/mscorlib/src/System/Threading/LockRecursionException.cs @@ -0,0 +1,34 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +/*============================================================ +// +// +// +// Purpose: +// This exception represents a failed attempt to recursively +// acquire a lock, because the particular lock kind doesn't +// support it in its current state. +============================================================*/ + +namespace System.Threading +{ + using System; + using System.Runtime.Serialization; + using System.Runtime.CompilerServices; + + [Serializable] + [System.Security.Permissions.HostProtection(MayLeakOnAbort = true)] +#if !FEATURE_CORECLR + [TypeForwardedFrom("System.Core, Version=3.5.0.0, Culture=Neutral, PublicKeyToken=b77a5c561934e089")] +#endif + public class LockRecursionException : System.Exception + { + public LockRecursionException() { } + public LockRecursionException(string message) : base(message) { } + protected LockRecursionException(SerializationInfo info, StreamingContext context) : base(info, context) { } + public LockRecursionException(string message, Exception innerException) : base(message, innerException) { } + } + +} diff --git a/src/mscorlib/src/System/Threading/ManualResetEvent.cs b/src/mscorlib/src/System/Threading/ManualResetEvent.cs new file mode 100644 index 0000000000..504cfb423c --- /dev/null +++ b/src/mscorlib/src/System/Threading/ManualResetEvent.cs @@ -0,0 +1,27 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +// +/*============================================================================= +** +** +** +** Purpose: An example of a WaitHandle class +** +** +=============================================================================*/ +namespace System.Threading { + + using System; + using System.Security.Permissions; + using System.Runtime.InteropServices; + + [HostProtection(Synchronization=true, ExternalThreading=true)] +[System.Runtime.InteropServices.ComVisible(true)] + public sealed class ManualResetEvent : EventWaitHandle + { + public ManualResetEvent(bool initialState) : base(initialState,EventResetMode.ManualReset){} + } +} + diff --git a/src/mscorlib/src/System/Threading/ManualResetEventSlim.cs b/src/mscorlib/src/System/Threading/ManualResetEventSlim.cs new file mode 100644 index 0000000000..b6114ef917 --- /dev/null +++ b/src/mscorlib/src/System/Threading/ManualResetEventSlim.cs @@ -0,0 +1,813 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. +#pragma warning disable 0420 + +// =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ +// +// SlimManualResetEvent.cs +// +// +// An manual-reset event that mixes a little spinning with a true Win32 event. +// +// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + +using System; +using System.Diagnostics; +using System.Security.Permissions; +using System.Threading; +using System.Runtime.InteropServices; +using System.Diagnostics.Contracts; + +namespace System.Threading +{ + + // ManualResetEventSlim wraps a manual-reset event internally with a little bit of + // spinning. When an event will be set imminently, it is often advantageous to avoid + // a 4k+ cycle context switch in favor of briefly spinning. Therefore we layer on to + // a brief amount of spinning that should, on the average, make using the slim event + // cheaper than using Win32 events directly. This can be reset manually, much like + // a Win32 manual-reset would be. + // + // Notes: + // We lazily allocate the Win32 event internally. Therefore, the caller should + // always call Dispose to clean it up, just in case. This API is a no-op of the + // event wasn't allocated, but if it was, ensures that the event goes away + // eagerly, instead of waiting for finalization. + + /// <summary> + /// Provides a slimmed down version of <see cref="T:System.Threading.ManualResetEvent"/>. + /// </summary> + /// <remarks> + /// All public and protected members of <see cref="ManualResetEventSlim"/> are thread-safe and may be used + /// concurrently from multiple threads, with the exception of Dispose, which + /// must only be used when all other operations on the <see cref="ManualResetEventSlim"/> have + /// completed, and Reset, which should only be used when no other threads are + /// accessing the event. + /// </remarks> + [ComVisible(false)] + [DebuggerDisplay("Set = {IsSet}")] + [HostProtection(Synchronization = true, ExternalThreading = true)] + public class ManualResetEventSlim : IDisposable + { + // These are the default spin counts we use on single-proc and MP machines. + private const int DEFAULT_SPIN_SP = 1; + private const int DEFAULT_SPIN_MP = SpinWait.YIELD_THRESHOLD; + + private volatile object m_lock; + // A lock used for waiting and pulsing. Lazily initialized via EnsureLockObjectCreated() + + private volatile ManualResetEvent m_eventObj; // A true Win32 event used for waiting. + + // -- State -- // + //For a packed word a uint would seem better, but Interlocked.* doesn't support them as uint isn't CLS-compliant. + private volatile int m_combinedState; //ie a UInt32. Used for the state items listed below. + + //1-bit for signalled state + private const int SignalledState_BitMask = unchecked((int)0x80000000);//1000 0000 0000 0000 0000 0000 0000 0000 + private const int SignalledState_ShiftCount = 31; + + //1-bit for disposed state + private const int Dispose_BitMask = unchecked((int)0x40000000);//0100 0000 0000 0000 0000 0000 0000 0000 + + //11-bits for m_spinCount + private const int SpinCountState_BitMask = unchecked((int)0x3FF80000); //0011 1111 1111 1000 0000 0000 0000 0000 + private const int SpinCountState_ShiftCount = 19; + private const int SpinCountState_MaxValue = (1 << 11) - 1; //2047 + + //19-bits for m_waiters. This allows support of 512K threads waiting which should be ample + private const int NumWaitersState_BitMask = unchecked((int)0x0007FFFF); // 0000 0000 0000 0111 1111 1111 1111 1111 + private const int NumWaitersState_ShiftCount = 0; + private const int NumWaitersState_MaxValue = (1 << 19) - 1; //512K-1 + // ----------- // + +#if DEBUG + private static int s_nextId; // The next id that will be given out. + private int m_id = Interlocked.Increment(ref s_nextId); // A unique id for debugging purposes only. + private long m_lastSetTime; + private long m_lastResetTime; +#endif + + /// <summary> + /// Gets the underlying <see cref="T:System.Threading.WaitHandle"/> object for this <see + /// cref="ManualResetEventSlim"/>. + /// </summary> + /// <value>The underlying <see cref="T:System.Threading.WaitHandle"/> event object fore this <see + /// cref="ManualResetEventSlim"/>.</value> + /// <remarks> + /// Accessing this property forces initialization of an underlying event object if one hasn't + /// already been created. To simply wait on this <see cref="ManualResetEventSlim"/>, + /// the public Wait methods should be preferred. + /// </remarks> + public WaitHandle WaitHandle + { + + get + { + ThrowIfDisposed(); + if (m_eventObj == null) + { + // Lazily initialize the event object if needed. + LazyInitializeEvent(); + } + + return m_eventObj; + } + } + + /// <summary> + /// Gets whether the event is set. + /// </summary> + /// <value>true if the event has is set; otherwise, false.</value> + public bool IsSet + { + get + { + return 0 != ExtractStatePortion(m_combinedState, SignalledState_BitMask); + } + + private set + { + UpdateStateAtomically(((value) ? 1 : 0) << SignalledState_ShiftCount, SignalledState_BitMask); + } + } + + /// <summary> + /// Gets the number of spin waits that will be occur before falling back to a true wait. + /// </summary> + public int SpinCount + { + get + { + return ExtractStatePortionAndShiftRight(m_combinedState, SpinCountState_BitMask, SpinCountState_ShiftCount); + } + + private set + { + Contract.Assert(value >= 0, "SpinCount is a restricted-width integer. The value supplied is outside the legal range."); + Contract.Assert(value <= SpinCountState_MaxValue, "SpinCount is a restricted-width integer. The value supplied is outside the legal range."); + // Don't worry about thread safety because it's set one time from the constructor + m_combinedState = (m_combinedState & ~SpinCountState_BitMask) | (value << SpinCountState_ShiftCount); + } + } + + /// <summary> + /// How many threads are waiting. + /// </summary> + private int Waiters + { + get + { + return ExtractStatePortionAndShiftRight(m_combinedState, NumWaitersState_BitMask, NumWaitersState_ShiftCount); + } + + set + { + //setting to <0 would indicate an internal flaw, hence Assert is appropriate. + Contract.Assert(value >= 0, "NumWaiters should never be less than zero. This indicates an internal error."); + + // it is possible for the max number of waiters to be exceeded via user-code, hence we use a real exception here. + if (value >= NumWaitersState_MaxValue) + throw new InvalidOperationException(String.Format(Environment.GetResourceString("ManualResetEventSlim_ctor_TooManyWaiters"), NumWaitersState_MaxValue)); + + UpdateStateAtomically(value << NumWaitersState_ShiftCount, NumWaitersState_BitMask); + } + + } + + //----------------------------------------------------------------------------------- + // Constructs a new event, optionally specifying the initial state and spin count. + // The defaults are that the event is unsignaled and some reasonable default spin. + // + + /// <summary> + /// Initializes a new instance of the <see cref="ManualResetEventSlim"/> + /// class with an initial state of nonsignaled. + /// </summary> + public ManualResetEventSlim() + : this(false) + { + + } + + /// <summary> + /// Initializes a new instance of the <see cref="ManualResetEventSlim"/> + /// class with a Boolen value indicating whether to set the intial state to signaled. + /// </summary> + /// <param name="initialState">true to set the initial state signaled; false to set the initial state + /// to nonsignaled.</param> + public ManualResetEventSlim(bool initialState) + { + // Specify the defualt spin count, and use default spin if we're + // on a multi-processor machine. Otherwise, we won't. + Initialize(initialState, DEFAULT_SPIN_MP); + } + + /// <summary> + /// Initializes a new instance of the <see cref="ManualResetEventSlim"/> + /// class with a Boolen value indicating whether to set the intial state to signaled and a specified + /// spin count. + /// </summary> + /// <param name="initialState">true to set the initial state to signaled; false to set the initial state + /// to nonsignaled.</param> + /// <param name="spinCount">The number of spin waits that will occur before falling back to a true + /// wait.</param> + /// <exception cref="T:System.ArgumentOutOfRangeException"><paramref name="spinCount"/> is less than + /// 0 or greater than the maximum allowed value.</exception> + public ManualResetEventSlim(bool initialState, int spinCount) + { + if (spinCount < 0) + { + throw new ArgumentOutOfRangeException("spinCount"); + } + + if (spinCount > SpinCountState_MaxValue) + { + throw new ArgumentOutOfRangeException( + "spinCount", + String.Format(Environment.GetResourceString("ManualResetEventSlim_ctor_SpinCountOutOfRange"), SpinCountState_MaxValue)); + } + + // We will suppress default spin because the user specified a count. + Initialize(initialState, spinCount); + } + + /// <summary> + /// Initializes the internal state of the event. + /// </summary> + /// <param name="initialState">Whether the event is set initially or not.</param> + /// <param name="spinCount">The spin count that decides when the event will block.</param> + private void Initialize(bool initialState, int spinCount) + { + this.m_combinedState = initialState ? (1 << SignalledState_ShiftCount) : 0; + //the spinCount argument has been validated by the ctors. + //but we now sanity check our predefined constants. + Contract.Assert(DEFAULT_SPIN_SP >= 0, "Internal error - DEFAULT_SPIN_SP is outside the legal range."); + Contract.Assert(DEFAULT_SPIN_SP <= SpinCountState_MaxValue, "Internal error - DEFAULT_SPIN_SP is outside the legal range."); + + SpinCount = PlatformHelper.IsSingleProcessor ? DEFAULT_SPIN_SP : spinCount; + + } + + /// <summary> + /// Helper to ensure the lock object is created before first use. + /// </summary> + private void EnsureLockObjectCreated() + { + Contract.Ensures(m_lock != null); + + if (m_lock != null) + return; + + object newObj = new object(); + Interlocked.CompareExchange(ref m_lock, newObj, null); // failure is benign. Someone else set the value. + } + + /// <summary> + /// This method lazily initializes the event object. It uses CAS to guarantee that + /// many threads racing to call this at once don't result in more than one event + /// being stored and used. The event will be signaled or unsignaled depending on + /// the state of the thin-event itself, with synchronization taken into account. + /// </summary> + /// <returns>True if a new event was created and stored, false otherwise.</returns> + private bool LazyInitializeEvent() + { + bool preInitializeIsSet = IsSet; + ManualResetEvent newEventObj = new ManualResetEvent(preInitializeIsSet); + + // We have to CAS this in case we are racing with another thread. We must + // guarantee only one event is actually stored in this field. + if (Interlocked.CompareExchange(ref m_eventObj, newEventObj, null) != null) + { + // Someone else set the value due to a race condition. Destroy the garbage event. + newEventObj.Close(); + + return false; + } + else + { + + // Now that the event is published, verify that the state hasn't changed since + // we snapped the preInitializeState. Another thread could have done that + // between our initial observation above and here. The barrier incurred from + // the CAS above (in addition to m_state being volatile) prevents this read + // from moving earlier and being collapsed with our original one. + bool currentIsSet = IsSet; + if (currentIsSet != preInitializeIsSet) + { + Contract.Assert(currentIsSet, + "The only safe concurrent transition is from unset->set: detected set->unset."); + + // We saw it as unsignaled, but it has since become set. + lock (newEventObj) + { + // If our event hasn't already been disposed of, we must set it. + if (m_eventObj == newEventObj) + { + newEventObj.Set(); + } + } + } + + return true; + } + } + + /// <summary> + /// Sets the state of the event to signaled, which allows one or more threads waiting on the event to + /// proceed. + /// </summary> + public void Set() + { + Set(false); + } + + /// <summary> + /// Private helper to actually perform the Set. + /// </summary> + /// <param name="duringCancellation">Indicates whether we are calling Set() during cancellation.</param> + /// <exception cref="T:System.OperationCanceledException">The object has been canceled.</exception> + private void Set(bool duringCancellation) + { + // We need to ensure that IsSet=true does not get reordered past the read of m_eventObj + // This would be a legal movement according to the .NET memory model. + // The code is safe as IsSet involves an Interlocked.CompareExchange which provides a full memory barrier. + IsSet = true; + + // If there are waiting threads, we need to pulse them. + if (Waiters > 0) + { + Contract.Assert(m_lock != null); //if waiters>0, then m_lock has already been created. + lock (m_lock) + { + + Monitor.PulseAll(m_lock); + } + } + + ManualResetEvent eventObj = m_eventObj; + + //Design-decision: do not set the event if we are in cancellation -> better to deadlock than to wake up waiters incorrectly + //It would be preferable to wake up the event and have it throw OCE. This requires MRE to implement cancellation logic + + if (eventObj != null && !duringCancellation) + { + // We must surround this call to Set in a lock. The reason is fairly subtle. + // Sometimes a thread will issue a Wait and wake up after we have set m_state, + // but before we have gotten around to setting m_eventObj (just below). That's + // because Wait first checks m_state and will only access the event if absolutely + // necessary. However, the coding pattern { event.Wait(); event.Dispose() } is + // quite common, and we must support it. If the waiter woke up and disposed of + // the event object before the setter has finished, however, we would try to set a + // now-disposed Win32 event. Crash! To deal with this race condition, we use a lock to + // protect access to the event object when setting and disposing of it. We also + // double-check that the event has not become null in the meantime when in the lock. + + lock (eventObj) + { + if (m_eventObj != null) + { + // If somebody is waiting, we must set the event. + m_eventObj.Set(); + } + } + } + +#if DEBUG + m_lastSetTime = DateTime.UtcNow.Ticks; +#endif + } + + /// <summary> + /// Sets the state of the event to nonsignaled, which causes threads to block. + /// </summary> + /// <remarks> + /// Unlike most of the members of <see cref="ManualResetEventSlim"/>, <see cref="Reset()"/> is not + /// thread-safe and may not be used concurrently with other members of this instance. + /// </remarks> + public void Reset() + { + ThrowIfDisposed(); + // If there's an event, reset it. + if (m_eventObj != null) + { + m_eventObj.Reset(); + } + + // There is a race condition here. If another thread Sets the event, we will get into a state + // where m_state will be unsignaled, yet the Win32 event object will have been signaled. + // This could cause waiting threads to wake up even though the event is in an + // unsignaled state. This is fine -- those that are calling Reset concurrently are + // responsible for doing "the right thing" -- e.g. rechecking the condition and + // resetting the event manually. + + // And finally set our state back to unsignaled. + IsSet = false; + +#if DEBUG + m_lastResetTime = DateTime.UtcNow.Ticks; +#endif + } + + /// <summary> + /// Blocks the current thread until the current <see cref="ManualResetEventSlim"/> is set. + /// </summary> + /// <exception cref="T:System.InvalidOperationException"> + /// The maximum number of waiters has been exceeded. + /// </exception> + /// <remarks> + /// The caller of this method blocks indefinitely until the current instance is set. The caller will + /// return immediately if the event is currently in a set state. + /// </remarks> + public void Wait() + { + Wait(Timeout.Infinite, new CancellationToken()); + } + + /// <summary> + /// Blocks the current thread until the current <see cref="ManualResetEventSlim"/> receives a signal, + /// while observing a <see cref="T:System.Threading.CancellationToken"/>. + /// </summary> + /// <param name="cancellationToken">The <see cref="T:System.Threading.CancellationToken"/> to + /// observe.</param> + /// <exception cref="T:System.InvalidOperationException"> + /// The maximum number of waiters has been exceeded. + /// </exception> + /// <exception cref="T:System.OperationCanceledExcepton"><paramref name="cancellationToken"/> was + /// canceled.</exception> + /// <remarks> + /// The caller of this method blocks indefinitely until the current instance is set. The caller will + /// return immediately if the event is currently in a set state. + /// </remarks> + public void Wait(CancellationToken cancellationToken) + { + Wait(Timeout.Infinite, cancellationToken); + } + + /// <summary> + /// Blocks the current thread until the current <see cref="ManualResetEventSlim"/> is set, using a + /// <see cref="T:System.TimeSpan"/> to measure the time interval. + /// </summary> + /// <param name="timeout">A <see cref="System.TimeSpan"/> that represents the number of milliseconds + /// to wait, or a <see cref="System.TimeSpan"/> that represents -1 milliseconds to wait indefinitely. + /// </param> + /// <returns>true if the <see cref="System.Threading.ManualResetEventSlim"/> was set; otherwise, + /// false.</returns> + /// <exception cref="T:System.ArgumentOutOfRangeException"><paramref name="timeout"/> is a negative + /// number other than -1 milliseconds, which represents an infinite time-out -or- timeout is greater + /// than <see cref="System.Int32.MaxValue"/>.</exception> + /// <exception cref="T:System.InvalidOperationException"> + /// The maximum number of waiters has been exceeded. + /// </exception> + public bool Wait(TimeSpan timeout) + { + long totalMilliseconds = (long)timeout.TotalMilliseconds; + if (totalMilliseconds < -1 || totalMilliseconds > int.MaxValue) + { + throw new ArgumentOutOfRangeException("timeout"); + } + + return Wait((int)totalMilliseconds, new CancellationToken()); + } + + /// <summary> + /// Blocks the current thread until the current <see cref="ManualResetEventSlim"/> is set, using a + /// <see cref="T:System.TimeSpan"/> to measure the time interval, while observing a <see + /// cref="T:System.Threading.CancellationToken"/>. + /// </summary> + /// <param name="timeout">A <see cref="System.TimeSpan"/> that represents the number of milliseconds + /// to wait, or a <see cref="System.TimeSpan"/> that represents -1 milliseconds to wait indefinitely. + /// </param> + /// <param name="cancellationToken">The <see cref="T:System.Threading.CancellationToken"/> to + /// observe.</param> + /// <returns>true if the <see cref="System.Threading.ManualResetEventSlim"/> was set; otherwise, + /// false.</returns> + /// <exception cref="T:System.ArgumentOutOfRangeException"><paramref name="timeout"/> is a negative + /// number other than -1 milliseconds, which represents an infinite time-out -or- timeout is greater + /// than <see cref="System.Int32.MaxValue"/>.</exception> + /// <exception cref="T:System.Threading.OperationCanceledException"><paramref + /// name="cancellationToken"/> was canceled.</exception> + /// <exception cref="T:System.InvalidOperationException"> + /// The maximum number of waiters has been exceeded. + /// </exception> + public bool Wait(TimeSpan timeout, CancellationToken cancellationToken) + { + long totalMilliseconds = (long)timeout.TotalMilliseconds; + if (totalMilliseconds < -1 || totalMilliseconds > int.MaxValue) + { + throw new ArgumentOutOfRangeException("timeout"); + } + + return Wait((int)totalMilliseconds, cancellationToken); + } + + /// <summary> + /// Blocks the current thread until the current <see cref="ManualResetEventSlim"/> is set, using a + /// 32-bit signed integer to measure the time interval. + /// </summary> + /// <param name="millisecondsTimeout">The number of milliseconds to wait, or <see + /// cref="Timeout.Infinite"/>(-1) to wait indefinitely.</param> + /// <returns>true if the <see cref="System.Threading.ManualResetEventSlim"/> was set; otherwise, + /// false.</returns> + /// <exception cref="T:System.ArgumentOutOfRangeException"><paramref name="millisecondsTimeout"/> is a + /// negative number other than -1, which represents an infinite time-out.</exception> + /// <exception cref="T:System.InvalidOperationException"> + /// The maximum number of waiters has been exceeded. + /// </exception> + public bool Wait(int millisecondsTimeout) + { + return Wait(millisecondsTimeout, new CancellationToken()); + } + + /// <summary> + /// Blocks the current thread until the current <see cref="ManualResetEventSlim"/> is set, using a + /// 32-bit signed integer to measure the time interval, while observing a <see + /// cref="T:System.Threading.CancellationToken"/>. + /// </summary> + /// <param name="millisecondsTimeout">The number of milliseconds to wait, or <see + /// cref="Timeout.Infinite"/>(-1) to wait indefinitely.</param> + /// <param name="cancellationToken">The <see cref="T:System.Threading.CancellationToken"/> to + /// observe.</param> + /// <returns>true if the <see cref="System.Threading.ManualResetEventSlim"/> was set; otherwise, + /// false.</returns> + /// <exception cref="T:System.ArgumentOutOfRangeException"><paramref name="millisecondsTimeout"/> is a + /// negative number other than -1, which represents an infinite time-out.</exception> + /// <exception cref="T:System.InvalidOperationException"> + /// The maximum number of waiters has been exceeded. + /// </exception> + /// <exception cref="T:System.Threading.OperationCanceledException"><paramref + /// name="cancellationToken"/> was canceled.</exception> + public bool Wait(int millisecondsTimeout, CancellationToken cancellationToken) + { + ThrowIfDisposed(); + cancellationToken.ThrowIfCancellationRequested(); // an early convenience check + + if (millisecondsTimeout < -1) + { + throw new ArgumentOutOfRangeException("millisecondsTimeout"); + } + + if (!IsSet) + { + if (millisecondsTimeout == 0) + { + // For 0-timeouts, we just return immediately. + return false; + } + + + // We spin briefly before falling back to allocating and/or waiting on a true event. + uint startTime = 0; + bool bNeedTimeoutAdjustment = false; + int realMillisecondsTimeout = millisecondsTimeout; //this will be adjusted if necessary. + + if (millisecondsTimeout != Timeout.Infinite) + { + // We will account for time spent spinning, so that we can decrement it from our + // timeout. In most cases the time spent in this section will be negligible. But + // we can't discount the possibility of our thread being switched out for a lengthy + // period of time. The timeout adjustments only take effect when and if we actually + // decide to block in the kernel below. + + startTime = TimeoutHelper.GetTime(); + bNeedTimeoutAdjustment = true; + } + + //spin + int HOW_MANY_SPIN_BEFORE_YIELD = 10; + int HOW_MANY_YIELD_EVERY_SLEEP_0 = 5; + int HOW_MANY_YIELD_EVERY_SLEEP_1 = 20; + + int spinCount = SpinCount; + for (int i = 0; i < spinCount; i++) + { + if (IsSet) + { + return true; + } + + else if (i < HOW_MANY_SPIN_BEFORE_YIELD) + { + if (i == HOW_MANY_SPIN_BEFORE_YIELD / 2) + { + Thread.Yield(); + } + else + { + Thread.SpinWait(PlatformHelper.ProcessorCount * (4 << i)); + } + } + else if (i % HOW_MANY_YIELD_EVERY_SLEEP_1 == 0) + { + Thread.Sleep(1); + } + else if (i % HOW_MANY_YIELD_EVERY_SLEEP_0 == 0) + { + Thread.Sleep(0); + } + else + { + Thread.Yield(); + } + + if (i >= 100 && i % 10 == 0) // check the cancellation token if the user passed a very large spin count + cancellationToken.ThrowIfCancellationRequested(); + } + + // Now enter the lock and wait. + EnsureLockObjectCreated(); + + // We must register and deregister the token outside of the lock, to avoid deadlocks. + using (cancellationToken.InternalRegisterWithoutEC(s_cancellationTokenCallback, this)) + { + lock (m_lock) + { + // Loop to cope with spurious wakeups from other waits being canceled + while (!IsSet) + { + // If our token was canceled, we must throw and exit. + cancellationToken.ThrowIfCancellationRequested(); + + //update timeout (delays in wait commencement are due to spinning and/or spurious wakeups from other waits being canceled) + if (bNeedTimeoutAdjustment) + { + realMillisecondsTimeout = TimeoutHelper.UpdateTimeOut(startTime, millisecondsTimeout); + if (realMillisecondsTimeout <= 0) + return false; + } + + // There is a race condition that Set will fail to see that there are waiters as Set does not take the lock, + // so after updating waiters, we must check IsSet again. + // Also, we must ensure there cannot be any reordering of the assignment to Waiters and the + // read from IsSet. This is guaranteed as Waiters{set;} involves an Interlocked.CompareExchange + // operation which provides a full memory barrier. + // If we see IsSet=false, then we are guaranteed that Set() will see that we are + // waiting and will pulse the monitor correctly. + + Waiters = Waiters + 1; + + if (IsSet) //This check must occur after updating Waiters. + { + Waiters--; //revert the increment. + return true; + } + + // Now finally perform the wait. + try + { + // ** the actual wait ** + if (!Monitor.Wait(m_lock, realMillisecondsTimeout)) + return false; //return immediately if the timeout has expired. + } + finally + { + // Clean up: we're done waiting. + Waiters = Waiters - 1; + } + + // Now just loop back around, and the right thing will happen. Either: + // 1. We had a spurious wake-up due to some other wait being canceled via a different cancellationToken (rewait) + // or 2. the wait was successful. (the loop will break) + + } + } + } + } // automatically disposes (and deregisters) the callback + + return true; //done. The wait was satisfied. + } + + /// <summary> + /// Releases all resources used by the current instance of <see cref="ManualResetEventSlim"/>. + /// </summary> + /// <remarks> + /// Unlike most of the members of <see cref="ManualResetEventSlim"/>, <see cref="Dispose()"/> is not + /// thread-safe and may not be used concurrently with other members of this instance. + /// </remarks> + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + /// <summary> + /// When overridden in a derived class, releases the unmanaged resources used by the + /// <see cref="ManualResetEventSlim"/>, and optionally releases the managed resources. + /// </summary> + /// <param name="disposing">true to release both managed and unmanaged resources; + /// false to release only unmanaged resources.</param> + /// <remarks> + /// Unlike most of the members of <see cref="ManualResetEventSlim"/>, <see cref="Dispose(Boolean)"/> is not + /// thread-safe and may not be used concurrently with other members of this instance. + /// </remarks> + protected virtual void Dispose(bool disposing) + { + if ((m_combinedState & Dispose_BitMask) != 0) + return; // already disposed + + m_combinedState |= Dispose_BitMask; //set the dispose bit + if (disposing) + { + // We will dispose of the event object. We do this under a lock to protect + // against the race condition outlined in the Set method above. + ManualResetEvent eventObj = m_eventObj; + if (eventObj != null) + { + lock (eventObj) + { + eventObj.Close(); + m_eventObj = null; + } + } + } + } + + /// <summary> + /// Throw ObjectDisposedException if the MRES is disposed + /// </summary> + private void ThrowIfDisposed() + { + if ((m_combinedState & Dispose_BitMask) != 0) + throw new ObjectDisposedException(Environment.GetResourceString("ManualResetEventSlim_Disposed")); + } + + /// <summary> + /// Private helper method to wake up waiters when a cancellationToken gets canceled. + /// </summary> + private static Action<object> s_cancellationTokenCallback = new Action<object>(CancellationTokenCallback); + private static void CancellationTokenCallback(object obj) + { + ManualResetEventSlim mre = obj as ManualResetEventSlim; + Contract.Assert(mre != null, "Expected a ManualResetEventSlim"); + Contract.Assert(mre.m_lock != null); //the lock should have been created before this callback is registered for use. + lock (mre.m_lock) + { + Monitor.PulseAll(mre.m_lock); // awaken all waiters + } + } + + /// <summary> + /// Private helper method for updating parts of a bit-string state value. + /// Mainly called from the IsSet and Waiters properties setters + /// </summary> + /// <remarks> + /// Note: the parameter types must be int as CompareExchange cannot take a Uint + /// </remarks> + /// <param name="newBits">The new value</param> + /// <param name="updateBitsMask">The mask used to set the bits</param> + private void UpdateStateAtomically(int newBits, int updateBitsMask) + { + SpinWait sw = new SpinWait(); + + Contract.Assert((newBits | updateBitsMask) == updateBitsMask, "newBits do not fall within the updateBitsMask."); + + do + { + int oldState = m_combinedState; // cache the old value for testing in CAS + + // Procedure:(1) zero the updateBits. eg oldState = [11111111] flag= [00111000] newState = [11000111] + // then (2) map in the newBits. eg [11000111] newBits=00101000, newState=[11101111] + int newState = (oldState & ~updateBitsMask) | newBits; + + if (Interlocked.CompareExchange(ref m_combinedState, newState, oldState) == oldState) + { + return; + } + + sw.SpinOnce(); + } while (true); + } + + /// <summary> + /// Private helper method - performs Mask and shift, particular helpful to extract a field from a packed word. + /// eg ExtractStatePortionAndShiftRight(0x12345678, 0xFF000000, 24) => 0x12, ie extracting the top 8-bits as a simple integer + /// + /// ?? is there a common place to put this rather than being private to MRES? + /// </summary> + /// <param name="state"></param> + /// <param name="mask"></param> + /// <param name="rightBitShiftCount"></param> + /// <returns></returns> + private static int ExtractStatePortionAndShiftRight(int state, int mask, int rightBitShiftCount) + { + //convert to uint before shifting so that right-shift does not replicate the sign-bit, + //then convert back to int. + return unchecked((int)(((uint)(state & mask)) >> rightBitShiftCount)); + } + + /// <summary> + /// Performs a Mask operation, but does not perform the shift. + /// This is acceptable for boolean values for which the shift is unnecessary + /// eg (val & Mask) != 0 is an appropriate way to extract a boolean rather than using + /// ((val & Mask) >> shiftAmount) == 1 + /// + /// ?? is there a common place to put this rather than being private to MRES? + /// </summary> + /// <param name="state"></param> + /// <param name="mask"></param> + private static int ExtractStatePortion(int state, int mask) + { + return state & mask; + } + } +} diff --git a/src/mscorlib/src/System/Threading/Monitor.cs b/src/mscorlib/src/System/Threading/Monitor.cs new file mode 100644 index 0000000000..415948b425 --- /dev/null +++ b/src/mscorlib/src/System/Threading/Monitor.cs @@ -0,0 +1,256 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +// +/*============================================================================= +** +** +** +** Purpose: Synchronizes access to a shared resource or region of code in a multi-threaded +** program. +** +** +=============================================================================*/ + + +namespace System.Threading { + + using System; + using System.Security.Permissions; + using System.Runtime; + using System.Runtime.Remoting; + using System.Threading; + using System.Runtime.CompilerServices; + using System.Runtime.ConstrainedExecution; + using System.Runtime.Versioning; + using System.Diagnostics.Contracts; + + [HostProtection(Synchronization=true, ExternalThreading=true)] + [System.Runtime.InteropServices.ComVisible(true)] + public static class Monitor + { + /*========================================================================= + ** Obtain the monitor lock of obj. Will block if another thread holds the lock + ** Will not block if the current thread holds the lock, + ** however the caller must ensure that the same number of Exit + ** calls are made as there were Enter calls. + ** + ** Exceptions: ArgumentNullException if object is null. + =========================================================================*/ + [System.Security.SecuritySafeCritical] + [MethodImplAttribute(MethodImplOptions.InternalCall)] + public static extern void Enter(Object obj); + + + // Use a ref bool instead of out to ensure that unverifiable code must + // initialize this value to something. If we used out, the value + // could be uninitialized if we threw an exception in our prolog. + // The JIT should inline this method to allow check of lockTaken argument to be optimized out + // in the typical case. Note that the method has to be transparent for inlining to be allowed by the VM. + public static void Enter(Object obj, ref bool lockTaken) + { + if (lockTaken) + ThrowLockTakenException(); + + ReliableEnter(obj, ref lockTaken); + Contract.Assert(lockTaken); + } + + private static void ThrowLockTakenException() + { + throw new ArgumentException(Environment.GetResourceString("Argument_MustBeFalse"), "lockTaken"); + } + + [System.Security.SecuritySafeCritical] + [MethodImplAttribute(MethodImplOptions.InternalCall)] + private static extern void ReliableEnter(Object obj, ref bool lockTaken); + + + + /*========================================================================= + ** Release the monitor lock. If one or more threads are waiting to acquire the + ** lock, and the current thread has executed as many Exits as + ** Enters, one of the threads will be unblocked and allowed to proceed. + ** + ** Exceptions: ArgumentNullException if object is null. + ** SynchronizationLockException if the current thread does not + ** own the lock. + =========================================================================*/ + [System.Security.SecuritySafeCritical] + [MethodImplAttribute(MethodImplOptions.InternalCall)] + [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] + public static extern void Exit(Object obj); + + /*========================================================================= + ** Similar to Enter, but will never block. That is, if the current thread can + ** acquire the monitor lock without blocking, it will do so and TRUE will + ** be returned. Otherwise FALSE will be returned. + ** + ** Exceptions: ArgumentNullException if object is null. + =========================================================================*/ + public static bool TryEnter(Object obj) + { + bool lockTaken = false; + TryEnter(obj, 0, ref lockTaken); + return lockTaken; + } + + // The JIT should inline this method to allow check of lockTaken argument to be optimized out + // in the typical case. Note that the method has to be transparent for inlining to be allowed by the VM. + public static void TryEnter(Object obj, ref bool lockTaken) + { + if (lockTaken) + ThrowLockTakenException(); + + ReliableEnterTimeout(obj, 0, ref lockTaken); + } + + /*========================================================================= + ** Version of TryEnter that will block, but only up to a timeout period + ** expressed in milliseconds. If timeout == Timeout.Infinite the method + ** becomes equivalent to Enter. + ** + ** Exceptions: ArgumentNullException if object is null. + ** ArgumentException if timeout < 0. + =========================================================================*/ + // The JIT should inline this method to allow check of lockTaken argument to be optimized out + // in the typical case. Note that the method has to be transparent for inlining to be allowed by the VM. + public static bool TryEnter(Object obj, int millisecondsTimeout) + { + bool lockTaken = false; + TryEnter(obj, millisecondsTimeout, ref lockTaken); + return lockTaken; + } + + private static int MillisecondsTimeoutFromTimeSpan(TimeSpan timeout) + { + long tm = (long)timeout.TotalMilliseconds; + if (tm < -1 || tm > (long)Int32.MaxValue) + throw new ArgumentOutOfRangeException("timeout", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegOrNegative1")); + return (int)tm; + } + + public static bool TryEnter(Object obj, TimeSpan timeout) + { + return TryEnter(obj, MillisecondsTimeoutFromTimeSpan(timeout)); + } + + // The JIT should inline this method to allow check of lockTaken argument to be optimized out + // in the typical case. Note that the method has to be transparent for inlining to be allowed by the VM. + public static void TryEnter(Object obj, int millisecondsTimeout, ref bool lockTaken) + { + if (lockTaken) + ThrowLockTakenException(); + + ReliableEnterTimeout(obj, millisecondsTimeout, ref lockTaken); + } + + public static void TryEnter(Object obj, TimeSpan timeout, ref bool lockTaken) + { + if (lockTaken) + ThrowLockTakenException(); + + ReliableEnterTimeout(obj, MillisecondsTimeoutFromTimeSpan(timeout), ref lockTaken); + } + + [System.Security.SecuritySafeCritical] + [MethodImplAttribute(MethodImplOptions.InternalCall)] + private static extern void ReliableEnterTimeout(Object obj, int timeout, ref bool lockTaken); + + [System.Security.SecuritySafeCritical] + public static bool IsEntered(object obj) + { + if (obj == null) + throw new ArgumentNullException("obj"); + + return IsEnteredNative(obj); + } + + [System.Security.SecurityCritical] + [MethodImplAttribute(MethodImplOptions.InternalCall)] + private static extern bool IsEnteredNative(Object obj); + + /*======================================================================== + ** Waits for notification from the object (via a Pulse/PulseAll). + ** timeout indicates how long to wait before the method returns. + ** This method acquires the monitor waithandle for the object + ** If this thread holds the monitor lock for the object, it releases it. + ** On exit from the method, it obtains the monitor lock back. + ** If exitContext is true then the synchronization domain for the context + ** (if in a synchronized context) is exited before the wait and reacquired + ** + ** Exceptions: ArgumentNullException if object is null. + ========================================================================*/ + [System.Security.SecurityCritical] // auto-generated + [MethodImplAttribute(MethodImplOptions.InternalCall)] + private static extern bool ObjWait(bool exitContext, int millisecondsTimeout, Object obj); + + [System.Security.SecuritySafeCritical] // auto-generated + public static bool Wait(Object obj, int millisecondsTimeout, bool exitContext) + { + if (obj == null) + throw (new ArgumentNullException("obj")); + return ObjWait(exitContext, millisecondsTimeout, obj); + } + + public static bool Wait(Object obj, TimeSpan timeout, bool exitContext) + { + return Wait(obj, MillisecondsTimeoutFromTimeSpan(timeout), exitContext); + } + + public static bool Wait(Object obj, int millisecondsTimeout) + { + return Wait(obj, millisecondsTimeout, false); + } + + public static bool Wait(Object obj, TimeSpan timeout) + { + return Wait(obj, MillisecondsTimeoutFromTimeSpan(timeout), false); + } + + public static bool Wait(Object obj) + { + return Wait(obj, Timeout.Infinite, false); + } + + /*======================================================================== + ** Sends a notification to a single waiting object. + * Exceptions: SynchronizationLockException if this method is not called inside + * a synchronized block of code. + ========================================================================*/ + [System.Security.SecurityCritical] // auto-generated + [MethodImplAttribute(MethodImplOptions.InternalCall)] + private static extern void ObjPulse(Object obj); + + [System.Security.SecuritySafeCritical] // auto-generated + public static void Pulse(Object obj) + { + if (obj == null) + { + throw new ArgumentNullException("obj"); + } + Contract.EndContractBlock(); + + ObjPulse(obj); + } + /*======================================================================== + ** Sends a notification to all waiting objects. + ========================================================================*/ + [System.Security.SecurityCritical] // auto-generated + [MethodImplAttribute(MethodImplOptions.InternalCall)] + private static extern void ObjPulseAll(Object obj); + + [System.Security.SecuritySafeCritical] // auto-generated + public static void PulseAll(Object obj) + { + if (obj == null) + { + throw new ArgumentNullException("obj"); + } + Contract.EndContractBlock(); + + ObjPulseAll(obj); + } + } +} diff --git a/src/mscorlib/src/System/Threading/Mutex.cs b/src/mscorlib/src/System/Threading/Mutex.cs new file mode 100644 index 0000000000..2e9b68176d --- /dev/null +++ b/src/mscorlib/src/System/Threading/Mutex.cs @@ -0,0 +1,488 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +// +/*============================================================================= +** +** +** +** Purpose: synchronization primitive that can also be used for interprocess synchronization +** +** +=============================================================================*/ +namespace System.Threading +{ + using System; + using System.Threading; + using System.Runtime.CompilerServices; + using System.Security.Permissions; + using System.IO; + using Microsoft.Win32; + using Microsoft.Win32.SafeHandles; + using System.Runtime.InteropServices; + using System.Runtime.ConstrainedExecution; + using System.Runtime.Versioning; + using System.Security; + using System.Diagnostics.Contracts; + +#if FEATURE_MACL + using System.Security.AccessControl; +#endif + + [HostProtection(Synchronization=true, ExternalThreading=true)] + [ComVisible(true)] + public sealed class Mutex : WaitHandle + { + static bool dummyBool; + +#if !FEATURE_MACL + public class MutexSecurity { + } +#endif + + [System.Security.SecurityCritical] // auto-generated_required + [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)] + public Mutex(bool initiallyOwned, String name, out bool createdNew) + : this(initiallyOwned, name, out createdNew, (MutexSecurity)null) + { + } + + [System.Security.SecurityCritical] // auto-generated_required + [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)] + public unsafe Mutex(bool initiallyOwned, String name, out bool createdNew, MutexSecurity mutexSecurity) + { + if (name == string.Empty) + { + // Empty name is treated as an unnamed mutex. Set to null, and we will check for null from now on. + name = null; + } +#if !PLATFORM_UNIX + if (name != null && System.IO.Path.MaxPath < name.Length) + { + throw new ArgumentException(Environment.GetResourceString("Argument_WaitHandleNameTooLong", Path.MaxPath), "name"); + } +#endif + Contract.EndContractBlock(); + Win32Native.SECURITY_ATTRIBUTES secAttrs = null; +#if FEATURE_MACL + // For ACL's, get the security descriptor from the MutexSecurity. + if (mutexSecurity != null) { + + secAttrs = new Win32Native.SECURITY_ATTRIBUTES(); + secAttrs.nLength = (int)Marshal.SizeOf(secAttrs); + + byte[] sd = mutexSecurity.GetSecurityDescriptorBinaryForm(); + byte* pSecDescriptor = stackalloc byte[sd.Length]; + Buffer.Memcpy(pSecDescriptor, 0, sd, 0, sd.Length); + secAttrs.pSecurityDescriptor = pSecDescriptor; + } +#endif + + CreateMutexWithGuaranteedCleanup(initiallyOwned, name, out createdNew, secAttrs); + } + + [System.Security.SecurityCritical] // auto-generated_required + [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)] + internal Mutex(bool initiallyOwned, String name, out bool createdNew, Win32Native.SECURITY_ATTRIBUTES secAttrs) + { + if (name == string.Empty) + { + // Empty name is treated as an unnamed mutex. Set to null, and we will check for null from now on. + name = null; + } +#if !PLATFORM_UNIX + if (name != null && System.IO.Path.MaxPath < name.Length) + { + throw new ArgumentException(Environment.GetResourceString("Argument_WaitHandleNameTooLong", Path.MaxPath), "name"); + } +#endif + Contract.EndContractBlock(); + + CreateMutexWithGuaranteedCleanup(initiallyOwned, name, out createdNew, secAttrs); + } + + [System.Security.SecurityCritical] // auto-generated_required + [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)] + internal void CreateMutexWithGuaranteedCleanup(bool initiallyOwned, String name, out bool createdNew, Win32Native.SECURITY_ATTRIBUTES secAttrs) + { + RuntimeHelpers.CleanupCode cleanupCode = new RuntimeHelpers.CleanupCode(MutexCleanupCode); + MutexCleanupInfo cleanupInfo = new MutexCleanupInfo(null, false); + MutexTryCodeHelper tryCodeHelper = new MutexTryCodeHelper(initiallyOwned, cleanupInfo, name, secAttrs, this); + RuntimeHelpers.TryCode tryCode = new RuntimeHelpers.TryCode(tryCodeHelper.MutexTryCode); + RuntimeHelpers.ExecuteCodeWithGuaranteedCleanup( + tryCode, + cleanupCode, + cleanupInfo); + createdNew = tryCodeHelper.m_newMutex; + } + + internal class MutexTryCodeHelper + { + bool m_initiallyOwned; + MutexCleanupInfo m_cleanupInfo; + internal bool m_newMutex; + String m_name; + [System.Security.SecurityCritical] // auto-generated + Win32Native.SECURITY_ATTRIBUTES m_secAttrs; + Mutex m_mutex; + + [System.Security.SecurityCritical] // auto-generated + [PrePrepareMethod] + internal MutexTryCodeHelper(bool initiallyOwned,MutexCleanupInfo cleanupInfo, String name, Win32Native.SECURITY_ATTRIBUTES secAttrs, Mutex mutex) + { + Contract.Assert(name == null || name.Length != 0); + + m_initiallyOwned = initiallyOwned; + m_cleanupInfo = cleanupInfo; + m_name = name; + m_secAttrs = secAttrs; + m_mutex = mutex; + } + + [System.Security.SecurityCritical] // auto-generated + [PrePrepareMethod] + internal void MutexTryCode(object userData) + { + SafeWaitHandle mutexHandle = null; + // try block + RuntimeHelpers.PrepareConstrainedRegions(); + try + { + } + finally + { + if (m_initiallyOwned) + { + m_cleanupInfo.inCriticalRegion = true; +#if !FEATURE_CORECLR + Thread.BeginThreadAffinity(); + Thread.BeginCriticalRegion(); +#endif //!FEATURE_CORECLR + } + } + + int errorCode = 0; + RuntimeHelpers.PrepareConstrainedRegions(); + try + { + } + finally + { + errorCode = CreateMutexHandle(m_initiallyOwned, m_name, m_secAttrs, out mutexHandle); + } + + if (mutexHandle.IsInvalid) + { + mutexHandle.SetHandleAsInvalid(); + if (m_name != null) + { + switch (errorCode) + { +#if PLATFORM_UNIX + case Win32Native.ERROR_FILENAME_EXCED_RANGE: + // On Unix, length validation is done by CoreCLR's PAL after converting to utf-8 + throw new ArgumentException(Environment.GetResourceString("Argument_WaitHandleNameTooLong", Path.MaxPathComponentLength), "name"); +#endif + + case Win32Native.ERROR_INVALID_HANDLE: + throw new WaitHandleCannotBeOpenedException(Environment.GetResourceString("Threading.WaitHandleCannotBeOpenedException_InvalidHandle", m_name)); + } + } + __Error.WinIOError(errorCode, m_name); + } + m_newMutex = errorCode != Win32Native.ERROR_ALREADY_EXISTS; + m_mutex.SetHandleInternal(mutexHandle); + + m_mutex.hasThreadAffinity = true; + + } + } + + [System.Security.SecurityCritical] // auto-generated + [PrePrepareMethod] + private void MutexCleanupCode(Object userData, bool exceptionThrown) + { + MutexCleanupInfo cleanupInfo = (MutexCleanupInfo) userData; + + // If hasThreadAffinity isn't true, we've thrown an exception in the above try, and we must free the mutex + // on this OS thread before ending our thread affninity. + if(!hasThreadAffinity) { + if (cleanupInfo.mutexHandle != null && !cleanupInfo.mutexHandle.IsInvalid) { + if( cleanupInfo.inCriticalRegion) { + Win32Native.ReleaseMutex(cleanupInfo.mutexHandle); + } + cleanupInfo.mutexHandle.Dispose(); + + } + + if( cleanupInfo.inCriticalRegion) { +#if !FEATURE_CORECLR + Thread.EndCriticalRegion(); + Thread.EndThreadAffinity(); +#endif + } + } + } + + internal class MutexCleanupInfo + { + [System.Security.SecurityCritical] // auto-generated + internal SafeWaitHandle mutexHandle; + internal bool inCriticalRegion; + [System.Security.SecurityCritical] // auto-generated + internal MutexCleanupInfo(SafeWaitHandle mutexHandle, bool inCriticalRegion) + { + this.mutexHandle = mutexHandle; + this.inCriticalRegion = inCriticalRegion; + } + } + + [System.Security.SecurityCritical] // auto-generated_required + [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)] + public Mutex(bool initiallyOwned, String name) : this(initiallyOwned, name, out dummyBool) { + } + + [System.Security.SecuritySafeCritical] // auto-generated + [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)] + public Mutex(bool initiallyOwned) : this(initiallyOwned, null, out dummyBool) + { + } + + [System.Security.SecuritySafeCritical] // auto-generated + [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)] + public Mutex() : this(false, null, out dummyBool) + { + } + + [System.Security.SecurityCritical] // auto-generated + [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)] + private Mutex(SafeWaitHandle handle) + { + SetHandleInternal(handle); + hasThreadAffinity = true; + } + + [System.Security.SecurityCritical] // auto-generated_required + public static Mutex OpenExisting(string name) + { +#if !FEATURE_MACL + return OpenExisting(name, (MutexRights) 0); +#else // FEATURE_MACL + return OpenExisting(name, MutexRights.Modify | MutexRights.Synchronize); +#endif // FEATURE_MACL + } + +#if !FEATURE_MACL + public enum MutexRights + { + } +#endif + + [System.Security.SecurityCritical] // auto-generated_required + public static Mutex OpenExisting(string name, MutexRights rights) + { + Mutex result; + switch (OpenExistingWorker(name, rights, out result)) + { + case OpenExistingResult.NameNotFound: + throw new WaitHandleCannotBeOpenedException(); + + case OpenExistingResult.NameInvalid: + throw new WaitHandleCannotBeOpenedException(Environment.GetResourceString("Threading.WaitHandleCannotBeOpenedException_InvalidHandle", name)); + + case OpenExistingResult.PathNotFound: + __Error.WinIOError(Win32Native.ERROR_PATH_NOT_FOUND, name); + return result; //never executes + + default: + return result; + } + } + + [System.Security.SecurityCritical] // auto-generated_required + public static bool TryOpenExisting(string name, out Mutex result) + { +#if !FEATURE_MACL + return OpenExistingWorker(name, (MutexRights)0, out result) == OpenExistingResult.Success; +#else // FEATURE_MACL + return OpenExistingWorker(name, MutexRights.Modify | MutexRights.Synchronize, out result) == OpenExistingResult.Success; +#endif // FEATURE_MACL + } + + [System.Security.SecurityCritical] // auto-generated_required + public static bool TryOpenExisting(string name, MutexRights rights, out Mutex result) + { + return OpenExistingWorker(name, rights, out result) == OpenExistingResult.Success; + } + + [System.Security.SecurityCritical] + private static OpenExistingResult OpenExistingWorker(string name, MutexRights rights, out Mutex result) + { + if (name == null) + { + throw new ArgumentNullException("name", Environment.GetResourceString("ArgumentNull_WithParamName")); + } + + if(name.Length == 0) + { + throw new ArgumentException(Environment.GetResourceString("Argument_EmptyName"), "name"); + } +#if !PLATFORM_UNIX + if(System.IO.Path.MaxPath < name.Length) + { + throw new ArgumentException(Environment.GetResourceString("Argument_WaitHandleNameTooLong", Path.MaxPath), "name"); + } +#endif + Contract.EndContractBlock(); + + result = null; + + // To allow users to view & edit the ACL's, call OpenMutex + // with parameters to allow us to view & edit the ACL. This will + // fail if we don't have permission to view or edit the ACL's. + // If that happens, ask for less permissions. +#if FEATURE_MACL + SafeWaitHandle myHandle = Win32Native.OpenMutex((int) rights, false, name); +#else + SafeWaitHandle myHandle = Win32Native.OpenMutex(Win32Native.MUTEX_MODIFY_STATE | Win32Native.SYNCHRONIZE, false, name); +#endif + + int errorCode = 0; + if (myHandle.IsInvalid) + { + errorCode = Marshal.GetLastWin32Error(); + +#if PLATFORM_UNIX + if (name != null && errorCode == Win32Native.ERROR_FILENAME_EXCED_RANGE) + { + // On Unix, length validation is done by CoreCLR's PAL after converting to utf-8 + throw new ArgumentException(Environment.GetResourceString("Argument_WaitHandleNameTooLong", Path.MaxPathComponentLength), "name"); + } +#endif + + if(Win32Native.ERROR_FILE_NOT_FOUND == errorCode || Win32Native.ERROR_INVALID_NAME == errorCode) + return OpenExistingResult.NameNotFound; + if (Win32Native.ERROR_PATH_NOT_FOUND == errorCode) + return OpenExistingResult.PathNotFound; + if (null != name && Win32Native.ERROR_INVALID_HANDLE == errorCode) + return OpenExistingResult.NameInvalid; + + // this is for passed through Win32Native Errors + __Error.WinIOError(errorCode,name); + } + + result = new Mutex(myHandle); + return OpenExistingResult.Success; + } + + // Note: To call ReleaseMutex, you must have an ACL granting you + // MUTEX_MODIFY_STATE rights (0x0001). The other interesting value + // in a Mutex's ACL is MUTEX_ALL_ACCESS (0x1F0001). + [System.Security.SecuritySafeCritical] // auto-generated + [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)] + public void ReleaseMutex() + { + if (Win32Native.ReleaseMutex(safeWaitHandle)) + { +#if !FEATURE_CORECLR + Thread.EndCriticalRegion(); + Thread.EndThreadAffinity(); +#endif + } + else + { +#if FEATURE_CORECLR + throw new Exception(Environment.GetResourceString("Arg_SynchronizationLockException")); +#else + throw new ApplicationException(Environment.GetResourceString("Arg_SynchronizationLockException")); +#endif // FEATURE_CORECLR + } + } + + [System.Security.SecurityCritical] // auto-generated + [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)] + static int CreateMutexHandle(bool initiallyOwned, String name, Win32Native.SECURITY_ATTRIBUTES securityAttribute, out SafeWaitHandle mutexHandle) { + int errorCode; + bool fAffinity = false; + + while(true) { + mutexHandle = Win32Native.CreateMutex(securityAttribute, initiallyOwned, name); + errorCode = Marshal.GetLastWin32Error(); + if( !mutexHandle.IsInvalid) { + break; + } + + if( errorCode == Win32Native.ERROR_ACCESS_DENIED) { + // If a mutex with the name already exists, OS will try to open it with FullAccess. + // It might fail if we don't have enough access. In that case, we try to open the mutex will modify and synchronize access. + // + + RuntimeHelpers.PrepareConstrainedRegions(); + try + { + try + { + } + finally + { +#if !FEATURE_CORECLR + Thread.BeginThreadAffinity(); +#endif + fAffinity = true; + } + mutexHandle = Win32Native.OpenMutex(Win32Native.MUTEX_MODIFY_STATE | Win32Native.SYNCHRONIZE, false, name); + if(!mutexHandle.IsInvalid) + { + errorCode = Win32Native.ERROR_ALREADY_EXISTS; + } + else + { + errorCode = Marshal.GetLastWin32Error(); + } + } + finally + { + if (fAffinity) { +#if !FEATURE_CORECLR + Thread.EndThreadAffinity(); +#endif + } + } + + // There could be a race condition here, the other owner of the mutex can free the mutex, + // We need to retry creation in that case. + if( errorCode != Win32Native.ERROR_FILE_NOT_FOUND) { + if( errorCode == Win32Native.ERROR_SUCCESS) { + errorCode = Win32Native.ERROR_ALREADY_EXISTS; + } + break; + } + } + else { + break; + } + } + return errorCode; + } + +#if FEATURE_MACL + [System.Security.SecuritySafeCritical] // auto-generated + public MutexSecurity GetAccessControl() + { + return new MutexSecurity(safeWaitHandle, AccessControlSections.Access | AccessControlSections.Owner | AccessControlSections.Group); + } + + [System.Security.SecuritySafeCritical] // auto-generated + public void SetAccessControl(MutexSecurity mutexSecurity) + { + if (mutexSecurity == null) + throw new ArgumentNullException("mutexSecurity"); + Contract.EndContractBlock(); + + mutexSecurity.Persist(safeWaitHandle); + } +#endif + + } +} diff --git a/src/mscorlib/src/System/Threading/Overlapped.cs b/src/mscorlib/src/System/Threading/Overlapped.cs new file mode 100644 index 0000000000..3f9fbc4989 --- /dev/null +++ b/src/mscorlib/src/System/Threading/Overlapped.cs @@ -0,0 +1,423 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +// + +/* + * This files defines the following types: + * - NativeOverlapped + * - _IOCompletionCallback + * - OverlappedData + * - Overlapped + * - OverlappedDataCache + */ + +/*============================================================================= +** +** +** +** Purpose: Class for converting information to and from the native +** overlapped structure used in asynchronous file i/o +** +** +=============================================================================*/ + + +namespace System.Threading +{ + using System; + using System.Runtime.InteropServices; + using System.Runtime.CompilerServices; + using System.Runtime.Versioning; + using System.Security; + using System.Security.Permissions; + using System.Runtime.ConstrainedExecution; + using System.Diagnostics.Contracts; + using System.Collections.Concurrent; + + #region struct NativeOverlapped + + // Valuetype that represents the (unmanaged) Win32 OVERLAPPED structure + // the layout of this structure must be identical to OVERLAPPED. + // The first five matches OVERLAPPED structure. + // The remaining are reserved at the end + [System.Runtime.InteropServices.StructLayout(LayoutKind.Sequential)] + [System.Runtime.InteropServices.ComVisible(true)] + public struct NativeOverlapped + { + public IntPtr InternalLow; + public IntPtr InternalHigh; + public int OffsetLow; + public int OffsetHigh; + public IntPtr EventHandle; + } + + #endregion struct NativeOverlapped + + + #region class _IOCompletionCallback + + unsafe internal class _IOCompletionCallback + { + [System.Security.SecurityCritical] // auto-generated + IOCompletionCallback _ioCompletionCallback; + ExecutionContext _executionContext; + uint _errorCode; // Error code + uint _numBytes; // No. of bytes transferred + [SecurityCritical] + NativeOverlapped* _pOVERLAP; + + [System.Security.SecuritySafeCritical] // auto-generated + static _IOCompletionCallback() + { + } + + [System.Security.SecurityCritical] // auto-generated + internal _IOCompletionCallback(IOCompletionCallback ioCompletionCallback, ref StackCrawlMark stackMark) + { + _ioCompletionCallback = ioCompletionCallback; + // clone the exection context + _executionContext = ExecutionContext.Capture( + ref stackMark, + ExecutionContext.CaptureOptions.IgnoreSyncCtx | ExecutionContext.CaptureOptions.OptimizeDefaultCase); + } + // Context callback: same sig for SendOrPostCallback and ContextCallback + #if FEATURE_CORECLR + [System.Security.SecurityCritical] // auto-generated + #endif + static internal ContextCallback _ccb = new ContextCallback(IOCompletionCallback_Context); + [System.Security.SecurityCritical] + static internal void IOCompletionCallback_Context(Object state) + { + _IOCompletionCallback helper = (_IOCompletionCallback)state; + Contract.Assert(helper != null,"_IOCompletionCallback cannot be null"); + helper._ioCompletionCallback(helper._errorCode, helper._numBytes, helper._pOVERLAP); + } + + + // call back helper + [System.Security.SecurityCritical] // auto-generated + static unsafe internal void PerformIOCompletionCallback(uint errorCode, // Error code + uint numBytes, // No. of bytes transferred + NativeOverlapped* pOVERLAP // ptr to OVERLAP structure + ) + { + Overlapped overlapped; + _IOCompletionCallback helper; + + do + { + overlapped = OverlappedData.GetOverlappedFromNative(pOVERLAP).m_overlapped; + helper = overlapped.iocbHelper; + + if (helper == null || helper._executionContext == null || helper._executionContext.IsDefaultFTContext(true)) + { + // We got here because of UnsafePack (or) Pack with EC flow supressed + IOCompletionCallback callback = overlapped.UserCallback; + callback( errorCode, numBytes, pOVERLAP); + } + else + { + // We got here because of Pack + helper._errorCode = errorCode; + helper._numBytes = numBytes; + helper._pOVERLAP = pOVERLAP; + using (ExecutionContext executionContext = helper._executionContext.CreateCopy()) + ExecutionContext.Run(executionContext, _ccb, helper, true); + } + + //Quickly check the VM again, to see if a packet has arrived. + + OverlappedData.CheckVMForIOPacket(out pOVERLAP, out errorCode, out numBytes); + + } while (pOVERLAP != null); + + } + } + + #endregion class _IOCompletionCallback + + + #region class OverlappedData + + sealed internal class OverlappedData + { + // ! If you make any change to the layout here, you need to make matching change + // ! to OverlappedObject in vm\nativeoverlapped.h + internal IAsyncResult m_asyncResult; + [System.Security.SecurityCritical] // auto-generated + internal IOCompletionCallback m_iocb; + internal _IOCompletionCallback m_iocbHelper; + internal Overlapped m_overlapped; + private Object m_userObject; + private IntPtr m_pinSelf; + private IntPtr m_userObjectInternal; + private int m_AppDomainId; +#pragma warning disable 414 // Field is not used from managed. +#pragma warning disable 169 + private byte m_isArray; + private byte m_toBeCleaned; +#pragma warning restore 414 +#pragma warning restore 169 + internal NativeOverlapped m_nativeOverlapped; + +#if FEATURE_CORECLR + // Adding an empty default ctor for annotation purposes + [System.Security.SecuritySafeCritical] // auto-generated + internal OverlappedData(){} +#endif // FEATURE_CORECLR + + + [System.Security.SecurityCritical] + internal void ReInitialize() + { + m_asyncResult = null; + m_iocb = null; + m_iocbHelper = null; + m_overlapped = null; + m_userObject = null; + Contract.Assert(m_pinSelf.IsNull(), "OverlappedData has not been freed: m_pinSelf"); + m_pinSelf = (IntPtr)0; + m_userObjectInternal = (IntPtr)0; + Contract.Assert(m_AppDomainId == 0 || m_AppDomainId == AppDomain.CurrentDomain.Id, "OverlappedData is not in the current domain"); + m_AppDomainId = 0; + m_nativeOverlapped.EventHandle = (IntPtr)0; + m_isArray = 0; + m_nativeOverlapped.InternalLow = (IntPtr)0; + m_nativeOverlapped.InternalHigh = (IntPtr)0; + } + + [System.Security.SecurityCritical] // auto-generated + [MethodImplAttribute(MethodImplOptions.NoInlining)] // Methods containing StackCrawlMark local var has to be marked non-inlineable + unsafe internal NativeOverlapped* Pack(IOCompletionCallback iocb, Object userData) + { + if (!m_pinSelf.IsNull()) { + throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_Overlapped_Pack")); + } + StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller; + + if (iocb != null) + { + m_iocbHelper = new _IOCompletionCallback(iocb, ref stackMark); + m_iocb = iocb; + } + else + { + m_iocbHelper = null; + m_iocb = null; + } + m_userObject = userData; + if (m_userObject != null) + { + if (m_userObject.GetType() == typeof(Object[])) + { + m_isArray = 1; + } + else + { + m_isArray = 0; + } + } + return AllocateNativeOverlapped(); + } + + [System.Security.SecurityCritical] // auto-generated_required + unsafe internal NativeOverlapped* UnsafePack(IOCompletionCallback iocb, Object userData) + { + if (!m_pinSelf.IsNull()) { + throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_Overlapped_Pack")); + } + m_userObject = userData; + if (m_userObject != null) + { + if (m_userObject.GetType() == typeof(Object[])) + { + m_isArray = 1; + } + else + { + m_isArray = 0; + } + } + m_iocb = iocb; + m_iocbHelper = null; + return AllocateNativeOverlapped(); + } + + [ComVisible(false)] + internal IntPtr UserHandle + { + get { return m_nativeOverlapped.EventHandle; } + set { m_nativeOverlapped.EventHandle = value; } + } + + [System.Security.SecurityCritical] // auto-generated + [MethodImplAttribute(MethodImplOptions.InternalCall)] + unsafe private extern NativeOverlapped* AllocateNativeOverlapped(); + + [System.Security.SecurityCritical] // auto-generated + [MethodImplAttribute(MethodImplOptions.InternalCall)] + unsafe internal static extern void FreeNativeOverlapped(NativeOverlapped* nativeOverlappedPtr); + + [System.Security.SecurityCritical] // auto-generated + [MethodImplAttribute(MethodImplOptions.InternalCall)] + unsafe internal static extern OverlappedData GetOverlappedFromNative(NativeOverlapped* nativeOverlappedPtr); + + [System.Security.SecurityCritical] // auto-generated + [MethodImplAttribute(MethodImplOptions.InternalCall)] + unsafe internal static extern void CheckVMForIOPacket(out NativeOverlapped* pOVERLAP, out uint errorCode, out uint numBytes); + } + + #endregion class OverlappedData + + + #region class Overlapped + + /// <internalonly/> + [System.Runtime.InteropServices.ComVisible(true)] + public class Overlapped + { + private OverlappedData m_overlappedData; + private static PinnableBufferCache s_overlappedDataCache = new PinnableBufferCache("System.Threading.OverlappedData", ()=> new OverlappedData()); + +#if FEATURE_CORECLR + [System.Security.SecuritySafeCritical] // auto-generated +#endif + public Overlapped() + { + m_overlappedData = (OverlappedData) s_overlappedDataCache.Allocate(); + m_overlappedData.m_overlapped = this; + } + + public Overlapped(int offsetLo, int offsetHi, IntPtr hEvent, IAsyncResult ar) + { + m_overlappedData = (OverlappedData) s_overlappedDataCache.Allocate(); + m_overlappedData.m_overlapped = this; + m_overlappedData.m_nativeOverlapped.OffsetLow = offsetLo; + m_overlappedData.m_nativeOverlapped.OffsetHigh = offsetHi; + m_overlappedData.UserHandle = hEvent; + m_overlappedData.m_asyncResult = ar; + } + + [Obsolete("This constructor is not 64-bit compatible. Use the constructor that takes an IntPtr for the event handle. http://go.microsoft.com/fwlink/?linkid=14202")] + public Overlapped(int offsetLo, int offsetHi, int hEvent, IAsyncResult ar) : this(offsetLo, offsetHi, new IntPtr(hEvent), ar) + { + } + + public IAsyncResult AsyncResult + { + get { return m_overlappedData.m_asyncResult; } + set { m_overlappedData.m_asyncResult = value; } + } + + public int OffsetLow + { + get { return m_overlappedData.m_nativeOverlapped.OffsetLow; } + set { m_overlappedData.m_nativeOverlapped.OffsetLow = value; } + } + + public int OffsetHigh + { + get { return m_overlappedData.m_nativeOverlapped.OffsetHigh; } + set { m_overlappedData.m_nativeOverlapped.OffsetHigh = value; } + } + + [Obsolete("This property is not 64-bit compatible. Use EventHandleIntPtr instead. http://go.microsoft.com/fwlink/?linkid=14202")] + public int EventHandle + { + get { return m_overlappedData.UserHandle.ToInt32(); } + set { m_overlappedData.UserHandle = new IntPtr(value); } + } + + [ComVisible(false)] + public IntPtr EventHandleIntPtr + { + get { return m_overlappedData.UserHandle; } + set { m_overlappedData.UserHandle = value; } + } + + internal _IOCompletionCallback iocbHelper + { + get { return m_overlappedData.m_iocbHelper; } + } + + internal IOCompletionCallback UserCallback + { + [System.Security.SecurityCritical] + get { return m_overlappedData.m_iocb; } + } + + /*==================================================================== + * Packs a managed overlapped class into native Overlapped struct. + * Roots the iocb and stores it in the ReservedCOR field of native Overlapped + * Pins the native Overlapped struct and returns the pinned index. + ====================================================================*/ + [System.Security.SecurityCritical] // auto-generated + [Obsolete("This method is not safe. Use Pack (iocb, userData) instead. http://go.microsoft.com/fwlink/?linkid=14202")] + [CLSCompliant(false)] + unsafe public NativeOverlapped* Pack(IOCompletionCallback iocb) + { + return Pack (iocb, null); + } + + [System.Security.SecurityCritical] // auto-generated + [CLSCompliant(false),ComVisible(false)] + unsafe public NativeOverlapped* Pack(IOCompletionCallback iocb, Object userData) + { + return m_overlappedData.Pack(iocb, userData); + } + + [System.Security.SecurityCritical] // auto-generated_required + [Obsolete("This method is not safe. Use UnsafePack (iocb, userData) instead. http://go.microsoft.com/fwlink/?linkid=14202")] + [CLSCompliant(false)] + unsafe public NativeOverlapped* UnsafePack(IOCompletionCallback iocb) + { + return UnsafePack (iocb, null); + } + + [System.Security.SecurityCritical] // auto-generated_required + [CLSCompliant(false), ComVisible(false)] + unsafe public NativeOverlapped* UnsafePack(IOCompletionCallback iocb, Object userData) + { + return m_overlappedData.UnsafePack(iocb, userData); + } + + /*==================================================================== + * Unpacks an unmanaged native Overlapped struct. + * Unpins the native Overlapped struct + ====================================================================*/ + [System.Security.SecurityCritical] // auto-generated + [CLSCompliant(false)] + unsafe public static Overlapped Unpack(NativeOverlapped* nativeOverlappedPtr) + { + if (nativeOverlappedPtr == null) + throw new ArgumentNullException("nativeOverlappedPtr"); + Contract.EndContractBlock(); + + Overlapped overlapped = OverlappedData.GetOverlappedFromNative(nativeOverlappedPtr).m_overlapped; + + return overlapped; + } + + [System.Security.SecurityCritical] // auto-generated + [CLSCompliant(false)] + unsafe public static void Free(NativeOverlapped* nativeOverlappedPtr) + { + if (nativeOverlappedPtr == null) + throw new ArgumentNullException("nativeOverlappedPtr"); + Contract.EndContractBlock(); + + Overlapped overlapped = OverlappedData.GetOverlappedFromNative(nativeOverlappedPtr).m_overlapped; + OverlappedData.FreeNativeOverlapped(nativeOverlappedPtr); + OverlappedData overlappedData = overlapped.m_overlappedData; + overlapped.m_overlappedData = null; + overlappedData.ReInitialize(); + s_overlappedDataCache.Free(overlappedData); + } + + } + + #endregion class Overlapped + +} // namespace diff --git a/src/mscorlib/src/System/Threading/ParameterizedThreadStart.cs b/src/mscorlib/src/System/Threading/ParameterizedThreadStart.cs new file mode 100644 index 0000000000..45d24fef49 --- /dev/null +++ b/src/mscorlib/src/System/Threading/ParameterizedThreadStart.cs @@ -0,0 +1,24 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +// +/*============================================================================= +** +** +** +** Purpose: This class is a Delegate which defines the start method +** for starting a thread. That method must match this delegate. +** +** +=============================================================================*/ + + +namespace System.Threading { + using System.Security.Permissions; + using System.Threading; + using System.Runtime.InteropServices; + + [ComVisibleAttribute(false)] + public delegate void ParameterizedThreadStart(object obj); +} diff --git a/src/mscorlib/src/System/Threading/ReaderWriterLock.cs b/src/mscorlib/src/System/Threading/ReaderWriterLock.cs new file mode 100644 index 0000000000..8cead1a87a --- /dev/null +++ b/src/mscorlib/src/System/Threading/ReaderWriterLock.cs @@ -0,0 +1,306 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +// +/*============================================================ +** +** +** +** Purpose: Defines the lock that implements +** single-writer/multiple-reader semantics +** +** +===========================================================*/ + +#if FEATURE_RWLOCK +namespace System.Threading { + using System.Threading; + using System.Security.Permissions; + using System.Runtime.Remoting; + using System; + using System.Runtime.CompilerServices; + using System.Runtime.InteropServices; + using System.Runtime.ConstrainedExecution; + using System.Runtime.Versioning; + using System.Diagnostics.Contracts; + + [HostProtection(Synchronization=true, ExternalThreading=true)] + [ComVisible(true)] + public sealed class ReaderWriterLock: CriticalFinalizerObject + { + /* + * Constructor + */ + [System.Security.SecuritySafeCritical] // auto-generated + public ReaderWriterLock() + { + PrivateInitialize(); + } + + /* + * Destructor + */ + [System.Security.SecuritySafeCritical] // auto-generated + ~ReaderWriterLock() + { + PrivateDestruct(); + } + + /* + * Property that returns TRUE if the reader lock is held + * by the current thread + */ + public bool IsReaderLockHeld { + [System.Security.SecuritySafeCritical] // auto-generated + [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] + get { + return(PrivateGetIsReaderLockHeld()); + } + } + + /* + * Property that returns TRUE if the writer lock is held + * by the current thread + */ + public bool IsWriterLockHeld { + [System.Security.SecuritySafeCritical] // auto-generated + [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] + get { + return(PrivateGetIsWriterLockHeld()); + } + } + + /* + * Property that returns the current writer sequence number. + * The caller should be a reader or writer for getting + * meaningful results + */ + public int WriterSeqNum { + [System.Security.SecuritySafeCritical] // auto-generated + get { + return(PrivateGetWriterSeqNum()); + } + } + + /* + * Acquires reader lock. The thread will block if a different + * thread has writer lock. + */ + [System.Security.SecurityCritical] // auto-generated + [MethodImplAttribute(MethodImplOptions.InternalCall)] + private extern void AcquireReaderLockInternal(int millisecondsTimeout); + + [System.Security.SecuritySafeCritical] // auto-generated + public void AcquireReaderLock(int millisecondsTimeout) + { + AcquireReaderLockInternal(millisecondsTimeout); + } + + + [System.Security.SecuritySafeCritical] // auto-generated + public void AcquireReaderLock(TimeSpan timeout) + { + long tm = (long)timeout.TotalMilliseconds; + if (tm < -1 || tm > (long) Int32.MaxValue) + throw new ArgumentOutOfRangeException("timeout", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegOrNegative1")); + AcquireReaderLockInternal((int)tm); + } + + /* + * Acquires writer lock. The thread will block if a different + * thread has reader lock. It will dead lock if this thread + * has reader lock. Use UpgardeToWriterLock when you are not + * sure if the thread has reader lock + */ + [System.Security.SecurityCritical] // auto-generated + [MethodImplAttribute(MethodImplOptions.InternalCall)] + private extern void AcquireWriterLockInternal(int millisecondsTimeout); + + [System.Security.SecuritySafeCritical] // auto-generated + public void AcquireWriterLock(int millisecondsTimeout) + { + AcquireWriterLockInternal(millisecondsTimeout); + } + + [System.Security.SecuritySafeCritical] // auto-generated + public void AcquireWriterLock(TimeSpan timeout) + { + long tm = (long)timeout.TotalMilliseconds; + if (tm < -1 || tm > (long) Int32.MaxValue) + throw new ArgumentOutOfRangeException("timeout", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegOrNegative1")); + AcquireWriterLockInternal((int)tm); + } + + + /* + * Releases reader lock. + */ + [System.Security.SecurityCritical] // auto-generated + [MethodImplAttribute(MethodImplOptions.InternalCall)] + [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] + private extern void ReleaseReaderLockInternal(); + + [System.Security.SecuritySafeCritical] // auto-generated + [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] + public void ReleaseReaderLock() + { + ReleaseReaderLockInternal(); + } + + /* + * Releases writer lock. + */ + [System.Security.SecurityCritical] // auto-generated + [MethodImplAttribute(MethodImplOptions.InternalCall)] + [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] + private extern void ReleaseWriterLockInternal(); + + [System.Security.SecuritySafeCritical] // auto-generated + [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] + public void ReleaseWriterLock() + { + ReleaseWriterLockInternal(); + } + + /* + * Upgardes the thread to a writer. If the thread has is a + * reader, it is possible that the reader lock was + * released before writer lock was acquired. + */ + [System.Security.SecuritySafeCritical] // auto-generated + public LockCookie UpgradeToWriterLock(int millisecondsTimeout) + { + LockCookie result = new LockCookie (); + FCallUpgradeToWriterLock (ref result, millisecondsTimeout); + return result; + } + + [System.Security.SecurityCritical] // auto-generated + [MethodImplAttribute(MethodImplOptions.InternalCall)] + private extern void FCallUpgradeToWriterLock(ref LockCookie result, int millisecondsTimeout); + + public LockCookie UpgradeToWriterLock(TimeSpan timeout) + { + long tm = (long)timeout.TotalMilliseconds; + if (tm < -1 || tm > (long) Int32.MaxValue) + throw new ArgumentOutOfRangeException("timeout", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegOrNegative1")); + return UpgradeToWriterLock((int)tm); + } + + /* + * Restores the lock status of the thread to the one it was + * in when it called UpgradeToWriterLock. + */ + [System.Security.SecurityCritical] // auto-generated + [MethodImplAttribute(MethodImplOptions.InternalCall)] + private extern void DowngradeFromWriterLockInternal(ref LockCookie lockCookie); + + [System.Security.SecuritySafeCritical] // auto-generated + public void DowngradeFromWriterLock(ref LockCookie lockCookie) + { + DowngradeFromWriterLockInternal(ref lockCookie); + } + + /* + * Releases the lock irrespective of the number of times the thread + * acquired the lock + */ + [System.Security.SecuritySafeCritical] // auto-generated + public LockCookie ReleaseLock() + { + LockCookie result = new LockCookie (); + FCallReleaseLock (ref result); + return result; + } + + [System.Security.SecurityCritical] // auto-generated + [MethodImplAttribute(MethodImplOptions.InternalCall)] + private extern void FCallReleaseLock(ref LockCookie result); + + /* + * Restores the lock status of the thread to the one it was + * in when it called ReleaseLock. + */ + [System.Security.SecurityCritical] // auto-generated + [MethodImplAttribute(MethodImplOptions.InternalCall)] + private extern void RestoreLockInternal(ref LockCookie lockCookie); + + [System.Security.SecuritySafeCritical] // auto-generated + public void RestoreLock(ref LockCookie lockCookie) + { + RestoreLockInternal(ref lockCookie); + } + + /* + * Internal helper that returns TRUE if the reader lock is held + * by the current thread + */ + [System.Security.SecurityCritical] // auto-generated + [MethodImplAttribute(MethodImplOptions.InternalCall)] + [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] + private extern bool PrivateGetIsReaderLockHeld(); + + /* + * Internal helper that returns TRUE if the writer lock is held + * by the current thread + */ + [System.Security.SecurityCritical] // auto-generated + [MethodImplAttribute(MethodImplOptions.InternalCall)] + [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] + private extern bool PrivateGetIsWriterLockHeld(); + + /* + * Internal helper that returns the current writer sequence + * number. The caller should be a reader or writer for getting + * meaningful results + */ + [System.Security.SecurityCritical] // auto-generated + [MethodImplAttribute(MethodImplOptions.InternalCall)] + private extern int PrivateGetWriterSeqNum(); + + /* + * Returns true if there were intermediate writes since the + * sequence number was obtained. The caller should be + * a reader or writer for getting meaningful results + */ + [System.Security.SecuritySafeCritical] // auto-generated + [MethodImplAttribute(MethodImplOptions.InternalCall)] + public extern bool AnyWritersSince(int seqNum); + + // Initialize state kept inside the lock + [System.Security.SecurityCritical] // auto-generated + [MethodImplAttribute(MethodImplOptions.InternalCall)] + private extern void PrivateInitialize(); + + // Destruct resource associated with the lock + [System.Security.SecurityCritical] // auto-generated + [MethodImplAttribute(MethodImplOptions.InternalCall)] + private extern void PrivateDestruct(); + + // State +#pragma warning disable 169 +#pragma warning disable 414 // These fields are not used from managed. + private IntPtr _hWriterEvent; + private IntPtr _hReaderEvent; + private IntPtr _hObjectHandle; + private int _dwState = 0; + private int _dwULockID = 0; + private int _dwLLockID = 0; + private int _dwWriterID = 0; + private int _dwWriterSeqNum = 0; + private short _wWriterLevel; +#if RWLOCK_STATISTICS + // WARNING: You must explicitly #define RWLOCK_STATISTICS when you + // build in both the VM and BCL directories if you want this. + private int _dwReaderEntryCount = 0; + private int _dwReaderContentionCount = 0; + private int _dwWriterEntryCount = 0; + private int _dwWriterContentionCount = 0; + private int _dwEventsReleasedCount = 0; +#endif // RWLOCK_STATISTICS +#pragma warning restore 414 +#pragma warning restore 169 + } +} +#endif //FEATURE_RWLOCK diff --git a/src/mscorlib/src/System/Threading/Semaphore.cs b/src/mscorlib/src/System/Threading/Semaphore.cs new file mode 100644 index 0000000000..303593b776 --- /dev/null +++ b/src/mscorlib/src/System/Threading/Semaphore.cs @@ -0,0 +1,201 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Microsoft.Win32; +using Microsoft.Win32.SafeHandles; +using System.Diagnostics.Contracts; +using System.IO; +using System.Runtime.InteropServices; +using System.Security; + +namespace System.Threading +{ + public sealed partial class Semaphore : WaitHandle + { + [SecuritySafeCritical] + public Semaphore(int initialCount, int maximumCount) : this(initialCount, maximumCount, null) { } + + [SecurityCritical] + public Semaphore(int initialCount, int maximumCount, string name) + { + if (initialCount < 0) + { + throw new ArgumentOutOfRangeException("initialCount", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum")); + } + + if (maximumCount < 1) + { + throw new ArgumentOutOfRangeException("maximumCount", Environment.GetResourceString("ArgumentOutOfRange_NeedPosNum")); + } + + if (initialCount > maximumCount) + { + throw new ArgumentException(Environment.GetResourceString("Argument_SemaphoreInitialMaximum")); + } + + SafeWaitHandle myHandle = CreateSemaphone(initialCount, maximumCount, name); + + if (myHandle.IsInvalid) + { + int errorCode = Marshal.GetLastWin32Error(); + + if (null != name && 0 != name.Length && Win32Native.ERROR_INVALID_HANDLE == errorCode) + throw new WaitHandleCannotBeOpenedException( + Environment.GetResourceString("Threading.WaitHandleCannotBeOpenedException_InvalidHandle", name)); + + __Error.WinIOError(); + } + this.SafeWaitHandle = myHandle; + } + + [SecurityCritical] + public Semaphore(int initialCount, int maximumCount, string name, out bool createdNew) + { + if (initialCount < 0) + { + throw new ArgumentOutOfRangeException("initialCount", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum")); + } + + if (maximumCount < 1) + { + throw new ArgumentOutOfRangeException("maximumCount", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum")); + } + + if (initialCount > maximumCount) + { + throw new ArgumentException(Environment.GetResourceString("Argument_SemaphoreInitialMaximum")); + } + + SafeWaitHandle myHandle = CreateSemaphone(initialCount, maximumCount, name); + + int errorCode = Marshal.GetLastWin32Error(); + if (myHandle.IsInvalid) + { + if (null != name && 0 != name.Length && Win32Native.ERROR_INVALID_HANDLE == errorCode) + throw new WaitHandleCannotBeOpenedException( + Environment.GetResourceString("Threading.WaitHandleCannotBeOpenedException_InvalidHandle", name)); + __Error.WinIOError(); + } + createdNew = errorCode != Win32Native.ERROR_ALREADY_EXISTS; + this.SafeWaitHandle = myHandle; + } + + [SecurityCritical] + private Semaphore(SafeWaitHandle handle) + { + this.SafeWaitHandle = handle; + } + + [SecurityCritical] + private static SafeWaitHandle CreateSemaphone(int initialCount, int maximumCount, string name) + { + if (name != null) + { +#if PLATFORM_UNIX + throw new PlatformNotSupportedException(Environment.GetResourceString("PlatformNotSupported_NamedSynchronizationPrimitives")); +#else + if (name.Length > Path.MaxPath) + throw new ArgumentException(Environment.GetResourceString("Argument_WaitHandleNameTooLong", Path.MaxPath), "name"); +#endif + } + + Contract.Assert(initialCount >= 0); + Contract.Assert(maximumCount >= 1); + Contract.Assert(initialCount <= maximumCount); + + return Win32Native.CreateSemaphore(null, initialCount, maximumCount, name); + } + + [SecurityCritical] + + public static Semaphore OpenExisting(string name) + { + Semaphore result; + switch (OpenExistingWorker(name, out result)) + { + case OpenExistingResult.NameNotFound: + throw new WaitHandleCannotBeOpenedException(); + case OpenExistingResult.NameInvalid: + throw new WaitHandleCannotBeOpenedException(Environment.GetResourceString("Threading.WaitHandleCannotBeOpenedException_InvalidHandle", name)); + case OpenExistingResult.PathNotFound: + throw new IOException(Win32Native.GetMessage(Win32Native.ERROR_PATH_NOT_FOUND)); + default: + return result; + } + } + + [SecurityCritical] + public static bool TryOpenExisting(string name, out Semaphore result) + { + return OpenExistingWorker(name, out result) == OpenExistingResult.Success; + } + + [SecurityCritical] + private static OpenExistingResult OpenExistingWorker(string name, out Semaphore result) + { +#if PLATFORM_UNIX + throw new PlatformNotSupportedException(Environment.GetResourceString("PlatformNotSupported_NamedSynchronizationPrimitives")); +#else + if (name == null) + throw new ArgumentNullException("name", Environment.GetResourceString("ArgumentNull_WithParamName")); + if (name.Length == 0) + throw new ArgumentException(Environment.GetResourceString("Argument_EmptyName"), "name"); + if (name.Length > Path.MaxPath) + throw new ArgumentException(Environment.GetResourceString("Argument_WaitHandleNameTooLong", Path.MaxPath), "name"); + + const int SYNCHRONIZE = 0x00100000; + const int SEMAPHORE_MODIFY_STATE = 0x00000002; + + //Pass false to OpenSemaphore to prevent inheritedHandles + SafeWaitHandle myHandle = Win32Native.OpenSemaphore(SEMAPHORE_MODIFY_STATE | SYNCHRONIZE, false, name); + + if (myHandle.IsInvalid) + { + result = null; + + int errorCode = Marshal.GetLastWin32Error(); + + if (Win32Native.ERROR_FILE_NOT_FOUND == errorCode || Win32Native.ERROR_INVALID_NAME == errorCode) + return OpenExistingResult.NameNotFound; + if (Win32Native.ERROR_PATH_NOT_FOUND == errorCode) + return OpenExistingResult.PathNotFound; + if (null != name && 0 != name.Length && Win32Native.ERROR_INVALID_HANDLE == errorCode) + return OpenExistingResult.NameInvalid; + //this is for passed through NativeMethods Errors + __Error.WinIOError(); + } + + result = new Semaphore(myHandle); + return OpenExistingResult.Success; +#endif + } + + public int Release() + { + return Release(1); + } + + // increase the count on a semaphore, returns previous count + [SecuritySafeCritical] + public int Release(int releaseCount) + { + if (releaseCount < 1) + { + throw new ArgumentOutOfRangeException("releaseCount", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum")); + } + + //If ReleaseSempahore returns false when the specified value would cause + // the semaphore's count to exceed the maximum count set when Semaphore was created + //Non-Zero return + + int previousCount; + if (!Win32Native.ReleaseSemaphore(SafeWaitHandle, releaseCount, out previousCount)) + { + throw new SemaphoreFullException(); + } + + return previousCount; + } + } +} diff --git a/src/mscorlib/src/System/Threading/SemaphoreFullException.cs b/src/mscorlib/src/System/Threading/SemaphoreFullException.cs new file mode 100644 index 0000000000..e1928e05de --- /dev/null +++ b/src/mscorlib/src/System/Threading/SemaphoreFullException.cs @@ -0,0 +1,30 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace System.Threading { + using System; + using System.Runtime.Serialization; + using System.Runtime.InteropServices; + + [Serializable] + [ComVisibleAttribute(false)] +#if !FEATURE_CORECLR + [System.Runtime.CompilerServices.TypeForwardedFrom("System, Version=2.0.0.0, Culture=Neutral, PublicKeyToken=b77a5c561934e089")] +#endif + public class SemaphoreFullException : SystemException { + + public SemaphoreFullException() : base(Environment.GetResourceString("Threading_SemaphoreFullException")){ + } + + public SemaphoreFullException(String message) : base(message) { + } + + public SemaphoreFullException(String message, Exception innerException) : base(message, innerException) { + } + + protected SemaphoreFullException(SerializationInfo info, StreamingContext context) : base (info, context) { + } + } +} + diff --git a/src/mscorlib/src/System/Threading/SemaphoreSlim.cs b/src/mscorlib/src/System/Threading/SemaphoreSlim.cs new file mode 100644 index 0000000000..c2dcbb3451 --- /dev/null +++ b/src/mscorlib/src/System/Threading/SemaphoreSlim.cs @@ -0,0 +1,930 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +// =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ +// +// +// +// A lightweight semahore class that contains the basic semaphore functions plus some useful functions like interrupt +// and wait handle exposing to allow waiting on multiple semaphores. +// +// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Security; +using System.Security.Permissions; +using System.Runtime.InteropServices; +using System.Diagnostics.Contracts; +using System.Threading.Tasks; + +// The class will be part of the current System.Threading namespace +namespace System.Threading +{ + /// <summary> + /// Limits the number of threads that can access a resource or pool of resources concurrently. + /// </summary> + /// <remarks> + /// <para> + /// The <see cref="SemaphoreSlim"/> provides a lightweight semaphore class that doesn't + /// use Windows kernel semaphores. + /// </para> + /// <para> + /// All public and protected members of <see cref="SemaphoreSlim"/> are thread-safe and may be used + /// concurrently from multiple threads, with the exception of Dispose, which + /// must only be used when all other operations on the <see cref="SemaphoreSlim"/> have + /// completed. + /// </para> + /// </remarks> + [ComVisible(false)] + [HostProtection(Synchronization = true, ExternalThreading = true)] + [DebuggerDisplay("Current Count = {m_currentCount}")] + public class SemaphoreSlim : IDisposable + { + #region Private Fields + + // The semaphore count, initialized in the constructor to the initial value, every release call incremetns it + // and every wait call decrements it as long as its value is positive otherwise the wait will block. + // Its value must be between the maximum semaphore value and zero + private volatile int m_currentCount; + + // The maximum semaphore value, it is initialized to Int.MaxValue if the client didn't specify it. it is used + // to check if the count excceeded the maxi value or not. + private readonly int m_maxCount; + + // The number of synchronously waiting threads, it is set to zero in the constructor and increments before blocking the + // threading and decrements it back after that. It is used as flag for the release call to know if there are + // waiting threads in the monitor or not. + private volatile int m_waitCount; + + // Dummy object used to in lock statements to protect the semaphore count, wait handle and cancelation + private object m_lockObj; + + // Act as the semaphore wait handle, it's lazily initialized if needed, the first WaitHandle call initialize it + // and wait an release sets and resets it respectively as long as it is not null + private volatile ManualResetEvent m_waitHandle; + + // Head of list representing asynchronous waits on the semaphore. + private TaskNode m_asyncHead; + + // Tail of list representing asynchronous waits on the semaphore. + private TaskNode m_asyncTail; + + // A pre-completed task with Result==true + private readonly static Task<bool> s_trueTask = + new Task<bool>(false, true, (TaskCreationOptions)InternalTaskOptions.DoNotDispose, default(CancellationToken)); + // A pre-completed task with Result==false + private readonly static Task<bool> s_falseTask = + new Task<bool>(false, false, (TaskCreationOptions)InternalTaskOptions.DoNotDispose, default(CancellationToken)); + + // No maximum constant + private const int NO_MAXIMUM = Int32.MaxValue; + + // Task in a linked list of asynchronous waiters + private sealed class TaskNode : Task<bool>, IThreadPoolWorkItem + { + internal TaskNode Prev, Next; + internal TaskNode() : base() {} + + [SecurityCritical] + void IThreadPoolWorkItem.ExecuteWorkItem() + { + bool setSuccessfully = TrySetResult(true); + Contract.Assert(setSuccessfully, "Should have been able to complete task"); + } + + [SecurityCritical] + void IThreadPoolWorkItem.MarkAborted(ThreadAbortException tae) { /* nop */ } + } + #endregion + + #region Public properties + + /// <summary> + /// Gets the current count of the <see cref="SemaphoreSlim"/>. + /// </summary> + /// <value>The current count of the <see cref="SemaphoreSlim"/>.</value> + public int CurrentCount + { + get { return m_currentCount; } + } + + /// <summary> + /// Returns a <see cref="T:System.Threading.WaitHandle"/> that can be used to wait on the semaphore. + /// </summary> + /// <value>A <see cref="T:System.Threading.WaitHandle"/> that can be used to wait on the + /// semaphore.</value> + /// <remarks> + /// A successful wait on the <see cref="AvailableWaitHandle"/> does not imply a successful wait on + /// the <see cref="SemaphoreSlim"/> itself, nor does it decrement the semaphore's + /// count. <see cref="AvailableWaitHandle"/> exists to allow a thread to block waiting on multiple + /// semaphores, but such a wait should be followed by a true wait on the target semaphore. + /// </remarks> + /// <exception cref="T:System.ObjectDisposedException">The <see + /// cref="SemaphoreSlim"/> has been disposed.</exception> + public WaitHandle AvailableWaitHandle + { + get + { + CheckDispose(); + + // Return it directly if it is not null + if (m_waitHandle != null) + return m_waitHandle; + + //lock the count to avoid multiple threads initializing the handle if it is null + lock (m_lockObj) + { + if (m_waitHandle == null) + { + // The initial state for the wait handle is true if the count is greater than zero + // false otherwise + m_waitHandle = new ManualResetEvent(m_currentCount != 0); + } + } + return m_waitHandle; + } + } + + #endregion + + #region Constructors + /// <summary> + /// Initializes a new instance of the <see cref="SemaphoreSlim"/> class, specifying + /// the initial number of requests that can be granted concurrently. + /// </summary> + /// <param name="initialCount">The initial number of requests for the semaphore that can be granted + /// concurrently.</param> + /// <exception cref="T:System.ArgumentOutOfRangeException"><paramref name="initialCount"/> + /// is less than 0.</exception> + public SemaphoreSlim(int initialCount) + : this(initialCount, NO_MAXIMUM) + { + } + + /// <summary> + /// Initializes a new instance of the <see cref="SemaphoreSlim"/> class, specifying + /// the initial and maximum number of requests that can be granted concurrently. + /// </summary> + /// <param name="initialCount">The initial number of requests for the semaphore that can be granted + /// concurrently.</param> + /// <param name="maxCount">The maximum number of requests for the semaphore that can be granted + /// concurrently.</param> + /// <exception cref="T:System.ArgumentOutOfRangeException"> <paramref name="initialCount"/> + /// is less than 0. -or- + /// <paramref name="initialCount"/> is greater than <paramref name="maxCount"/>. -or- + /// <paramref name="maxCount"/> is less than 0.</exception> + public SemaphoreSlim(int initialCount, int maxCount) + { + if (initialCount < 0 || initialCount > maxCount) + { + throw new ArgumentOutOfRangeException( + "initialCount", initialCount, GetResourceString("SemaphoreSlim_ctor_InitialCountWrong")); + } + + //validate input + if (maxCount <= 0) + { + throw new ArgumentOutOfRangeException("maxCount", maxCount, GetResourceString("SemaphoreSlim_ctor_MaxCountWrong")); + } + + m_maxCount = maxCount; + m_lockObj = new object(); + m_currentCount = initialCount; + } + + #endregion + + #region Methods + /// <summary> + /// Blocks the current thread until it can enter the <see cref="SemaphoreSlim"/>. + /// </summary> + /// <exception cref="T:System.ObjectDisposedException">The current instance has already been + /// disposed.</exception> + public void Wait() + { + // Call wait with infinite timeout + Wait(Timeout.Infinite, new CancellationToken()); + } + + /// <summary> + /// Blocks the current thread until it can enter the <see cref="SemaphoreSlim"/>, while observing a + /// <see cref="T:System.Threading.CancellationToken"/>. + /// </summary> + /// <param name="cancellationToken">The <see cref="T:System.Threading.CancellationToken"/> token to + /// observe.</param> + /// <exception cref="T:System.OperationCanceledException"><paramref name="cancellationToken"/> was + /// canceled.</exception> + /// <exception cref="T:System.ObjectDisposedException">The current instance has already been + /// disposed.</exception> + public void Wait(CancellationToken cancellationToken) + { + // Call wait with infinite timeout + Wait(Timeout.Infinite, cancellationToken); + } + + /// <summary> + /// Blocks the current thread until it can enter the <see cref="SemaphoreSlim"/>, using a <see + /// cref="T:System.TimeSpan"/> to measure the time interval. + /// </summary> + /// <param name="timeout">A <see cref="System.TimeSpan"/> that represents the number of milliseconds + /// to wait, or a <see cref="System.TimeSpan"/> that represents -1 milliseconds to wait indefinitely. + /// </param> + /// <returns>true if the current thread successfully entered the <see cref="SemaphoreSlim"/>; + /// otherwise, false.</returns> + /// <exception cref="T:System.ArgumentOutOfRangeException"><paramref name="timeout"/> is a negative + /// number other than -1 milliseconds, which represents an infinite time-out -or- timeout is greater + /// than <see cref="System.Int32.MaxValue"/>.</exception> + public bool Wait(TimeSpan timeout) + { + // Validate the timeout + Int64 totalMilliseconds = (Int64)timeout.TotalMilliseconds; + if (totalMilliseconds < -1 || totalMilliseconds > Int32.MaxValue) + { + throw new System.ArgumentOutOfRangeException( + "timeout", timeout, GetResourceString("SemaphoreSlim_Wait_TimeoutWrong")); + } + + // Call wait with the timeout milliseconds + return Wait((int)timeout.TotalMilliseconds, new CancellationToken()); + } + + /// <summary> + /// Blocks the current thread until it can enter the <see cref="SemaphoreSlim"/>, using a <see + /// cref="T:System.TimeSpan"/> to measure the time interval, while observing a <see + /// cref="T:System.Threading.CancellationToken"/>. + /// </summary> + /// <param name="timeout">A <see cref="System.TimeSpan"/> that represents the number of milliseconds + /// to wait, or a <see cref="System.TimeSpan"/> that represents -1 milliseconds to wait indefinitely. + /// </param> + /// <param name="cancellationToken">The <see cref="T:System.Threading.CancellationToken"/> to + /// observe.</param> + /// <returns>true if the current thread successfully entered the <see cref="SemaphoreSlim"/>; + /// otherwise, false.</returns> + /// <exception cref="T:System.ArgumentOutOfRangeException"><paramref name="timeout"/> is a negative + /// number other than -1 milliseconds, which represents an infinite time-out -or- timeout is greater + /// than <see cref="System.Int32.MaxValue"/>.</exception> + /// <exception cref="System.OperationCanceledException"><paramref name="cancellationToken"/> was canceled.</exception> + public bool Wait(TimeSpan timeout, CancellationToken cancellationToken) + { + // Validate the timeout + Int64 totalMilliseconds = (Int64)timeout.TotalMilliseconds; + if (totalMilliseconds < -1 || totalMilliseconds > Int32.MaxValue) + { + throw new System.ArgumentOutOfRangeException( + "timeout", timeout, GetResourceString("SemaphoreSlim_Wait_TimeoutWrong")); + } + + // Call wait with the timeout milliseconds + return Wait((int)timeout.TotalMilliseconds, cancellationToken); + } + + /// <summary> + /// Blocks the current thread until it can enter the <see cref="SemaphoreSlim"/>, using a 32-bit + /// signed integer to measure the time interval. + /// </summary> + /// <param name="millisecondsTimeout">The number of milliseconds to wait, or <see + /// cref="Timeout.Infinite"/>(-1) to wait indefinitely.</param> + /// <returns>true if the current thread successfully entered the <see cref="SemaphoreSlim"/>; + /// otherwise, false.</returns> + /// <exception cref="ArgumentOutOfRangeException"><paramref name="millisecondsTimeout"/> is a + /// negative number other than -1, which represents an infinite time-out.</exception> + public bool Wait(int millisecondsTimeout) + { + return Wait(millisecondsTimeout, new CancellationToken()); + } + + + /// <summary> + /// Blocks the current thread until it can enter the <see cref="SemaphoreSlim"/>, + /// using a 32-bit signed integer to measure the time interval, + /// while observing a <see cref="T:System.Threading.CancellationToken"/>. + /// </summary> + /// <param name="millisecondsTimeout">The number of milliseconds to wait, or <see cref="Timeout.Infinite"/>(-1) to + /// wait indefinitely.</param> + /// <param name="cancellationToken">The <see cref="T:System.Threading.CancellationToken"/> to observe.</param> + /// <returns>true if the current thread successfully entered the <see cref="SemaphoreSlim"/>; otherwise, false.</returns> + /// <exception cref="ArgumentOutOfRangeException"><paramref name="millisecondsTimeout"/> is a negative number other than -1, + /// which represents an infinite time-out.</exception> + /// <exception cref="System.OperationCanceledException"><paramref name="cancellationToken"/> was canceled.</exception> + public bool Wait(int millisecondsTimeout, CancellationToken cancellationToken) + { + CheckDispose(); + + // Validate input + if (millisecondsTimeout < -1) + { + throw new ArgumentOutOfRangeException( + "totalMilliSeconds", millisecondsTimeout, GetResourceString("SemaphoreSlim_Wait_TimeoutWrong")); + } + + cancellationToken.ThrowIfCancellationRequested(); + + // Perf: Check the stack timeout parameter before checking the volatile count + if (millisecondsTimeout == 0 && m_currentCount == 0) + { + // Pessimistic fail fast, check volatile count outside lock (only when timeout is zero!) + return false; + } + + uint startTime = 0; + if (millisecondsTimeout != Timeout.Infinite && millisecondsTimeout > 0) + { + startTime = TimeoutHelper.GetTime(); + } + + bool waitSuccessful = false; + Task<bool> asyncWaitTask = null; + bool lockTaken = false; + + //Register for cancellation outside of the main lock. + //NOTE: Register/deregister inside the lock can deadlock as different lock acquisition orders could + // occur for (1)this.m_lockObj and (2)cts.internalLock + CancellationTokenRegistration cancellationTokenRegistration = cancellationToken.InternalRegisterWithoutEC(s_cancellationTokenCanceledEventHandler, this); + try + { + // Perf: first spin wait for the count to be positive, but only up to the first planned yield. + // This additional amount of spinwaiting in addition + // to Monitor.Enter()’s spinwaiting has shown measurable perf gains in test scenarios. + // + SpinWait spin = new SpinWait(); + while (m_currentCount == 0 && !spin.NextSpinWillYield) + { + spin.SpinOnce(); + } + // entering the lock and incrementing waiters must not suffer a thread-abort, else we cannot + // clean up m_waitCount correctly, which may lead to deadlock due to non-woken waiters. + try { } + finally + { + Monitor.Enter(m_lockObj, ref lockTaken); + if (lockTaken) + { + m_waitCount++; + } + } + + // If there are any async waiters, for fairness we'll get in line behind + // then by translating our synchronous wait into an asynchronous one that we + // then block on (once we've released the lock). + if (m_asyncHead != null) + { + Contract.Assert(m_asyncTail != null, "tail should not be null if head isn't"); + asyncWaitTask = WaitAsync(millisecondsTimeout, cancellationToken); + } + // There are no async waiters, so we can proceed with normal synchronous waiting. + else + { + // If the count > 0 we are good to move on. + // If not, then wait if we were given allowed some wait duration + + OperationCanceledException oce = null; + + if (m_currentCount == 0) + { + if (millisecondsTimeout == 0) + { + return false; + } + + // Prepare for the main wait... + // wait until the count become greater than zero or the timeout is expired + try + { + waitSuccessful = WaitUntilCountOrTimeout(millisecondsTimeout, startTime, cancellationToken); + } + catch (OperationCanceledException e) { oce = e; } + } + + // Now try to acquire. We prioritize acquisition over cancellation/timeout so that we don't + // lose any counts when there are asynchronous waiters in the mix. Asynchronous waiters + // defer to synchronous waiters in priority, which means that if it's possible an asynchronous + // waiter didn't get released because a synchronous waiter was present, we need to ensure + // that synchronous waiter succeeds so that they have a chance to release. + Contract.Assert(!waitSuccessful || m_currentCount > 0, + "If the wait was successful, there should be count available."); + if (m_currentCount > 0) + { + waitSuccessful = true; + m_currentCount--; + } + else if (oce != null) + { + throw oce; + } + + // Exposing wait handle which is lazily initialized if needed + if (m_waitHandle != null && m_currentCount == 0) + { + m_waitHandle.Reset(); + } + } + } + finally + { + // Release the lock + if (lockTaken) + { + m_waitCount--; + Monitor.Exit(m_lockObj); + } + + // Unregister the cancellation callback. + cancellationTokenRegistration.Dispose(); + } + + // If we had to fall back to asynchronous waiting, block on it + // here now that we've released the lock, and return its + // result when available. Otherwise, this was a synchronous + // wait, and whether we successfully acquired the semaphore is + // stored in waitSuccessful. + + return (asyncWaitTask != null) ? asyncWaitTask.GetAwaiter().GetResult() : waitSuccessful; + } + + /// <summary> + /// Local helper function, waits on the monitor until the monitor recieves signal or the + /// timeout is expired + /// </summary> + /// <param name="millisecondsTimeout">The maximum timeout</param> + /// <param name="startTime">The start ticks to calculate the elapsed time</param> + /// <param name="cancellationToken">The CancellationToken to observe.</param> + /// <returns>true if the monitor recieved a signal, false if the timeout expired</returns> + private bool WaitUntilCountOrTimeout(int millisecondsTimeout, uint startTime, CancellationToken cancellationToken) + { + int remainingWaitMilliseconds = Timeout.Infinite; + + //Wait on the monitor as long as the count is zero + while (m_currentCount == 0) + { + // If cancelled, we throw. Trying to wait could lead to deadlock. + cancellationToken.ThrowIfCancellationRequested(); + + if (millisecondsTimeout != Timeout.Infinite) + { + remainingWaitMilliseconds = TimeoutHelper.UpdateTimeOut(startTime, millisecondsTimeout); + if (remainingWaitMilliseconds <= 0) + { + // The thread has expires its timeout + return false; + } + } + // ** the actual wait ** + if (!Monitor.Wait(m_lockObj, remainingWaitMilliseconds)) + { + return false; + } + } + + return true; + } + + /// <summary> + /// Asynchronously waits to enter the <see cref="SemaphoreSlim"/>. + /// </summary> + /// <returns>A task that will complete when the semaphore has been entered.</returns> + public Task WaitAsync() + { + return WaitAsync(Timeout.Infinite, default(CancellationToken)); + } + + /// <summary> + /// Asynchronously waits to enter the <see cref="SemaphoreSlim"/>, while observing a + /// <see cref="T:System.Threading.CancellationToken"/>. + /// </summary> + /// <returns>A task that will complete when the semaphore has been entered.</returns> + /// <param name="cancellationToken"> + /// The <see cref="T:System.Threading.CancellationToken"/> token to observe. + /// </param> + /// <exception cref="T:System.ObjectDisposedException"> + /// The current instance has already been disposed. + /// </exception> + public Task WaitAsync(CancellationToken cancellationToken) + { + return WaitAsync(Timeout.Infinite, cancellationToken); + } + + /// <summary> + /// Asynchronously waits to enter the <see cref="SemaphoreSlim"/>, + /// using a 32-bit signed integer to measure the time interval. + /// </summary> + /// <param name="millisecondsTimeout"> + /// The number of milliseconds to wait, or <see cref="Timeout.Infinite"/>(-1) to wait indefinitely. + /// </param> + /// <returns> + /// A task that will complete with a result of true if the current thread successfully entered + /// the <see cref="SemaphoreSlim"/>, otherwise with a result of false. + /// </returns> + /// <exception cref="T:System.ObjectDisposedException">The current instance has already been + /// disposed.</exception> + /// <exception cref="ArgumentOutOfRangeException"><paramref name="millisecondsTimeout"/> is a negative number other than -1, + /// which represents an infinite time-out. + /// </exception> + public Task<bool> WaitAsync(int millisecondsTimeout) + { + return WaitAsync(millisecondsTimeout, default(CancellationToken)); + } + + /// <summary> + /// Asynchronously waits to enter the <see cref="SemaphoreSlim"/>, using a <see + /// cref="T:System.TimeSpan"/> to measure the time interval, while observing a + /// <see cref="T:System.Threading.CancellationToken"/>. + /// </summary> + /// <param name="timeout"> + /// A <see cref="System.TimeSpan"/> that represents the number of milliseconds + /// to wait, or a <see cref="System.TimeSpan"/> that represents -1 milliseconds to wait indefinitely. + /// </param> + /// <param name="cancellationToken"> + /// The <see cref="T:System.Threading.CancellationToken"/> token to observe. + /// </param> + /// <returns> + /// A task that will complete with a result of true if the current thread successfully entered + /// the <see cref="SemaphoreSlim"/>, otherwise with a result of false. + /// </returns> + /// <exception cref="T:System.ObjectDisposedException"> + /// The current instance has already been disposed. + /// </exception> + /// <exception cref="T:System.ArgumentOutOfRangeException"> + /// <paramref name="timeout"/> is a negative number other than -1 milliseconds, which represents + /// an infinite time-out -or- timeout is greater than <see cref="System.Int32.MaxValue"/>. + /// </exception> + public Task<bool> WaitAsync(TimeSpan timeout) + { + return WaitAsync(timeout, default(CancellationToken)); + } + + /// <summary> + /// Asynchronously waits to enter the <see cref="SemaphoreSlim"/>, using a <see + /// cref="T:System.TimeSpan"/> to measure the time interval. + /// </summary> + /// <param name="timeout"> + /// A <see cref="System.TimeSpan"/> that represents the number of milliseconds + /// to wait, or a <see cref="System.TimeSpan"/> that represents -1 milliseconds to wait indefinitely. + /// </param> + /// <returns> + /// A task that will complete with a result of true if the current thread successfully entered + /// the <see cref="SemaphoreSlim"/>, otherwise with a result of false. + /// </returns> + /// <exception cref="T:System.ArgumentOutOfRangeException"> + /// <paramref name="timeout"/> is a negative number other than -1 milliseconds, which represents + /// an infinite time-out -or- timeout is greater than <see cref="System.Int32.MaxValue"/>. + /// </exception> + public Task<bool> WaitAsync(TimeSpan timeout, CancellationToken cancellationToken) + { + // Validate the timeout + Int64 totalMilliseconds = (Int64)timeout.TotalMilliseconds; + if (totalMilliseconds < -1 || totalMilliseconds > Int32.MaxValue) + { + throw new System.ArgumentOutOfRangeException( + "timeout", timeout, GetResourceString("SemaphoreSlim_Wait_TimeoutWrong")); + } + + // Call wait with the timeout milliseconds + return WaitAsync((int)timeout.TotalMilliseconds, cancellationToken); + } + + /// <summary> + /// Asynchronously waits to enter the <see cref="SemaphoreSlim"/>, + /// using a 32-bit signed integer to measure the time interval, + /// while observing a <see cref="T:System.Threading.CancellationToken"/>. + /// </summary> + /// <param name="millisecondsTimeout"> + /// The number of milliseconds to wait, or <see cref="Timeout.Infinite"/>(-1) to wait indefinitely. + /// </param> + /// <param name="cancellationToken">The <see cref="T:System.Threading.CancellationToken"/> to observe.</param> + /// <returns> + /// A task that will complete with a result of true if the current thread successfully entered + /// the <see cref="SemaphoreSlim"/>, otherwise with a result of false. + /// </returns> + /// <exception cref="T:System.ObjectDisposedException">The current instance has already been + /// disposed.</exception> + /// <exception cref="ArgumentOutOfRangeException"><paramref name="millisecondsTimeout"/> is a negative number other than -1, + /// which represents an infinite time-out. + /// </exception> + public Task<bool> WaitAsync(int millisecondsTimeout, CancellationToken cancellationToken) + { + CheckDispose(); + + // Validate input + if (millisecondsTimeout < -1) + { + throw new ArgumentOutOfRangeException( + "totalMilliSeconds", millisecondsTimeout, GetResourceString("SemaphoreSlim_Wait_TimeoutWrong")); + } + + // Bail early for cancellation + if (cancellationToken.IsCancellationRequested) + return Task.FromCanceled<bool>(cancellationToken); + + lock (m_lockObj) + { + // If there are counts available, allow this waiter to succeed. + if (m_currentCount > 0) + { + --m_currentCount; + if (m_waitHandle != null && m_currentCount == 0) m_waitHandle.Reset(); + return s_trueTask; + } + else if (millisecondsTimeout == 0) + { + // No counts, if timeout is zero fail fast + return s_falseTask; + } + // If there aren't, create and return a task to the caller. + // The task will be completed either when they've successfully acquired + // the semaphore or when the timeout expired or cancellation was requested. + else + { + Contract.Assert(m_currentCount == 0, "m_currentCount should never be negative"); + var asyncWaiter = CreateAndAddAsyncWaiter(); + return (millisecondsTimeout == Timeout.Infinite && !cancellationToken.CanBeCanceled) ? + asyncWaiter : + WaitUntilCountOrTimeoutAsync(asyncWaiter, millisecondsTimeout, cancellationToken); + } + } + } + + /// <summary>Creates a new task and stores it into the async waiters list.</summary> + /// <returns>The created task.</returns> + private TaskNode CreateAndAddAsyncWaiter() + { + Contract.Assert(Monitor.IsEntered(m_lockObj), "Requires the lock be held"); + + // Create the task + var task = new TaskNode(); + + // Add it to the linked list + if (m_asyncHead == null) + { + Contract.Assert(m_asyncTail == null, "If head is null, so too should be tail"); + m_asyncHead = task; + m_asyncTail = task; + } + else + { + Contract.Assert(m_asyncTail != null, "If head is not null, neither should be tail"); + m_asyncTail.Next = task; + task.Prev = m_asyncTail; + m_asyncTail = task; + } + + // Hand it back + return task; + } + + /// <summary>Removes the waiter task from the linked list.</summary> + /// <param name="task">The task to remove.</param> + /// <returns>true if the waiter was in the list; otherwise, false.</returns> + private bool RemoveAsyncWaiter(TaskNode task) + { + Contract.Requires(task != null, "Expected non-null task"); + Contract.Assert(Monitor.IsEntered(m_lockObj), "Requires the lock be held"); + + // Is the task in the list? To be in the list, either it's the head or it has a predecessor that's in the list. + bool wasInList = m_asyncHead == task || task.Prev != null; + + // Remove it from the linked list + if (task.Next != null) task.Next.Prev = task.Prev; + if (task.Prev != null) task.Prev.Next = task.Next; + if (m_asyncHead == task) m_asyncHead = task.Next; + if (m_asyncTail == task) m_asyncTail = task.Prev; + Contract.Assert((m_asyncHead == null) == (m_asyncTail == null), "Head is null iff tail is null"); + + // Make sure not to leak + task.Next = task.Prev = null; + + // Return whether the task was in the list + return wasInList; + } + + /// <summary>Performs the asynchronous wait.</summary> + /// <param name="millisecondsTimeout">The timeout.</param> + /// <param name="cancellationToken">The cancellation token.</param> + /// <returns>The task to return to the caller.</returns> + private async Task<bool> WaitUntilCountOrTimeoutAsync(TaskNode asyncWaiter, int millisecondsTimeout, CancellationToken cancellationToken) + { + Contract.Assert(asyncWaiter != null, "Waiter should have been constructed"); + Contract.Assert(Monitor.IsEntered(m_lockObj), "Requires the lock be held"); + + // Wait until either the task is completed, timeout occurs, or cancellation is requested. + // We need to ensure that the Task.Delay task is appropriately cleaned up if the await + // completes due to the asyncWaiter completing, so we use our own token that we can explicitly + // cancel, and we chain the caller's supplied token into it. + using (var cts = cancellationToken.CanBeCanceled ? + CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, default(CancellationToken)) : + new CancellationTokenSource()) + { + var waitCompleted = Task.WhenAny(asyncWaiter, Task.Delay(millisecondsTimeout, cts.Token)); + if (asyncWaiter == await waitCompleted.ConfigureAwait(false)) + { + cts.Cancel(); // ensure that the Task.Delay task is cleaned up + return true; // successfully acquired + } + } + + // If we get here, the wait has timed out or been canceled. + + // If the await completed synchronously, we still hold the lock. If it didn't, + // we no longer hold the lock. As such, acquire it. + lock (m_lockObj) + { + // Remove the task from the list. If we're successful in doing so, + // we know that no one else has tried to complete this waiter yet, + // so we can safely cancel or timeout. + if (RemoveAsyncWaiter(asyncWaiter)) + { + cancellationToken.ThrowIfCancellationRequested(); // cancellation occurred + return false; // timeout occurred + } + } + + // The waiter had already been removed, which means it's already completed or is about to + // complete, so let it, and don't return until it does. + return await asyncWaiter.ConfigureAwait(false); + } + + /// <summary> + /// Exits the <see cref="SemaphoreSlim"/> once. + /// </summary> + /// <returns>The previous count of the <see cref="SemaphoreSlim"/>.</returns> + /// <exception cref="T:System.ObjectDisposedException">The current instance has already been + /// disposed.</exception> + public int Release() + { + return Release(1); + } + + /// <summary> + /// Exits the <see cref="SemaphoreSlim"/> a specified number of times. + /// </summary> + /// <param name="releaseCount">The number of times to exit the semaphore.</param> + /// <returns>The previous count of the <see cref="SemaphoreSlim"/>.</returns> + /// <exception cref="T:System.ArgumentOutOfRangeException"><paramref name="releaseCount"/> is less + /// than 1.</exception> + /// <exception cref="T:System.Threading.SemaphoreFullException">The <see cref="SemaphoreSlim"/> has + /// already reached its maximum size.</exception> + /// <exception cref="T:System.ObjectDisposedException">The current instance has already been + /// disposed.</exception> + public int Release(int releaseCount) + { + CheckDispose(); + + // Validate input + if (releaseCount < 1) + { + throw new ArgumentOutOfRangeException( + "releaseCount", releaseCount, GetResourceString("SemaphoreSlim_Release_CountWrong")); + } + int returnCount; + + lock (m_lockObj) + { + // Read the m_currentCount into a local variable to avoid unnecessary volatile accesses inside the lock. + int currentCount = m_currentCount; + returnCount = currentCount; + + // If the release count would result exceeding the maximum count, throw SemaphoreFullException. + if (m_maxCount - currentCount < releaseCount) + { + throw new SemaphoreFullException(); + } + + // Increment the count by the actual release count + currentCount += releaseCount; + + // Signal to any synchronous waiters + int waitCount = m_waitCount; + if (currentCount == 1 || waitCount == 1) + { + Monitor.Pulse(m_lockObj); + } + else if (waitCount > 1) + { + Monitor.PulseAll(m_lockObj); + } + + // Now signal to any asynchronous waiters, if there are any. While we've already + // signaled the synchronous waiters, we still hold the lock, and thus + // they won't have had an opportunity to acquire this yet. So, when releasing + // asynchronous waiters, we assume that all synchronous waiters will eventually + // acquire the semaphore. That could be a faulty assumption if those synchronous + // waits are canceled, but the wait code path will handle that. + if (m_asyncHead != null) + { + Contract.Assert(m_asyncTail != null, "tail should not be null if head isn't null"); + int maxAsyncToRelease = currentCount - waitCount; + while (maxAsyncToRelease > 0 && m_asyncHead != null) + { + --currentCount; + --maxAsyncToRelease; + + // Get the next async waiter to release and queue it to be completed + var waiterTask = m_asyncHead; + RemoveAsyncWaiter(waiterTask); // ensures waiterTask.Next/Prev are null + QueueWaiterTask(waiterTask); + } + } + m_currentCount = currentCount; + + // Exposing wait handle if it is not null + if (m_waitHandle != null && returnCount == 0 && currentCount > 0) + { + m_waitHandle.Set(); + } + } + + // And return the count + return returnCount; + } + + /// <summary> + /// Queues a waiter task to the ThreadPool. We use this small helper method so that + /// the larger Release(count) method does not need to be SecuritySafeCritical. + /// </summary> + [SecuritySafeCritical] // for ThreadPool.UnsafeQueueCustomWorkItem + private static void QueueWaiterTask(TaskNode waiterTask) + { + ThreadPool.UnsafeQueueCustomWorkItem(waiterTask, forceGlobal: false); + } + + /// <summary> + /// Releases all resources used by the current instance of <see + /// cref="SemaphoreSlim"/>. + /// </summary> + /// <remarks> + /// Unlike most of the members of <see cref="SemaphoreSlim"/>, <see cref="Dispose()"/> is not + /// thread-safe and may not be used concurrently with other members of this instance. + /// </remarks> + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + /// <summary> + /// When overridden in a derived class, releases the unmanaged resources used by the + /// <see cref="T:System.Threading.ManualResetEventSlim"/>, and optionally releases the managed resources. + /// </summary> + /// <param name="disposing">true to release both managed and unmanaged resources; + /// false to release only unmanaged resources.</param> + /// <remarks> + /// Unlike most of the members of <see cref="SemaphoreSlim"/>, <see cref="Dispose(Boolean)"/> is not + /// thread-safe and may not be used concurrently with other members of this instance. + /// </remarks> + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + if (m_waitHandle != null) + { + m_waitHandle.Close(); + m_waitHandle = null; + } + m_lockObj = null; + m_asyncHead = null; + m_asyncTail = null; + } + } + + + + /// <summary> + /// Private helper method to wake up waiters when a cancellationToken gets canceled. + /// </summary> + private static Action<object> s_cancellationTokenCanceledEventHandler = new Action<object>(CancellationTokenCanceledEventHandler); + private static void CancellationTokenCanceledEventHandler(object obj) + { + SemaphoreSlim semaphore = obj as SemaphoreSlim; + Contract.Assert(semaphore != null, "Expected a SemaphoreSlim"); + lock (semaphore.m_lockObj) + { + Monitor.PulseAll(semaphore.m_lockObj); //wake up all waiters. + } + } + + /// <summary> + /// Checks the dispose status by checking the lock object, if it is null means that object + /// has been disposed and throw ObjectDisposedException + /// </summary> + private void CheckDispose() + { + if (m_lockObj == null) + { + throw new ObjectDisposedException(null, GetResourceString("SemaphoreSlim_Disposed")); + } + } + + /// <summary> + /// local helper function to retrieve the exception string message from the resource file + /// </summary> + /// <param name="str">The key string</param> + private static string GetResourceString(string str) + { + return Environment.GetResourceString(str); + } + #endregion + } +} diff --git a/src/mscorlib/src/System/Threading/SendOrPostCallback.cs b/src/mscorlib/src/System/Threading/SendOrPostCallback.cs new file mode 100644 index 0000000000..b81d2bff64 --- /dev/null +++ b/src/mscorlib/src/System/Threading/SendOrPostCallback.cs @@ -0,0 +1,16 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. +/*============================================================ +** +** +** +** Purpose: Represents a method to be called when a message is to be dispatched to a synchronization context. +** +** +===========================================================*/ + +namespace System.Threading +{ + public delegate void SendOrPostCallback(Object state); +} diff --git a/src/mscorlib/src/System/Threading/SpinLock.cs b/src/mscorlib/src/System/Threading/SpinLock.cs new file mode 100644 index 0000000000..dea87435a7 --- /dev/null +++ b/src/mscorlib/src/System/Threading/SpinLock.cs @@ -0,0 +1,750 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. +#pragma warning disable 0420 + +// =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ +// +// A spin lock is a mutual exclusion lock primitive where a thread trying to acquire the lock waits in a loop ("spins") +// repeatedly checking until the lock becomes available. As the thread remains active performing a non-useful task, +// the use of such a lock is a kind of busy waiting and consumes CPU resources without performing real work. +// +// +// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- +using System; +using System.Diagnostics; +using System.Security.Permissions; +using System.Runtime.InteropServices; +using System.Runtime.CompilerServices; +using System.Runtime.ConstrainedExecution; +using System.Diagnostics.Contracts; + +namespace System.Threading +{ + + /// <summary> + /// Provides a mutual exclusion lock primitive where a thread trying to acquire the lock waits in a loop + /// repeatedly checking until the lock becomes available. + /// </summary> + /// <remarks> + /// <para> + /// Spin locks can be used for leaf-level locks where the object allocation implied by using a <see + /// cref="System.Threading.Monitor"/>, in size or due to garbage collection pressure, is overly + /// expensive. Avoiding blocking is another reason that a spin lock can be useful, however if you expect + /// any significant amount of blocking, you are probably best not using spin locks due to excessive + /// spinning. Spinning can be beneficial when locks are fine grained and large in number (for example, a + /// lock per node in a linked list) as well as when lock hold times are always extremely short. In + /// general, while holding a spin lock, one should avoid blocking, calling anything that itself may + /// block, holding more than one spin lock at once, making dynamically dispatched calls (interface and + /// virtuals), making statically dispatched calls into any code one doesn't own, or allocating memory. + /// </para> + /// <para> + /// <see cref="SpinLock"/> should only be used when it's been determined that doing so will improve an + /// application's performance. It's also important to note that <see cref="SpinLock"/> is a value type, + /// for performance reasons. As such, one must be very careful not to accidentally copy a SpinLock + /// instance, as the two instances (the original and the copy) would then be completely independent of + /// one another, which would likely lead to erroneous behavior of the application. If a SpinLock instance + /// must be passed around, it should be passed by reference rather than by value. + /// </para> + /// <para> + /// Do not store <see cref="SpinLock"/> instances in readonly fields. + /// </para> + /// <para> + /// All members of <see cref="SpinLock"/> are thread-safe and may be used from multiple threads + /// concurrently. + /// </para> + /// </remarks> + [ComVisible(false)] + [HostProtection(Synchronization = true, ExternalThreading = true)] + [DebuggerTypeProxy(typeof(SystemThreading_SpinLockDebugView))] + [DebuggerDisplay("IsHeld = {IsHeld}")] + public struct SpinLock + { + // The current ownership state is a single signed int. There are two modes: + // + // 1) Ownership tracking enabled: the high bit is 0, and the remaining bits + // store the managed thread ID of the current owner. When the 31 low bits + // are 0, the lock is available. + // 2) Performance mode: when the high bit is 1, lock availability is indicated by the low bit. + // When the low bit is 1 -- the lock is held; 0 -- the lock is available. + // + // There are several masks and constants below for convenience. + + private volatile int m_owner; + + // The multiplier factor for the each spinning iteration + // This number has been chosen after trying different numbers on different CPUs (4, 8 and 16 ) and this provided the best results + private const int SPINNING_FACTOR = 100; + + // After how many yields, call Sleep(1) + private const int SLEEP_ONE_FREQUENCY = 40; + + // After how many yields, call Sleep(0) + private const int SLEEP_ZERO_FREQUENCY = 10; + + // After how many yields, check the timeout + private const int TIMEOUT_CHECK_FREQUENCY = 10; + + // Thr thread tracking disabled mask + private const int LOCK_ID_DISABLE_MASK = unchecked((int)0x80000000); //1000 0000 0000 0000 0000 0000 0000 0000 + + //the lock is held by some thread, but we don't know which + private const int LOCK_ANONYMOUS_OWNED = 0x1; //0000 0000 0000 0000 0000 0000 0000 0001 + + // Waiters mask if the thread tracking is disabled + private const int WAITERS_MASK = ~(LOCK_ID_DISABLE_MASK | 1); //0111 1111 1111 1111 1111 1111 1111 1110 + + // The Thread tacking is disabled and the lock bit is set, used in Enter fast path to make sure the id is disabled and lock is available + private const int ID_DISABLED_AND_ANONYMOUS_OWNED = unchecked((int)0x80000001); //1000 0000 0000 0000 0000 0000 0000 0001 + + // If the thread is unowned if: + // m_owner zero and the threa tracking is enabled + // m_owner & LOCK_ANONYMOUS_OWNED = zero and the thread tracking is disabled + private const int LOCK_UNOWNED = 0; + + // The maximum number of waiters (only used if the thread tracking is disabled) + // The actual maximum waiters count is this number divided by two because each waiter increments the waiters count by 2 + // The waiters count is calculated by m_owner & WAITERS_MASK 01111....110 + private static int MAXIMUM_WAITERS = WAITERS_MASK; + + + /// <summary> + /// Initializes a new instance of the <see cref="T:System.Threading.SpinLock"/> + /// structure with the option to track thread IDs to improve debugging. + /// </summary> + /// <remarks> + /// The default constructor for <see cref="SpinLock"/> tracks thread ownership. + /// </remarks> + /// <param name="enableThreadOwnerTracking">Whether to capture and use thread IDs for debugging + /// purposes.</param> + public SpinLock(bool enableThreadOwnerTracking) + { + m_owner = LOCK_UNOWNED; + if (!enableThreadOwnerTracking) + { + m_owner |= LOCK_ID_DISABLE_MASK; + Contract.Assert(!IsThreadOwnerTrackingEnabled, "property should be false by now"); + } + } + + + /// <summary> + /// Initializes a new instance of the <see cref="T:System.Threading.SpinLock"/> + /// structure with the option to track thread IDs to improve debugging. + /// </summary> + /// <remarks> + /// The default constructor for <see cref="SpinLock"/> tracks thread ownership. + /// </remarks> + /// <summary> + /// Acquires the lock in a reliable manner, such that even if an exception occurs within the method + /// call, <paramref name="lockTaken"/> can be examined reliably to determine whether the lock was + /// acquired. + /// </summary> + /// <remarks> + /// <see cref="SpinLock"/> is a non-reentrant lock, meaning that if a thread holds the lock, it is + /// not allowed to enter the lock again. If thread ownership tracking is enabled (whether it's + /// enabled is available through <see cref="IsThreadOwnerTrackingEnabled"/>), an exception will be + /// thrown when a thread tries to re-enter a lock it already holds. However, if thread ownership + /// tracking is disabled, attempting to enter a lock already held will result in deadlock. + /// </remarks> + /// <param name="lockTaken">True if the lock is acquired; otherwise, false. <paramref + /// name="lockTaken"/> must be initialized to false prior to calling this method.</param> + /// <exception cref="T:System.Threading.LockRecursionException"> + /// Thread ownership tracking is enabled, and the current thread has already acquired this lock. + /// </exception> + /// <exception cref="T:System.ArgumentException"> + /// The <paramref name="lockTaken"/> argument must be initialized to false prior to calling Enter. + /// </exception> + public void Enter(ref bool lockTaken) + { +#if !FEATURE_CORECLR + Thread.BeginCriticalRegion(); +#endif + //Try to keep the code and branching in this method as small as possible in order to inline the method + int observedOwner = m_owner; + if (lockTaken || //invalid parameter + (observedOwner & ID_DISABLED_AND_ANONYMOUS_OWNED) != LOCK_ID_DISABLE_MASK || //thread tracking is enabled or the lock is already acquired + Interlocked.CompareExchange(ref m_owner, observedOwner | LOCK_ANONYMOUS_OWNED, observedOwner, ref lockTaken) != observedOwner) //acquiring the lock failed + ContinueTryEnter(Timeout.Infinite, ref lockTaken); // Then try the slow path if any of the above conditions is met + + } + + /// <summary> + /// Attempts to acquire the lock in a reliable manner, such that even if an exception occurs within + /// the method call, <paramref name="lockTaken"/> can be examined reliably to determine whether the + /// lock was acquired. + /// </summary> + /// <remarks> + /// Unlike <see cref="Enter"/>, TryEnter will not block waiting for the lock to be available. If the + /// lock is not available when TryEnter is called, it will return immediately without any further + /// spinning. + /// </remarks> + /// <param name="lockTaken">True if the lock is acquired; otherwise, false. <paramref + /// name="lockTaken"/> must be initialized to false prior to calling this method.</param> + /// <exception cref="T:System.Threading.LockRecursionException"> + /// Thread ownership tracking is enabled, and the current thread has already acquired this lock. + /// </exception> + /// <exception cref="T:System.ArgumentException"> + /// The <paramref name="lockTaken"/> argument must be initialized to false prior to calling TryEnter. + /// </exception> + public void TryEnter(ref bool lockTaken) + { + TryEnter(0, ref lockTaken); + } + + /// <summary> + /// Attempts to acquire the lock in a reliable manner, such that even if an exception occurs within + /// the method call, <paramref name="lockTaken"/> can be examined reliably to determine whether the + /// lock was acquired. + /// </summary> + /// <remarks> + /// Unlike <see cref="Enter"/>, TryEnter will not block indefinitely waiting for the lock to be + /// available. It will block until either the lock is available or until the <paramref + /// name="timeout"/> + /// has expired. + /// </remarks> + /// <param name="timeout">A <see cref="System.TimeSpan"/> that represents the number of milliseconds + /// to wait, or a <see cref="System.TimeSpan"/> that represents -1 milliseconds to wait indefinitely. + /// </param> + /// <param name="lockTaken">True if the lock is acquired; otherwise, false. <paramref + /// name="lockTaken"/> must be initialized to false prior to calling this method.</param> + /// <exception cref="T:System.Threading.LockRecursionException"> + /// Thread ownership tracking is enabled, and the current thread has already acquired this lock. + /// </exception> + /// <exception cref="T:System.ArgumentException"> + /// The <paramref name="lockTaken"/> argument must be initialized to false prior to calling TryEnter. + /// </exception> + /// <exception cref="T:System.ArgumentOutOfRangeException"><paramref name="timeout"/> is a negative + /// number other than -1 milliseconds, which represents an infinite time-out -or- timeout is greater + /// than <see cref="System.Int32.MaxValue"/> milliseconds. + /// </exception> + public void TryEnter(TimeSpan timeout, ref bool lockTaken) + { + // Validate the timeout + Int64 totalMilliseconds = (Int64)timeout.TotalMilliseconds; + if (totalMilliseconds < -1 || totalMilliseconds > int.MaxValue) + { + throw new System.ArgumentOutOfRangeException( + "timeout", timeout, Environment.GetResourceString("SpinLock_TryEnter_ArgumentOutOfRange")); + } + + // Call reliable enter with the int-based timeout milliseconds + TryEnter((int)timeout.TotalMilliseconds, ref lockTaken); + } + + /// <summary> + /// Attempts to acquire the lock in a reliable manner, such that even if an exception occurs within + /// the method call, <paramref name="lockTaken"/> can be examined reliably to determine whether the + /// lock was acquired. + /// </summary> + /// <remarks> + /// Unlike <see cref="Enter"/>, TryEnter will not block indefinitely waiting for the lock to be + /// available. It will block until either the lock is available or until the <paramref + /// name="millisecondsTimeout"/> has expired. + /// </remarks> + /// <param name="millisecondsTimeout">The number of milliseconds to wait, or <see + /// cref="System.Threading.Timeout.Infinite"/> (-1) to wait indefinitely.</param> + /// <param name="lockTaken">True if the lock is acquired; otherwise, false. <paramref + /// name="lockTaken"/> must be initialized to false prior to calling this method.</param> + /// <exception cref="T:System.Threading.LockRecursionException"> + /// Thread ownership tracking is enabled, and the current thread has already acquired this lock. + /// </exception> + /// <exception cref="T:System.ArgumentException"> + /// The <paramref name="lockTaken"/> argument must be initialized to false prior to calling TryEnter. + /// </exception> + /// <exception cref="T:System.ArgumentOutOfRangeException"><paramref name="millisecondsTimeout"/> is + /// a negative number other than -1, which represents an infinite time-out.</exception> + public void TryEnter(int millisecondsTimeout, ref bool lockTaken) + { +#if !FEATURE_CORECLR + Thread.BeginCriticalRegion(); +#endif + + int observedOwner = m_owner; + if (millisecondsTimeout < -1 || //invalid parameter + lockTaken || //invalid parameter + (observedOwner & ID_DISABLED_AND_ANONYMOUS_OWNED) != LOCK_ID_DISABLE_MASK || //thread tracking is enabled or the lock is already acquired + Interlocked.CompareExchange(ref m_owner, observedOwner | LOCK_ANONYMOUS_OWNED, observedOwner, ref lockTaken) != observedOwner) // acquiring the lock failed + ContinueTryEnter(millisecondsTimeout, ref lockTaken); // The call the slow pth + } + + /// <summary> + /// Try acquire the lock with long path, this is usually called after the first path in Enter and + /// TryEnter failed The reason for short path is to make it inline in the run time which improves the + /// performance. This method assumed that the parameter are validated in Enter ir TryENter method + /// </summary> + /// <param name="millisecondsTimeout">The timeout milliseconds</param> + /// <param name="lockTaken">The lockTaken param</param> + private void ContinueTryEnter(int millisecondsTimeout, ref bool lockTaken) + { + //Leave the critical region which is entered by the fast path +#if !FEATURE_CORECLR + Thread.EndCriticalRegion(); +#endif + // The fast path doesn't throw any exception, so we have to validate the parameters here + if (lockTaken) + { + lockTaken = false; + throw new System.ArgumentException(Environment.GetResourceString("SpinLock_TryReliableEnter_ArgumentException")); + } + + if (millisecondsTimeout < -1) + { + throw new ArgumentOutOfRangeException( + "millisecondsTimeout", millisecondsTimeout, Environment.GetResourceString("SpinLock_TryEnter_ArgumentOutOfRange")); + } + + + uint startTime = 0; + if (millisecondsTimeout != Timeout.Infinite && millisecondsTimeout != 0) + { + startTime = TimeoutHelper.GetTime(); + } + +#if !FEATURE_CORECLR + if (CdsSyncEtwBCLProvider.Log.IsEnabled()) + { + CdsSyncEtwBCLProvider.Log.SpinLock_FastPathFailed(m_owner); + } +#endif + + if (IsThreadOwnerTrackingEnabled) + { + // Slow path for enabled thread tracking mode + ContinueTryEnterWithThreadTracking(millisecondsTimeout, startTime, ref lockTaken); + return; + } + + // then thread tracking is disabled + // In this case there are three ways to acquire the lock + // 1- the first way the thread either tries to get the lock if it's free or updates the waiters, if the turn >= the processors count then go to 3 else go to 2 + // 2- In this step the waiter threads spins and tries to acquire the lock, the number of spin iterations and spin count is dependent on the thread turn + // the late the thread arrives the more it spins and less frequent it check the lock avilability + // Also the spins count is increases each iteration + // If the spins iterations finished and failed to acquire the lock, go to step 3 + // 3- This is the yielding step, there are two ways of yielding Thread.Yield and Sleep(1) + // If the timeout is expired in after step 1, we need to decrement the waiters count before returning + + int observedOwner; + int turn = int.MaxValue; + //***Step 1, take the lock or update the waiters + + // try to acquire the lock directly if possible or update the waiters count + observedOwner = m_owner; + if ((observedOwner & LOCK_ANONYMOUS_OWNED) == LOCK_UNOWNED) + { +#if !FEATURE_CORECLR + Thread.BeginCriticalRegion(); +#endif + + if (Interlocked.CompareExchange(ref m_owner, observedOwner | 1, observedOwner, ref lockTaken) == observedOwner) + { + return; + } + +#if !FEATURE_CORECLR + Thread.EndCriticalRegion(); +#endif + } + else //failed to acquire the lock,then try to update the waiters. If the waiters count reached the maximum, jsut break the loop to avoid overflow + { + if ((observedOwner & WAITERS_MASK) != MAXIMUM_WAITERS) + turn = (Interlocked.Add(ref m_owner, 2) & WAITERS_MASK) >> 1 ; + } + + + + // Check the timeout. + if (millisecondsTimeout == 0 || + (millisecondsTimeout != Timeout.Infinite && + TimeoutHelper.UpdateTimeOut(startTime, millisecondsTimeout) <= 0)) + { + DecrementWaiters(); + return; + } + + //***Step 2. Spinning + //lock acquired failed and waiters updated + int processorCount = PlatformHelper.ProcessorCount; + if (turn < processorCount) + { + int processFactor = 1; + for (int i = 1; i <= turn * SPINNING_FACTOR; i++) + { + Thread.SpinWait((turn + i) * SPINNING_FACTOR * processFactor); + if (processFactor < processorCount) + processFactor++; + observedOwner = m_owner; + if ((observedOwner & LOCK_ANONYMOUS_OWNED) == LOCK_UNOWNED) + { +#if !FEATURE_CORECLR + Thread.BeginCriticalRegion(); +#endif + + int newOwner = (observedOwner & WAITERS_MASK) == 0 ? // Gets the number of waiters, if zero + observedOwner | 1 // don't decrement it. just set the lock bit, it is zzero because a previous call of Exit(false) ehich corrupted the waiters + : (observedOwner - 2) | 1; // otherwise decrement the waiters and set the lock bit + Contract.Assert((newOwner & WAITERS_MASK) >= 0); + + if (Interlocked.CompareExchange(ref m_owner, newOwner, observedOwner, ref lockTaken) == observedOwner) + { + return; + } + +#if !FEATURE_CORECLR + Thread.EndCriticalRegion(); +#endif + } + } + } + + // Check the timeout. + if (millisecondsTimeout != Timeout.Infinite && TimeoutHelper.UpdateTimeOut(startTime, millisecondsTimeout) <= 0) + { + DecrementWaiters(); + return; + } + + //*** Step 3, Yielding + //Sleep(1) every 50 yields + int yieldsoFar = 0; + while (true) + { + observedOwner = m_owner; + if ((observedOwner & LOCK_ANONYMOUS_OWNED) == LOCK_UNOWNED) + { +#if !FEATURE_CORECLR + Thread.BeginCriticalRegion(); +#endif + int newOwner = (observedOwner & WAITERS_MASK) == 0 ? // Gets the number of waiters, if zero + observedOwner | 1 // don't decrement it. just set the lock bit, it is zzero because a previous call of Exit(false) ehich corrupted the waiters + : (observedOwner - 2) | 1; // otherwise decrement the waiters and set the lock bit + Contract.Assert((newOwner & WAITERS_MASK) >= 0); + + if (Interlocked.CompareExchange(ref m_owner, newOwner, observedOwner, ref lockTaken) == observedOwner) + { + return; + } + +#if !FEATURE_CORECLR + Thread.EndCriticalRegion(); +#endif + } + + if (yieldsoFar % SLEEP_ONE_FREQUENCY == 0) + { + Thread.Sleep(1); + } + else if (yieldsoFar % SLEEP_ZERO_FREQUENCY == 0) + { + Thread.Sleep(0); + } + else + { + Thread.Yield(); + } + + if (yieldsoFar % TIMEOUT_CHECK_FREQUENCY == 0) + { + //Check the timeout. + if (millisecondsTimeout != Timeout.Infinite && TimeoutHelper.UpdateTimeOut(startTime, millisecondsTimeout) <= 0) + { + DecrementWaiters(); + return; + } + } + + yieldsoFar++; + } + } + + /// <summary> + /// decrements the waiters, in case of the timeout is expired + /// </summary> + private void DecrementWaiters() + { + SpinWait spinner = new SpinWait(); + while (true) + { + int observedOwner = m_owner; + if ((observedOwner & WAITERS_MASK) == 0) return; // don't decrement the waiters if it's corrupted by previous call of Exit(false) + if (Interlocked.CompareExchange(ref m_owner, observedOwner - 2, observedOwner) == observedOwner) + { + Contract.Assert(!IsThreadOwnerTrackingEnabled); // Make sure the waiters never be negative which will cause the thread tracking bit to be flipped + break; + } + spinner.SpinOnce(); + } + + } + + /// <summary> + /// ContinueTryEnter for the thread tracking mode enabled + /// </summary> + private void ContinueTryEnterWithThreadTracking(int millisecondsTimeout, uint startTime, ref bool lockTaken) + { + Contract.Assert(IsThreadOwnerTrackingEnabled); + + int lockUnowned = 0; + // We are using thread IDs to mark ownership. Snap the thread ID and check for recursion. + // We also must or the ID enablement bit, to ensure we propagate when we CAS it in. + int m_newOwner = Thread.CurrentThread.ManagedThreadId; + if (m_owner == m_newOwner) + { + // We don't allow lock recursion. + throw new LockRecursionException(Environment.GetResourceString("SpinLock_TryEnter_LockRecursionException")); + } + + + SpinWait spinner = new SpinWait(); + + // Loop until the lock has been successfully acquired or, if specified, the timeout expires. + do + { + + // We failed to get the lock, either from the fast route or the last iteration + // and the timeout hasn't expired; spin once and try again. + spinner.SpinOnce(); + + // Test before trying to CAS, to avoid acquiring the line exclusively unnecessarily. + + if (m_owner == lockUnowned) + { +#if !FEATURE_CORECLR + Thread.BeginCriticalRegion(); +#endif + if (Interlocked.CompareExchange(ref m_owner, m_newOwner, lockUnowned, ref lockTaken) == lockUnowned) + { + return; + } +#if !FEATURE_CORECLR + // The thread failed to get the lock, so we don't need to remain in a critical region. + Thread.EndCriticalRegion(); +#endif + } + // Check the timeout. We only RDTSC if the next spin will yield, to amortize the cost. + if (millisecondsTimeout == 0 || + (millisecondsTimeout != Timeout.Infinite && spinner.NextSpinWillYield && + TimeoutHelper.UpdateTimeOut(startTime, millisecondsTimeout) <= 0)) + { + return; + } + } while (true); + } + + /// <summary> + /// Releases the lock. + /// </summary> + /// <remarks> + /// The default overload of <see cref="Exit()"/> provides the same behavior as if calling <see + /// cref="Exit(Boolean)"/> using true as the argument, but Exit() could be slightly faster than Exit(true). + /// </remarks> + /// <exception cref="SynchronizationLockException"> + /// Thread ownership tracking is enabled, and the current thread is not the owner of this lock. + /// </exception> + [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] + public void Exit() + { + //This is the fast path for the thread tracking is disabled, otherwise go to the slow path + if ((m_owner & LOCK_ID_DISABLE_MASK) == 0) + ExitSlowPath(true); + else + Interlocked.Decrement(ref m_owner); + +#if !FEATURE_CORECLR + Thread.EndCriticalRegion(); +#endif + + } + + /// <summary> + /// Releases the lock. + /// </summary> + /// <param name="useMemoryBarrier"> + /// A Boolean value that indicates whether a memory fence should be issued in order to immediately + /// publish the exit operation to other threads. + /// </param> + /// <remarks> + /// Calling <see cref="Exit(Boolean)"/> with the <paramref name="useMemoryBarrier"/> argument set to + /// true will improve the fairness of the lock at the expense of some performance. The default <see + /// cref="Enter"/> + /// overload behaves as if specifying true for <paramref name="useMemoryBarrier"/>. + /// </remarks> + /// <exception cref="SynchronizationLockException"> + /// Thread ownership tracking is enabled, and the current thread is not the owner of this lock. + /// </exception> + [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] + public void Exit(bool useMemoryBarrier) + { + // This is the fast path for the thread tracking is diabled and not to use memory barrier, otherwise go to the slow path + // The reason not to add else statement if the usememorybarrier is that it will add more barnching in the code and will prevent + // method inlining, so this is optimized for useMemoryBarrier=false and Exit() overload optimized for useMemoryBarrier=true + if ((m_owner & LOCK_ID_DISABLE_MASK) != 0 && !useMemoryBarrier) + { + int tmpOwner = m_owner; + m_owner = tmpOwner & (~LOCK_ANONYMOUS_OWNED); + } + else + ExitSlowPath(useMemoryBarrier); + +#if !FEATURE_CORECLR + Thread.EndCriticalRegion(); +#endif + } + + /// <summary> + /// The slow path for exit method if the fast path failed + /// </summary> + /// <param name="useMemoryBarrier"> + /// A Boolean value that indicates whether a memory fence should be issued in order to immediately + /// publish the exit operation to other threads + /// </param> + private void ExitSlowPath(bool useMemoryBarrier) + { + bool threadTrackingEnabled = (m_owner & LOCK_ID_DISABLE_MASK) == 0; + if (threadTrackingEnabled && !IsHeldByCurrentThread) + { + throw new System.Threading.SynchronizationLockException( + Environment.GetResourceString("SpinLock_Exit_SynchronizationLockException")); + } + + if (useMemoryBarrier) + { + if (threadTrackingEnabled) + Interlocked.Exchange(ref m_owner, LOCK_UNOWNED); + else + Interlocked.Decrement(ref m_owner); + + } + else + { + if (threadTrackingEnabled) + m_owner = LOCK_UNOWNED; + else + { + int tmpOwner = m_owner; + m_owner = tmpOwner & (~LOCK_ANONYMOUS_OWNED); + } + + } + + } + + /// <summary> + /// Gets whether the lock is currently held by any thread. + /// </summary> + public bool IsHeld + { + [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] + get + { + if (IsThreadOwnerTrackingEnabled) + return m_owner != LOCK_UNOWNED; + + return (m_owner & LOCK_ANONYMOUS_OWNED) != LOCK_UNOWNED; + } + } + + /// <summary> + /// Gets whether the lock is currently held by any thread. + /// </summary> + /// <summary> + /// Gets whether the lock is held by the current thread. + /// </summary> + /// <remarks> + /// If the lock was initialized to track owner threads, this will return whether the lock is acquired + /// by the current thread. It is invalid to use this property when the lock was initialized to not + /// track thread ownership. + /// </remarks> + /// <exception cref="T:System.InvalidOperationException"> + /// Thread ownership tracking is disabled. + /// </exception> + public bool IsHeldByCurrentThread + { + [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] + get + { + if (!IsThreadOwnerTrackingEnabled) + { + throw new InvalidOperationException(Environment.GetResourceString("SpinLock_IsHeldByCurrentThread")); + } + return ((m_owner & (~LOCK_ID_DISABLE_MASK)) == Thread.CurrentThread.ManagedThreadId); + } + } + + /// <summary>Gets whether thread ownership tracking is enabled for this instance.</summary> + public bool IsThreadOwnerTrackingEnabled + { + [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] + get { return (m_owner & LOCK_ID_DISABLE_MASK) == 0; } + } + + #region Debugger proxy class + /// <summary> + /// Internal class used by debug type proxy attribute to display the owner thread ID + /// </summary> + internal class SystemThreading_SpinLockDebugView + { + // SpinLock object + private SpinLock m_spinLock; + + /// <summary> + /// SystemThreading_SpinLockDebugView constructor + /// </summary> + /// <param name="spinLock">The SpinLock to be proxied.</param> + public SystemThreading_SpinLockDebugView(SpinLock spinLock) + { + // Note that this makes a copy of the SpinLock (struct). It doesn't hold a reference to it. + m_spinLock = spinLock; + } + + /// <summary> + /// Checks if the lock is held by the current thread or not + /// </summary> + public bool? IsHeldByCurrentThread + { + get + { + try + { + return m_spinLock.IsHeldByCurrentThread; + } + catch (InvalidOperationException) + { + return null; + } + } + } + + /// <summary> + /// Gets the current owner thread, zero if it is released + /// </summary> + public int? OwnerThreadID + { + get + { + if (m_spinLock.IsThreadOwnerTrackingEnabled) + { + return m_spinLock.m_owner; + } + else + { + return null; + } + } + } + + + /// <summary> + /// Gets whether the lock is currently held by any thread or not. + /// </summary> + public bool IsHeld + { + get { return m_spinLock.IsHeld; } + } + } + #endregion + + } +} +#pragma warning restore 0420 diff --git a/src/mscorlib/src/System/Threading/SpinWait.cs b/src/mscorlib/src/System/Threading/SpinWait.cs new file mode 100644 index 0000000000..c2cd0b6203 --- /dev/null +++ b/src/mscorlib/src/System/Threading/SpinWait.cs @@ -0,0 +1,369 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +// =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ +// +// +// +// Central spin logic used across the entire code-base. +// +// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + +using System; +using System.Runtime.ConstrainedExecution; +using System.Security.Permissions; +using System.Threading; +using System.Diagnostics.Contracts; +using System.Diagnostics.CodeAnalysis; + +namespace System.Threading +{ + // SpinWait is just a little value type that encapsulates some common spinning + // logic. It ensures we always yield on single-proc machines (instead of using busy + // waits), and that we work well on HT. It encapsulates a good mixture of spinning + // and real yielding. It's a value type so that various areas of the engine can use + // one by allocating it on the stack w/out unnecessary GC allocation overhead, e.g.: + // + // void f() { + // SpinWait wait = new SpinWait(); + // while (!p) { wait.SpinOnce(); } + // ... + // } + // + // Internally it just maintains a counter that is used to decide when to yield, etc. + // + // A common usage is to spin before blocking. In those cases, the NextSpinWillYield + // property allows a user to decide to fall back to waiting once it returns true: + // + // void f() { + // SpinWait wait = new SpinWait(); + // while (!p) { + // if (wait.NextSpinWillYield) { /* block! */ } + // else { wait.SpinOnce(); } + // } + // ... + // } + + /// <summary> + /// Provides support for spin-based waiting. + /// </summary> + /// <remarks> + /// <para> + /// <see cref="SpinWait"/> encapsulates common spinning logic. On single-processor machines, yields are + /// always used instead of busy waits, and on computers with Intel™ processors employing Hyper-Threading™ + /// technology, it helps to prevent hardware thread starvation. SpinWait encapsulates a good mixture of + /// spinning and true yielding. + /// </para> + /// <para> + /// <see cref="SpinWait"/> is a value type, which means that low-level code can utilize SpinWait without + /// fear of unnecessary allocation overheads. SpinWait is not generally useful for ordinary applications. + /// In most cases, you should use the synchronization classes provided by the .NET Framework, such as + /// <see cref="System.Threading.Monitor"/>. For most purposes where spin waiting is required, however, + /// the <see cref="SpinWait"/> type should be preferred over the <see + /// cref="System.Threading.Thread.SpinWait"/> method. + /// </para> + /// <para> + /// While SpinWait is designed to be used in concurrent applications, it is not designed to be + /// used from multiple threads concurrently. SpinWait's members are not thread-safe. If multiple + /// threads must spin, each should use its own instance of SpinWait. + /// </para> + /// </remarks> + [HostProtection(Synchronization = true, ExternalThreading = true)] + public struct SpinWait + { + + // These constants determine the frequency of yields versus spinning. The + // numbers may seem fairly arbitrary, but were derived with at least some + // thought in the design document. I fully expect they will need to change + // over time as we gain more experience with performance. + internal const int YIELD_THRESHOLD = 10; // When to switch over to a true yield. + internal const int SLEEP_0_EVERY_HOW_MANY_TIMES = 5; // After how many yields should we Sleep(0)? + internal const int SLEEP_1_EVERY_HOW_MANY_TIMES = 20; // After how many yields should we Sleep(1)? + + // The number of times we've spun already. + private int m_count; + + /// <summary> + /// Gets the number of times <see cref="SpinOnce"/> has been called on this instance. + /// </summary> + public int Count + { + get { return m_count; } + } + + /// <summary> + /// Gets whether the next call to <see cref="SpinOnce"/> will yield the processor, triggering a + /// forced context switch. + /// </summary> + /// <value>Whether the next call to <see cref="SpinOnce"/> will yield the processor, triggering a + /// forced context switch.</value> + /// <remarks> + /// On a single-CPU machine, <see cref="SpinOnce"/> always yields the processor. On machines with + /// multiple CPUs, <see cref="SpinOnce"/> may yield after an unspecified number of calls. + /// </remarks> + public bool NextSpinWillYield + { + get { return m_count > YIELD_THRESHOLD || PlatformHelper.IsSingleProcessor; } + } + + /// <summary> + /// Performs a single spin. + /// </summary> + /// <remarks> + /// This is typically called in a loop, and may change in behavior based on the number of times a + /// <see cref="SpinOnce"/> has been called thus far on this instance. + /// </remarks> + public void SpinOnce() + { + if (NextSpinWillYield) + { + // + // We must yield. + // + // We prefer to call Thread.Yield first, triggering a SwitchToThread. This + // unfortunately doesn't consider all runnable threads on all OS SKUs. In + // some cases, it may only consult the runnable threads whose ideal processor + // is the one currently executing code. Thus we occasionally issue a call to + // Sleep(0), which considers all runnable threads at equal priority. Even this + // is insufficient since we may be spin waiting for lower priority threads to + // execute; we therefore must call Sleep(1) once in a while too, which considers + // all runnable threads, regardless of ideal processor and priority, but may + // remove the thread from the scheduler's queue for 10+ms, if the system is + // configured to use the (default) coarse-grained system timer. + // + +#if !FEATURE_CORECLR + CdsSyncEtwBCLProvider.Log.SpinWait_NextSpinWillYield(); +#endif + int yieldsSoFar = (m_count >= YIELD_THRESHOLD ? m_count - YIELD_THRESHOLD : m_count); + + if ((yieldsSoFar % SLEEP_1_EVERY_HOW_MANY_TIMES) == (SLEEP_1_EVERY_HOW_MANY_TIMES - 1)) + { + Thread.Sleep(1); + } + else if ((yieldsSoFar % SLEEP_0_EVERY_HOW_MANY_TIMES) == (SLEEP_0_EVERY_HOW_MANY_TIMES - 1)) + { + Thread.Sleep(0); + } + else + { + Thread.Yield(); + } + } + else + { + // + // Otherwise, we will spin. + // + // We do this using the CLR's SpinWait API, which is just a busy loop that + // issues YIELD/PAUSE instructions to ensure multi-threaded CPUs can react + // intelligently to avoid starving. (These are NOOPs on other CPUs.) We + // choose a number for the loop iteration count such that each successive + // call spins for longer, to reduce cache contention. We cap the total + // number of spins we are willing to tolerate to reduce delay to the caller, + // since we expect most callers will eventually block anyway. + // + Thread.SpinWait(4 << m_count); + } + + // Finally, increment our spin counter. + m_count = (m_count == int.MaxValue ? YIELD_THRESHOLD : m_count + 1); + } + + /// <summary> + /// Resets the spin counter. + /// </summary> + /// <remarks> + /// This makes <see cref="SpinOnce"/> and <see cref="NextSpinWillYield"/> behave as though no calls + /// to <see cref="SpinOnce"/> had been issued on this instance. If a <see cref="SpinWait"/> instance + /// is reused many times, it may be useful to reset it to avoid yielding too soon. + /// </remarks> + public void Reset() + { + m_count = 0; + } + + #region Static Methods + /// <summary> + /// Spins until the specified condition is satisfied. + /// </summary> + /// <param name="condition">A delegate to be executed over and over until it returns true.</param> + /// <exception cref="ArgumentNullException">The <paramref name="condition"/> argument is null.</exception> + public static void SpinUntil(Func<bool> condition) + { +#if DEBUG + bool result = +#endif + SpinUntil(condition, Timeout.Infinite); +#if DEBUG + Contract.Assert(result); +#endif + } + + /// <summary> + /// Spins until the specified condition is satisfied or until the specified timeout is expired. + /// </summary> + /// <param name="condition">A delegate to be executed over and over until it returns true.</param> + /// <param name="timeout"> + /// A <see cref="TimeSpan"/> that represents the number of milliseconds to wait, + /// or a TimeSpan that represents -1 milliseconds to wait indefinitely.</param> + /// <returns>True if the condition is satisfied within the timeout; otherwise, false</returns> + /// <exception cref="ArgumentNullException">The <paramref name="condition"/> argument is null.</exception> + /// <exception cref="T:System.ArgumentOutOfRangeException"><paramref name="timeout"/> is a negative number + /// other than -1 milliseconds, which represents an infinite time-out -or- timeout is greater than + /// <see cref="System.Int32.MaxValue"/>.</exception> + public static bool SpinUntil(Func<bool> condition, TimeSpan timeout) + { + // Validate the timeout + Int64 totalMilliseconds = (Int64)timeout.TotalMilliseconds; + if (totalMilliseconds < -1 || totalMilliseconds > Int32.MaxValue) + { + throw new System.ArgumentOutOfRangeException( + "timeout", timeout, Environment.GetResourceString("SpinWait_SpinUntil_TimeoutWrong")); + } + + // Call wait with the timeout milliseconds + return SpinUntil(condition, (int)timeout.TotalMilliseconds); + } + + /// <summary> + /// Spins until the specified condition is satisfied or until the specified timeout is expired. + /// </summary> + /// <param name="condition">A delegate to be executed over and over until it returns true.</param> + /// <param name="millisecondsTimeout">The number of milliseconds to wait, or <see + /// cref="System.Threading.Timeout.Infinite"/> (-1) to wait indefinitely.</param> + /// <returns>True if the condition is satisfied within the timeout; otherwise, false</returns> + /// <exception cref="ArgumentNullException">The <paramref name="condition"/> argument is null.</exception> + /// <exception cref="T:System.ArgumentOutOfRangeException"><paramref name="millisecondsTimeout"/> is a + /// negative number other than -1, which represents an infinite time-out.</exception> + public static bool SpinUntil(Func<bool> condition, int millisecondsTimeout) + { + if (millisecondsTimeout < Timeout.Infinite) + { + throw new ArgumentOutOfRangeException( + "millisecondsTimeout", millisecondsTimeout, Environment.GetResourceString("SpinWait_SpinUntil_TimeoutWrong")); + } + if (condition == null) + { + throw new ArgumentNullException("condition", Environment.GetResourceString("SpinWait_SpinUntil_ArgumentNull")); + } + uint startTime = 0; + if (millisecondsTimeout != 0 && millisecondsTimeout != Timeout.Infinite) + { + startTime = TimeoutHelper.GetTime(); + } + SpinWait spinner = new SpinWait(); + while (!condition()) + { + if (millisecondsTimeout == 0) + { + return false; + } + + spinner.SpinOnce(); + + if (millisecondsTimeout != Timeout.Infinite && spinner.NextSpinWillYield) + { + if (millisecondsTimeout <= (TimeoutHelper.GetTime() - startTime)) + { + return false; + } + } + } + return true; + + } + #endregion + + } + + + /// <summary> + /// A helper class to get the number of processors, it updates the numbers of processors every sampling interval. + /// </summary> + internal static class PlatformHelper + { + private const int PROCESSOR_COUNT_REFRESH_INTERVAL_MS = 30000; // How often to refresh the count, in milliseconds. + private static volatile int s_processorCount; // The last count seen. + private static volatile int s_lastProcessorCountRefreshTicks; // The last time we refreshed. + + /// <summary> + /// Gets the number of available processors + /// </summary> + [SuppressMessage("Microsoft.Concurrency", "CA8001", Justification = "Reviewed for thread safety")] + internal static int ProcessorCount + { + get + { + int now = Environment.TickCount; + int procCount = s_processorCount; + if (procCount == 0 || (now - s_lastProcessorCountRefreshTicks) >= PROCESSOR_COUNT_REFRESH_INTERVAL_MS) + { + s_processorCount = procCount = Environment.ProcessorCount; + s_lastProcessorCountRefreshTicks = now; + } + + Contract.Assert(procCount > 0 && procCount <= 64, + "Processor count not within the expected range (1 - 64)."); + + return procCount; + } + } + + /// <summary> + /// Gets whether the current machine has only a single processor. + /// </summary> + internal static bool IsSingleProcessor + { + get { return ProcessorCount == 1; } + } + } + + /// <summary> + /// A helper class to capture a start time using Environment.TickCout as a time in milliseconds, also updates a given timeout bu subtracting the current time from + /// the start time + /// </summary> + internal static class TimeoutHelper + { + /// <summary> + /// Returns the Environment.TickCount as a start time in milliseconds as a uint, TickCount tools over from postive to negative every ~ 25 days + /// then ~25 days to back to positive again, uint is sued to ignore the sign and double the range to 50 days + /// </summary> + /// <returns></returns> + public static uint GetTime() + { + return (uint)Environment.TickCount; + } + + /// <summary> + /// Helper function to measure and update the elapsed time + /// </summary> + /// <param name="startTime"> The first time (in milliseconds) observed when the wait started</param> + /// <param name="originalWaitMillisecondsTimeout">The orginal wait timeoutout in milliseconds</param> + /// <returns>The new wait time in milliseconds, -1 if the time expired</returns> + public static int UpdateTimeOut(uint startTime, int originalWaitMillisecondsTimeout) + { + // The function must be called in case the time out is not infinite + Contract.Assert(originalWaitMillisecondsTimeout != Timeout.Infinite); + + uint elapsedMilliseconds = (GetTime() - startTime); + + // Check the elapsed milliseconds is greater than max int because this property is uint + if (elapsedMilliseconds > int.MaxValue) + { + return 0; + } + + // Subtract the elapsed time from the current wait time + int currentWaitTimeout = originalWaitMillisecondsTimeout - (int)elapsedMilliseconds; ; + if (currentWaitTimeout <= 0) + { + return 0; + } + + return currentWaitTimeout; + } + } + +} diff --git a/src/mscorlib/src/System/Threading/SynchronizationContext.cs b/src/mscorlib/src/System/Threading/SynchronizationContext.cs new file mode 100644 index 0000000000..a3f28d1d73 --- /dev/null +++ b/src/mscorlib/src/System/Threading/SynchronizationContext.cs @@ -0,0 +1,320 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. +/*============================================================ +** +** +** +** Purpose: Capture synchronization semantics for asynchronous callbacks +** +** +===========================================================*/ + +namespace System.Threading +{ + using Microsoft.Win32.SafeHandles; + using System.Security.Permissions; + using System.Runtime.InteropServices; + using System.Runtime.CompilerServices; +#if FEATURE_CORRUPTING_EXCEPTIONS + using System.Runtime.ExceptionServices; +#endif // FEATURE_CORRUPTING_EXCEPTIONS + using System.Runtime; + using System.Runtime.Versioning; + using System.Runtime.ConstrainedExecution; + using System.Reflection; + using System.Security; + using System.Diagnostics.Contracts; + using System.Diagnostics.CodeAnalysis; + + +#if FEATURE_SYNCHRONIZATIONCONTEXT_WAIT + [Flags] + enum SynchronizationContextProperties + { + None = 0, + RequireWaitNotification = 0x1 + }; +#endif + +#if FEATURE_COMINTEROP && FEATURE_APPX + // + // This is implemented in System.Runtime.WindowsRuntime, allowing us to ask that assembly for a WinRT-specific SyncCtx. + // I'd like this to be an interface, or at least an abstract class - but neither seems to play nice with FriendAccessAllowed. + // + [FriendAccessAllowed] + [SecurityCritical] + internal class WinRTSynchronizationContextFactoryBase + { + [SecurityCritical] + public virtual SynchronizationContext Create(object coreDispatcher) {return null;} + } +#endif //FEATURE_COMINTEROP + +#if !FEATURE_CORECLR + [SecurityPermissionAttribute(SecurityAction.InheritanceDemand, Flags =SecurityPermissionFlag.ControlPolicy|SecurityPermissionFlag.ControlEvidence)] +#endif + public class SynchronizationContext + { +#if FEATURE_SYNCHRONIZATIONCONTEXT_WAIT + SynchronizationContextProperties _props = SynchronizationContextProperties.None; +#endif + + public SynchronizationContext() + { + } + +#if FEATURE_SYNCHRONIZATIONCONTEXT_WAIT + + // helper delegate to statically bind to Wait method + private delegate int WaitDelegate(IntPtr[] waitHandles, bool waitAll, int millisecondsTimeout); + + static Type s_cachedPreparedType1; + static Type s_cachedPreparedType2; + static Type s_cachedPreparedType3; + static Type s_cachedPreparedType4; + static Type s_cachedPreparedType5; + + // protected so that only the derived sync context class can enable these flags + [System.Security.SecuritySafeCritical] // auto-generated + [SuppressMessage("Microsoft.Concurrency", "CA8001", Justification = "We never dereference s_cachedPreparedType*, so ordering is unimportant")] + protected void SetWaitNotificationRequired() + { + // + // Prepare the method so that it can be called in a reliable fashion when a wait is needed. + // This will obviously only make the Wait reliable if the Wait method is itself reliable. The only thing + // preparing the method here does is to ensure there is no failure point before the method execution begins. + // + // Preparing the method in this way is quite expensive, but only needs to be done once per type, per AppDomain. + // So we keep track of a few types we've already prepared in this AD. It is uncommon to have more than + // a few SynchronizationContext implementations, so we only cache the first five we encounter; this lets + // our cache be much faster than a more general cache might be. This is important, because this + // is a *very* hot code path for many WPF and WinForms apps. + // + Type type = this.GetType(); + if (s_cachedPreparedType1 != type && + s_cachedPreparedType2 != type && + s_cachedPreparedType3 != type && + s_cachedPreparedType4 != type && + s_cachedPreparedType5 != type) + { + RuntimeHelpers.PrepareDelegate(new WaitDelegate(this.Wait)); + + if (s_cachedPreparedType1 == null) s_cachedPreparedType1 = type; + else if (s_cachedPreparedType2 == null) s_cachedPreparedType2 = type; + else if (s_cachedPreparedType3 == null) s_cachedPreparedType3 = type; + else if (s_cachedPreparedType4 == null) s_cachedPreparedType4 = type; + else if (s_cachedPreparedType5 == null) s_cachedPreparedType5 = type; + } + + _props |= SynchronizationContextProperties.RequireWaitNotification; + } + + public bool IsWaitNotificationRequired() + { + return ((_props & SynchronizationContextProperties.RequireWaitNotification) != 0); + } +#endif + + + public virtual void Send(SendOrPostCallback d, Object state) + { + d(state); + } + + public virtual void Post(SendOrPostCallback d, Object state) + { + ThreadPool.QueueUserWorkItem(new WaitCallback(d), state); + } + + + /// <summary> + /// Optional override for subclasses, for responding to notification that operation is starting. + /// </summary> + public virtual void OperationStarted() + { + } + + /// <summary> + /// Optional override for subclasses, for responding to notification that operation has completed. + /// </summary> + public virtual void OperationCompleted() + { + } + +#if FEATURE_SYNCHRONIZATIONCONTEXT_WAIT + // Method called when the CLR does a wait operation + [System.Security.SecurityCritical] // auto-generated_required + [CLSCompliant(false)] + [PrePrepareMethod] + public virtual int Wait(IntPtr[] waitHandles, bool waitAll, int millisecondsTimeout) + { + if (waitHandles == null) + { + throw new ArgumentNullException("waitHandles"); + } + Contract.EndContractBlock(); + return WaitHelper(waitHandles, waitAll, millisecondsTimeout); + } + + // Static helper to which the above method can delegate to in order to get the default + // COM behavior. + [System.Security.SecurityCritical] // auto-generated_required + [CLSCompliant(false)] + [PrePrepareMethod] + [MethodImplAttribute(MethodImplOptions.InternalCall)] + [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)] + protected static extern int WaitHelper(IntPtr[] waitHandles, bool waitAll, int millisecondsTimeout); +#endif + +#if FEATURE_CORECLR + + [System.Security.SecurityCritical] + public static void SetSynchronizationContext(SynchronizationContext syncContext) + { + Thread.CurrentThread.SynchronizationContext = syncContext; + } + + [System.Security.SecurityCritical] + public static void SetThreadStaticContext(SynchronizationContext syncContext) + { + Thread.CurrentThread.SynchronizationContext = syncContext; + } + + public static SynchronizationContext Current + { + get + { + SynchronizationContext context = Thread.CurrentThread.SynchronizationContext; + +#if FEATURE_APPX + if (context == null && AppDomain.IsAppXModel()) + context = GetWinRTContext(); +#endif + + return context; + } + } + + // Get the last SynchronizationContext that was set explicitly (not flowed via ExecutionContext.Capture/Run) + internal static SynchronizationContext CurrentNoFlow + { + [FriendAccessAllowed] + get + { + return Current; // SC never flows + } + } + +#else //FEATURE_CORECLR + + // set SynchronizationContext on the current thread + [System.Security.SecurityCritical] // auto-generated_required + public static void SetSynchronizationContext(SynchronizationContext syncContext) + { + ExecutionContext ec = Thread.CurrentThread.GetMutableExecutionContext(); + ec.SynchronizationContext = syncContext; + ec.SynchronizationContextNoFlow = syncContext; + } + + // Get the current SynchronizationContext on the current thread + public static SynchronizationContext Current + { + get + { + return Thread.CurrentThread.GetExecutionContextReader().SynchronizationContext ?? GetThreadLocalContext(); + } + } + + // Get the last SynchronizationContext that was set explicitly (not flowed via ExecutionContext.Capture/Run) + internal static SynchronizationContext CurrentNoFlow + { + [FriendAccessAllowed] + get + { + return Thread.CurrentThread.GetExecutionContextReader().SynchronizationContextNoFlow ?? GetThreadLocalContext(); + } + } + + private static SynchronizationContext GetThreadLocalContext() + { + SynchronizationContext context = null; + +#if FEATURE_APPX + if (context == null && AppDomain.IsAppXModel()) + context = GetWinRTContext(); +#endif + + return context; + } + +#endif //FEATURE_CORECLR + +#if FEATURE_APPX + [SecuritySafeCritical] + private static SynchronizationContext GetWinRTContext() + { + Contract.Assert(Environment.IsWinRTSupported); + Contract.Assert(AppDomain.IsAppXModel()); + + // + // We call into the VM to get the dispatcher. This is because: + // + // a) We cannot call the WinRT APIs directly from mscorlib, because we don't have the fancy projections here. + // b) We cannot call into System.Runtime.WindowsRuntime here, because we don't want to load that assembly + // into processes that don't need it (for performance reasons). + // + // So, we check the VM to see if the current thread has a dispatcher; if it does, we pass that along to + // System.Runtime.WindowsRuntime to get a corresponding SynchronizationContext. + // + object dispatcher = GetWinRTDispatcherForCurrentThread(); + if (dispatcher != null) + return GetWinRTSynchronizationContextFactory().Create(dispatcher); + + return null; + } + + [SecurityCritical] + static WinRTSynchronizationContextFactoryBase s_winRTContextFactory; + + [SecurityCritical] + private static WinRTSynchronizationContextFactoryBase GetWinRTSynchronizationContextFactory() + { + // + // Since we can't directly reference System.Runtime.WindowsRuntime from mscorlib, we have to get the factory via reflection. + // It would be better if we could just implement WinRTSynchronizationContextFactory in mscorlib, but we can't, because + // we can do very little with WinRT stuff in mscorlib. + // + WinRTSynchronizationContextFactoryBase factory = s_winRTContextFactory; + if (factory == null) + { + Type factoryType = Type.GetType("System.Threading.WinRTSynchronizationContextFactory, " + AssemblyRef.SystemRuntimeWindowsRuntime, true); + s_winRTContextFactory = factory = (WinRTSynchronizationContextFactoryBase)Activator.CreateInstance(factoryType, true); + } + return factory; + } + + [DllImport(JitHelpers.QCall, CharSet = CharSet.Unicode)] + [SecurityCritical] + [SuppressUnmanagedCodeSecurity] + [return: MarshalAs(UnmanagedType.Interface)] + private static extern object GetWinRTDispatcherForCurrentThread(); +#endif //FEATURE_APPX + + + // helper to Clone this SynchronizationContext, + public virtual SynchronizationContext CreateCopy() + { + // the CLR dummy has an empty clone function - no member data + return new SynchronizationContext(); + } + +#if FEATURE_SYNCHRONIZATIONCONTEXT_WAIT + [System.Security.SecurityCritical] // auto-generated + private static int InvokeWaitMethodHelper(SynchronizationContext syncContext, IntPtr[] waitHandles, bool waitAll, int millisecondsTimeout) + { + return syncContext.Wait(waitHandles, waitAll, millisecondsTimeout); + } +#endif + } +} diff --git a/src/mscorlib/src/System/Threading/SynchronizationLockException.cs b/src/mscorlib/src/System/Threading/SynchronizationLockException.cs new file mode 100644 index 0000000000..0610a6063d --- /dev/null +++ b/src/mscorlib/src/System/Threading/SynchronizationLockException.cs @@ -0,0 +1,44 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +// +/*============================================================================= +** +** +** +** Purpose: Wait(), Notify() or NotifyAll() was called from an unsynchronized +** block of code. +** +** +=============================================================================*/ + +namespace System.Threading { + + using System; + using System.Runtime.Serialization; + [System.Runtime.InteropServices.ComVisible(true)] + [Serializable] + public class SynchronizationLockException : SystemException { + public SynchronizationLockException() + : base(Environment.GetResourceString("Arg_SynchronizationLockException")) { + SetErrorCode(__HResults.COR_E_SYNCHRONIZATIONLOCK); + } + + public SynchronizationLockException(String message) + : base(message) { + SetErrorCode(__HResults.COR_E_SYNCHRONIZATIONLOCK); + } + + public SynchronizationLockException(String message, Exception innerException) + : base(message, innerException) { + SetErrorCode(__HResults.COR_E_SYNCHRONIZATIONLOCK); + } + + protected SynchronizationLockException(SerializationInfo info, StreamingContext context) : base (info, context) { + } + } + +} + + diff --git a/src/mscorlib/src/System/Threading/Tasks/AsyncCausalityTracer.cs b/src/mscorlib/src/System/Threading/Tasks/AsyncCausalityTracer.cs new file mode 100644 index 0000000000..c29b11a922 --- /dev/null +++ b/src/mscorlib/src/System/Threading/Tasks/AsyncCausalityTracer.cs @@ -0,0 +1,295 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +// +// + +using System; +using System.Security; +using System.Diagnostics; +using System.Diagnostics.Contracts; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +#if FEATURE_COMINTEROP +using System.Runtime.InteropServices.WindowsRuntime; + +using WFD = Windows.Foundation.Diagnostics; +#endif + + +namespace System.Threading.Tasks +{ + + [FriendAccessAllowed] + internal enum CausalityTraceLevel + { +#if FEATURE_COMINTEROP + Required = WFD.CausalityTraceLevel.Required, + Important = WFD.CausalityTraceLevel.Important, + Verbose = WFD.CausalityTraceLevel.Verbose +#else + Required, + Important, + Verbose +#endif + } + + [FriendAccessAllowed] + internal enum AsyncCausalityStatus + { +#if FEATURE_COMINTEROP + Canceled = WFD.AsyncCausalityStatus.Canceled, + Completed = WFD.AsyncCausalityStatus.Completed, + Error = WFD.AsyncCausalityStatus.Error, + Started = WFD.AsyncCausalityStatus.Started +#else + Started, + Completed, + Canceled, + Error +#endif + } + + internal enum CausalityRelation + { +#if FEATURE_COMINTEROP + AssignDelegate = WFD.CausalityRelation.AssignDelegate, + Join = WFD.CausalityRelation.Join, + Choice = WFD.CausalityRelation.Choice, + Cancel = WFD.CausalityRelation.Cancel, + Error = WFD.CausalityRelation.Error +#else + AssignDelegate, + Join, + Choice, + Cancel, + Error +#endif + } + + internal enum CausalitySynchronousWork + { +#if FEATURE_COMINTEROP + CompletionNotification = WFD.CausalitySynchronousWork.CompletionNotification, + ProgressNotification = WFD.CausalitySynchronousWork.ProgressNotification, + Execution = WFD.CausalitySynchronousWork.Execution +#else + CompletionNotification, + ProgressNotification, + Execution +#endif + } + + [FriendAccessAllowed] + internal static class AsyncCausalityTracer + { + static internal void EnableToETW(bool enabled) + { +#if FEATURE_COMINTEROP + if (enabled) + f_LoggingOn |= Loggers.ETW; + else + f_LoggingOn &= ~Loggers.ETW; +#endif + } + + [FriendAccessAllowed] + internal static bool LoggingOn + { + [FriendAccessAllowed] + get + { +#if FEATURE_COMINTEROP + return f_LoggingOn != 0; +#else + return false; +#endif + } + } + +#if FEATURE_COMINTEROP + //s_PlatformId = {4B0171A6-F3D0-41A0-9B33-02550652B995} + private static readonly Guid s_PlatformId = new Guid(0x4B0171A6, 0xF3D0, 0x41A0, 0x9B, 0x33, 0x02, 0x55, 0x06, 0x52, 0xB9, 0x95); + + //Indicates this information comes from the BCL Library + private const WFD.CausalitySource s_CausalitySource = WFD.CausalitySource.Library; + + //Lazy initialize the actual factory + private static WFD.IAsyncCausalityTracerStatics s_TracerFactory; + + // The loggers that this Tracer knows about. + [Flags] + private enum Loggers : byte { + CausalityTracer = 1, + ETW = 2 + } + + + //We receive the actual value for these as a callback + private static Loggers f_LoggingOn; //assumes false by default + + // The precise static constructor will run first time somebody attempts to access this class + [SecuritySafeCritical] + static AsyncCausalityTracer() + { + if (!Environment.IsWinRTSupported) return; + + //COM Class Id + string ClassId = "Windows.Foundation.Diagnostics.AsyncCausalityTracer"; + + //COM Interface GUID {50850B26-267E-451B-A890-AB6A370245EE} + Guid guid = new Guid(0x50850B26, 0x267E, 0x451B, 0xA8, 0x90, 0XAB, 0x6A, 0x37, 0x02, 0x45, 0xEE); + + Object factory = null; + + try + { + int hresult = Microsoft.Win32.UnsafeNativeMethods.RoGetActivationFactory(ClassId, ref guid, out factory); + + if (hresult < 0 || factory == null) return; //This prevents having an exception thrown in case IAsyncCausalityTracerStatics isn't registered. + + s_TracerFactory = (WFD.IAsyncCausalityTracerStatics)factory; + + EventRegistrationToken token = s_TracerFactory.add_TracingStatusChanged(new EventHandler<WFD.TracingStatusChangedEventArgs>(TracingStatusChangedHandler)); + Contract.Assert(token != default(EventRegistrationToken), "EventRegistrationToken is null"); + } + catch (Exception ex) + { + // Although catching generic Exception is not recommended, this file is one exception + // since we don't want to propagate any kind of exception to the user since all we are + // doing here depends on internal state. + LogAndDisable(ex); + } + + } + + [SecuritySafeCritical] + private static void TracingStatusChangedHandler(Object sender, WFD.TracingStatusChangedEventArgs args) + { + if (args.Enabled) + f_LoggingOn |= Loggers.CausalityTracer; + else + f_LoggingOn &= ~Loggers.CausalityTracer; + } +#endif + + // + // The TraceXXX methods should be called only if LoggingOn property returned true + // + + [FriendAccessAllowed] + [MethodImplAttribute(MethodImplOptions.NoInlining)] // Tracking is slow path. Disable inlining for it. + internal static void TraceOperationCreation(CausalityTraceLevel traceLevel, int taskId, string operationName, ulong relatedContext) + { +#if FEATURE_COMINTEROP + try + { + if ((f_LoggingOn & Loggers.ETW) != 0) + TplEtwProvider.Log.TraceOperationBegin(taskId, operationName, (long) relatedContext); + if ((f_LoggingOn & Loggers.CausalityTracer) != 0) + s_TracerFactory.TraceOperationCreation((WFD.CausalityTraceLevel)traceLevel, s_CausalitySource, s_PlatformId, GetOperationId((uint)taskId), operationName, relatedContext); + } + catch(Exception ex) + { + //view function comment + LogAndDisable(ex); + } +#endif + } + + [FriendAccessAllowed] + [MethodImplAttribute(MethodImplOptions.NoInlining)] + internal static void TraceOperationCompletion(CausalityTraceLevel traceLevel, int taskId, AsyncCausalityStatus status) + { +#if FEATURE_COMINTEROP + try + { + if ((f_LoggingOn & Loggers.ETW) != 0) + TplEtwProvider.Log.TraceOperationEnd(taskId, status); + if ((f_LoggingOn & Loggers.CausalityTracer) != 0) + s_TracerFactory.TraceOperationCompletion((WFD.CausalityTraceLevel)traceLevel, s_CausalitySource, s_PlatformId, GetOperationId((uint)taskId), (WFD.AsyncCausalityStatus)status); + } + catch(Exception ex) + { + //view function comment + LogAndDisable(ex); + } +#endif + } + + [MethodImplAttribute(MethodImplOptions.NoInlining)] + internal static void TraceOperationRelation(CausalityTraceLevel traceLevel, int taskId, CausalityRelation relation) + { +#if FEATURE_COMINTEROP + try + { + if ((f_LoggingOn & Loggers.ETW) != 0) + TplEtwProvider.Log.TraceOperationRelation(taskId, relation); + if ((f_LoggingOn & Loggers.CausalityTracer) != 0) + s_TracerFactory.TraceOperationRelation((WFD.CausalityTraceLevel)traceLevel, s_CausalitySource, s_PlatformId, GetOperationId((uint)taskId), (WFD.CausalityRelation)relation); + } + catch(Exception ex) + { + //view function comment + LogAndDisable(ex); + } +#endif + } + + [MethodImplAttribute(MethodImplOptions.NoInlining)] + internal static void TraceSynchronousWorkStart(CausalityTraceLevel traceLevel, int taskId, CausalitySynchronousWork work) + { +#if FEATURE_COMINTEROP + try + { + if ((f_LoggingOn & Loggers.ETW) != 0) + TplEtwProvider.Log.TraceSynchronousWorkBegin(taskId, work); + if ((f_LoggingOn & Loggers.CausalityTracer) != 0) + s_TracerFactory.TraceSynchronousWorkStart((WFD.CausalityTraceLevel)traceLevel, s_CausalitySource, s_PlatformId, GetOperationId((uint)taskId), (WFD.CausalitySynchronousWork)work); + } + catch(Exception ex) + { + //view function comment + LogAndDisable(ex); + } +#endif + } + + [MethodImplAttribute(MethodImplOptions.NoInlining)] + internal static void TraceSynchronousWorkCompletion(CausalityTraceLevel traceLevel, CausalitySynchronousWork work) + { +#if FEATURE_COMINTEROP + try + { + if ((f_LoggingOn & Loggers.ETW) != 0) + TplEtwProvider.Log.TraceSynchronousWorkEnd(work); + if ((f_LoggingOn & Loggers.CausalityTracer) != 0) + s_TracerFactory.TraceSynchronousWorkCompletion((WFD.CausalityTraceLevel)traceLevel, s_CausalitySource, (WFD.CausalitySynchronousWork)work); + } + catch(Exception ex) + { + //view function comment + LogAndDisable(ex); + } +#endif + } + +#if FEATURE_COMINTEROP + //fix for 796185: leaking internal exceptions to customers, + //we should catch and log exceptions but never propagate them. + private static void LogAndDisable(Exception ex) + { + f_LoggingOn = 0; + Debugger.Log(0, "AsyncCausalityTracer", ex.ToString()); + } +#endif + + private static ulong GetOperationId(uint taskId) + { + return (((ulong)AppDomain.CurrentDomain.Id) << 32) + taskId; + } + + } +} diff --git a/src/mscorlib/src/System/Threading/Tasks/BeginEndAwaitableAdapter.cs b/src/mscorlib/src/System/Threading/Tasks/BeginEndAwaitableAdapter.cs new file mode 100644 index 0000000000..05e6dbf1a9 --- /dev/null +++ b/src/mscorlib/src/System/Threading/Tasks/BeginEndAwaitableAdapter.cs @@ -0,0 +1,157 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Diagnostics.Contracts; +using System.IO; +using System.Runtime.CompilerServices; +using System.Security; +using System.Threading; +using System.Threading.Tasks; + +namespace System.Threading.Tasks { + +/// <summary> +/// Provides an adapter to make Begin/End pairs awaitable. +/// In general, Task.Factory.FromAsync should be used for this purpose. +/// However, for cases where absolute minimal overhead is required, this type +/// may be used to making APM pairs awaitable while minimizing overhead. +/// (APM = Asynchronous Programming Model or the Begin/End pattern.) +/// </summary> +/// <remarks> +/// This instance may be reused repeatedly. However, it must only be used +/// by a single APM invocation at a time. It's state will automatically be reset +/// when the await completes. +/// </remarks> +/// <example> +/// Usage sample: +/// <code> +/// static async Task CopyStreamAsync(Stream source, Stream dest) { +/// +/// BeginEndAwaitableAdapter adapter = new BeginEndAwaitableAdapter(); +/// Byte[] buffer = new Byte[0x1000]; +/// +/// while (true) { +/// +/// source.BeginRead(buffer, 0, buffer.Length, BeginEndAwaitableAdapter.Callback, adapter); +/// Int32 numRead = source.EndRead(await adapter); +/// if (numRead == 0) +/// break; +/// +/// dest.BeginWrite(buffer, 0, numRead, BeginEndAwaitableAdapter.Callback, adapter); +/// dest.EndWrite(await adapter); +/// } +/// } +/// </code> +/// </example> +internal sealed class BeginEndAwaitableAdapter : ICriticalNotifyCompletion { + + /// <summary>A sentinel marker used to communicate between OnCompleted and the APM callback + /// that the callback has already run, and thus OnCompleted needs to execute the callback.</summary> + private readonly static Action CALLBACK_RAN = () => { }; + + /// <summary>The IAsyncResult for the APM operation.</summary> + private IAsyncResult _asyncResult; + + /// <summary>The continuation delegate provided to the awaiter.</summary> + private Action _continuation; + + + /// <summary>A callback to be passed as the AsyncCallback to an APM pair. + /// It expects that an BeginEndAwaitableAdapter instance was supplied to the APM Begin method as the object state.</summary> + public readonly static AsyncCallback Callback = (asyncResult) => { + + Contract.Assert(asyncResult != null); + Contract.Assert(asyncResult.IsCompleted); + Contract.Assert(asyncResult.AsyncState is BeginEndAwaitableAdapter); + + // Get the adapter object supplied as the "object state" to the Begin method + BeginEndAwaitableAdapter adapter = (BeginEndAwaitableAdapter) asyncResult.AsyncState; + + // Store the IAsyncResult into it so that it's available to the awaiter + adapter._asyncResult = asyncResult; + + // If the _continuation has already been set to the actual continuation by OnCompleted, then invoke the continuation. + // Set _continuation to the CALLBACK_RAN sentinel so that IsCompleted returns true and OnCompleted sees the sentinel + // and knows to invoke the callback. + // Due to some known incorrect implementations of IAsyncResult in the Framework where CompletedSynchronously is lazily + // set to true if it is first invoked after IsCompleted is true, we cannot rely here on CompletedSynchronously for + // synchronization between the caller and the callback, and thus do not use CompletedSynchronously at all. + Action continuation = Interlocked.Exchange(ref adapter._continuation, CALLBACK_RAN); + if (continuation != null) { + + Contract.Assert(continuation != CALLBACK_RAN); + continuation(); + } + }; + + + /// <summary>Gets an awaiter.</summary> + /// <returns>Returns itself as the awaiter.</returns> + public BeginEndAwaitableAdapter GetAwaiter() { + + return this; + } + + + /// <summary>Gets whether the awaited APM operation completed.</summary> + public bool IsCompleted { + get { + + // We are completed if the callback was called and it set the continuation to the CALLBACK_RAN sentinel. + // If the operation completes asynchronously, there's still a chance we'll see CALLBACK_RAN here, in which + // case we're still good to keep running synchronously. + return (_continuation == CALLBACK_RAN); + } + } + + /// <summary>Schedules the continuation to run when the operation completes.</summary> + /// <param name="continuation">The continuation.</param> + [SecurityCritical] + public void UnsafeOnCompleted(Action continuation) { + + Contract.Assert(continuation != null); + OnCompleted(continuation); + } + + + /// <summary>Schedules the continuation to run when the operation completes.</summary> + /// <param name="continuation">The continuation.</param> + public void OnCompleted(Action continuation) { + + Contract.Assert(continuation != null); + + // If the continuation field is null, then set it to be the target continuation + // so that when the operation completes, it'll invoke the continuation. If it's non-null, + // it was already set to the CALLBACK_RAN-sentinel by the Callback, in which case we hit a very rare race condition + // where the operation didn't complete synchronously but completed asynchronously between our + // calls to IsCompleted and OnCompleted... in that case, just schedule a task to run the continuation. + if (_continuation == CALLBACK_RAN + || Interlocked.CompareExchange(ref _continuation, continuation, null) == CALLBACK_RAN) { + + Task.Run(continuation); // must run async at this point, or else we'd risk stack diving + } + } + + + /// <summary>Gets the IAsyncResult for the APM operation after the operation completes, and then resets the adapter.</summary> + /// <returns>The IAsyncResult for the operation.</returns> + public IAsyncResult GetResult() { + + Contract.Assert(_asyncResult != null && _asyncResult.IsCompleted); + + // Get the IAsyncResult + IAsyncResult result = _asyncResult; + + // Reset the adapter + _asyncResult = null; + _continuation = null; + + // Return the result + return result; + } + +} // class BeginEndAwaitableAdapter + +} // namespace diff --git a/src/mscorlib/src/System/Threading/Tasks/ConcurrentExclusiveSchedulerPair.cs b/src/mscorlib/src/System/Threading/Tasks/ConcurrentExclusiveSchedulerPair.cs new file mode 100644 index 0000000000..cb4a22bb2b --- /dev/null +++ b/src/mscorlib/src/System/Threading/Tasks/ConcurrentExclusiveSchedulerPair.cs @@ -0,0 +1,795 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +// =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ +// +// +// +// A pair of schedulers that together support concurrent (reader) / exclusive (writer) +// task scheduling. Using just the exclusive scheduler can be used to simulate a serial +// processing queue, and using just the concurrent scheduler with a specified +// MaximumConcurrentlyLevel can be used to achieve a MaxDegreeOfParallelism across +// a bunch of tasks, parallel loops, dataflow blocks, etc. +// +// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Diagnostics.Contracts; +using System.Security; +using System.Security.Permissions; + +namespace System.Threading.Tasks +{ + /// <summary> + /// Provides concurrent and exclusive task schedulers that coordinate to execute + /// tasks while ensuring that concurrent tasks may run concurrently and exclusive tasks never do. + /// </summary> + [HostProtection(Synchronization = true, ExternalThreading = true)] + [DebuggerDisplay("Concurrent={ConcurrentTaskCountForDebugger}, Exclusive={ExclusiveTaskCountForDebugger}, Mode={ModeForDebugger}")] + [DebuggerTypeProxy(typeof(ConcurrentExclusiveSchedulerPair.DebugView))] + public class ConcurrentExclusiveSchedulerPair + { + /// <summary>A dictionary mapping thread ID to a processing mode to denote what kinds of tasks are currently being processed on this thread.</summary> + private readonly ConcurrentDictionary<int, ProcessingMode> m_threadProcessingMapping = new ConcurrentDictionary<int, ProcessingMode>(); + /// <summary>The scheduler used to queue and execute "concurrent" tasks that may run concurrently with other concurrent tasks.</summary> + private readonly ConcurrentExclusiveTaskScheduler m_concurrentTaskScheduler; + /// <summary>The scheduler used to queue and execute "exclusive" tasks that must run exclusively while no other tasks for this pair are running.</summary> + private readonly ConcurrentExclusiveTaskScheduler m_exclusiveTaskScheduler; + /// <summary>The underlying task scheduler to which all work should be scheduled.</summary> + private readonly TaskScheduler m_underlyingTaskScheduler; + /// <summary> + /// The maximum number of tasks allowed to run concurrently. This only applies to concurrent tasks, + /// since exlusive tasks are inherently limited to 1. + /// </summary> + private readonly int m_maxConcurrencyLevel; + /// <summary>The maximum number of tasks we can process before recyling our runner tasks.</summary> + private readonly int m_maxItemsPerTask; + /// <summary> + /// If positive, it represents the number of concurrently running concurrent tasks. + /// If negative, it means an exclusive task has been scheduled. + /// If 0, nothing has been scheduled. + /// </summary> + private int m_processingCount; + /// <summary>Completion state for a task representing the completion of this pair.</summary> + /// <remarks>Lazily-initialized only if the scheduler pair is shutting down or if the Completion is requested.</remarks> + private CompletionState m_completionState; + + /// <summary>A constant value used to signal unlimited processing.</summary> + private const int UNLIMITED_PROCESSING = -1; + /// <summary>Constant used for m_processingCount to indicate that an exclusive task is being processed.</summary> + private const int EXCLUSIVE_PROCESSING_SENTINEL = -1; + /// <summary>Default MaxItemsPerTask to use for processing if none is specified.</summary> + private const int DEFAULT_MAXITEMSPERTASK = UNLIMITED_PROCESSING; + /// <summary>Default MaxConcurrencyLevel is the processor count if not otherwise specified.</summary> + private static Int32 DefaultMaxConcurrencyLevel { get { return Environment.ProcessorCount; } } + + /// <summary>Gets the sync obj used to protect all state on this instance.</summary> + private object ValueLock { get { return m_threadProcessingMapping; } } + + /// <summary> + /// Initializes the ConcurrentExclusiveSchedulerPair. + /// </summary> + public ConcurrentExclusiveSchedulerPair() : + this(TaskScheduler.Default, DefaultMaxConcurrencyLevel, DEFAULT_MAXITEMSPERTASK) { } + + /// <summary> + /// Initializes the ConcurrentExclusiveSchedulerPair to target the specified scheduler. + /// </summary> + /// <param name="taskScheduler">The target scheduler on which this pair should execute.</param> + public ConcurrentExclusiveSchedulerPair(TaskScheduler taskScheduler) : + this(taskScheduler, DefaultMaxConcurrencyLevel, DEFAULT_MAXITEMSPERTASK) { } + + /// <summary> + /// Initializes the ConcurrentExclusiveSchedulerPair to target the specified scheduler with a maximum concurrency level. + /// </summary> + /// <param name="taskScheduler">The target scheduler on which this pair should execute.</param> + /// <param name="maxConcurrencyLevel">The maximum number of tasks to run concurrently.</param> + public ConcurrentExclusiveSchedulerPair(TaskScheduler taskScheduler, int maxConcurrencyLevel) : + this(taskScheduler, maxConcurrencyLevel, DEFAULT_MAXITEMSPERTASK) { } + + /// <summary> + /// Initializes the ConcurrentExclusiveSchedulerPair to target the specified scheduler with a maximum + /// concurrency level and a maximum number of scheduled tasks that may be processed as a unit. + /// </summary> + /// <param name="taskScheduler">The target scheduler on which this pair should execute.</param> + /// <param name="maxConcurrencyLevel">The maximum number of tasks to run concurrently.</param> + /// <param name="maxItemsPerTask">The maximum number of tasks to process for each underlying scheduled task used by the pair.</param> + public ConcurrentExclusiveSchedulerPair(TaskScheduler taskScheduler, int maxConcurrencyLevel, int maxItemsPerTask) + { + // Validate arguments + if (taskScheduler == null) throw new ArgumentNullException("taskScheduler"); + if (maxConcurrencyLevel == 0 || maxConcurrencyLevel < -1) throw new ArgumentOutOfRangeException("maxConcurrencyLevel"); + if (maxItemsPerTask == 0 || maxItemsPerTask < -1) throw new ArgumentOutOfRangeException("maxItemsPerTask"); + Contract.EndContractBlock(); + + // Store configuration + m_underlyingTaskScheduler = taskScheduler; + m_maxConcurrencyLevel = maxConcurrencyLevel; + m_maxItemsPerTask = maxItemsPerTask; + + // Downgrade to the underlying scheduler's max degree of parallelism if it's lower than the user-supplied level + int mcl = taskScheduler.MaximumConcurrencyLevel; + if (mcl > 0 && mcl < m_maxConcurrencyLevel) m_maxConcurrencyLevel = mcl; + + // Treat UNLIMITED_PROCESSING/-1 for both MCL and MIPT as the biggest possible value so that we don't + // have to special case UNLIMITED_PROCESSING later on in processing. + if (m_maxConcurrencyLevel == UNLIMITED_PROCESSING) m_maxConcurrencyLevel = Int32.MaxValue; + if (m_maxItemsPerTask == UNLIMITED_PROCESSING) m_maxItemsPerTask = Int32.MaxValue; + + // Create the concurrent/exclusive schedulers for this pair + m_exclusiveTaskScheduler = new ConcurrentExclusiveTaskScheduler(this, 1, ProcessingMode.ProcessingExclusiveTask); + m_concurrentTaskScheduler = new ConcurrentExclusiveTaskScheduler(this, m_maxConcurrencyLevel, ProcessingMode.ProcessingConcurrentTasks); + } + + /// <summary>Informs the scheduler pair that it should not accept any more tasks.</summary> + /// <remarks> + /// Calling <see cref="Complete"/> is optional, and it's only necessary if the <see cref="Completion"/> + /// will be relied on for notification of all processing being completed. + /// </remarks> + public void Complete() + { + lock (ValueLock) + { + if (!CompletionRequested) + { + RequestCompletion(); + CleanupStateIfCompletingAndQuiesced(); + } + } + } + + /// <summary>Gets a <see cref="System.Threading.Tasks.Task"/> that will complete when the scheduler has completed processing.</summary> + public Task Completion + { + // ValueLock not needed, but it's ok if it's held + get { return EnsureCompletionStateInitialized().Task; } + } + + /// <summary>Gets the lazily-initialized completion state.</summary> + private CompletionState EnsureCompletionStateInitialized() + { + // ValueLock not needed, but it's ok if it's held + return LazyInitializer.EnsureInitialized(ref m_completionState, () => new CompletionState()); + } + + /// <summary>Gets whether completion has been requested.</summary> + private bool CompletionRequested + { + // ValueLock not needed, but it's ok if it's held + get { return m_completionState != null && Volatile.Read(ref m_completionState.m_completionRequested); } + } + + /// <summary>Sets that completion has been requested.</summary> + private void RequestCompletion() + { + ContractAssertMonitorStatus(ValueLock, held: true); + EnsureCompletionStateInitialized().m_completionRequested = true; + } + + /// <summary> + /// Cleans up state if and only if there's no processing currently happening + /// and no more to be done later. + /// </summary> + private void CleanupStateIfCompletingAndQuiesced() + { + ContractAssertMonitorStatus(ValueLock, held: true); + if (ReadyToComplete) CompleteTaskAsync(); + } + + /// <summary>Gets whether the pair is ready to complete.</summary> + private bool ReadyToComplete + { + get + { + ContractAssertMonitorStatus(ValueLock, held: true); + + // We can only complete if completion has been requested and no processing is currently happening. + if (!CompletionRequested || m_processingCount != 0) return false; + + // Now, only allow shutdown if an exception occurred or if there are no more tasks to process. + var cs = EnsureCompletionStateInitialized(); + return + (cs.m_exceptions != null && cs.m_exceptions.Count > 0) || + (m_concurrentTaskScheduler.m_tasks.IsEmpty && m_exclusiveTaskScheduler.m_tasks.IsEmpty); + } + } + + /// <summary>Completes the completion task asynchronously.</summary> + private void CompleteTaskAsync() + { + Contract.Requires(ReadyToComplete, "The block must be ready to complete to be here."); + ContractAssertMonitorStatus(ValueLock, held: true); + + // Ensure we only try to complete once, then schedule completion + // in order to escape held locks and the caller's context + var cs = EnsureCompletionStateInitialized(); + if (!cs.m_completionQueued) + { + cs.m_completionQueued = true; + ThreadPool.QueueUserWorkItem(state => + { + var localCs = (CompletionState)state; // don't use 'cs', as it'll force a closure + Contract.Assert(!localCs.Task.IsCompleted, "Completion should only happen once."); + + var exceptions = localCs.m_exceptions; + bool success = (exceptions != null && exceptions.Count > 0) ? + localCs.TrySetException(exceptions) : + localCs.TrySetResult(default(VoidTaskResult)); + Contract.Assert(success, "Expected to complete completion task."); + }, cs); + } + } + + /// <summary>Initiatites scheduler shutdown due to a worker task faulting..</summary> + /// <param name="faultedTask">The faulted worker task that's initiating the shutdown.</param> + private void FaultWithTask(Task faultedTask) + { + Contract.Requires(faultedTask != null && faultedTask.IsFaulted && faultedTask.Exception.InnerExceptions.Count > 0, + "Needs a task in the faulted state and thus with exceptions."); + ContractAssertMonitorStatus(ValueLock, held: true); + + // Store the faulted task's exceptions + var cs = EnsureCompletionStateInitialized(); + if (cs.m_exceptions == null) cs.m_exceptions = new List<Exception>(); + cs.m_exceptions.AddRange(faultedTask.Exception.InnerExceptions); + + // Now that we're doomed, request completion + RequestCompletion(); + } + + /// <summary> + /// Gets a TaskScheduler that can be used to schedule tasks to this pair + /// that may run concurrently with other tasks on this pair. + /// </summary> + public TaskScheduler ConcurrentScheduler { get { return m_concurrentTaskScheduler; } } + /// <summary> + /// Gets a TaskScheduler that can be used to schedule tasks to this pair + /// that must run exclusively with regards to other tasks on this pair. + /// </summary> + public TaskScheduler ExclusiveScheduler { get { return m_exclusiveTaskScheduler; } } + + /// <summary>Gets the number of tasks waiting to run concurrently.</summary> + /// <remarks>This does not take the necessary lock, as it's only called from under the debugger.</remarks> + [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + private int ConcurrentTaskCountForDebugger { get { return m_concurrentTaskScheduler.m_tasks.Count; } } + + /// <summary>Gets the number of tasks waiting to run exclusively.</summary> + /// <remarks>This does not take the necessary lock, as it's only called from under the debugger.</remarks> + [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + private int ExclusiveTaskCountForDebugger { get { return m_exclusiveTaskScheduler.m_tasks.Count; } } + + /// <summary>Notifies the pair that new work has arrived to be processed.</summary> + /// <param name="fairly">Whether tasks should be scheduled fairly with regards to other tasks.</param> + /// <remarks>Must only be called while holding the lock.</remarks> + [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")] + [SuppressMessage("Microsoft.Performance", "CA1804:RemoveUnusedLocals")] + [SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope")] + private void ProcessAsyncIfNecessary(bool fairly = false) + { + ContractAssertMonitorStatus(ValueLock, held: true); + + // If the current processing count is >= 0, we can potentially launch further processing. + if (m_processingCount >= 0) + { + // We snap whether there are any exclusive tasks or concurrent tasks waiting. + // (We grab the concurrent count below only once we know we need it.) + // With processing happening concurrent to this operation, this data may + // immediately be out of date, but it can only go from non-empty + // to empty and not the other way around. As such, this is safe, + // as worst case is we'll schedule an extra task when we didn't + // otherwise need to, and we'll just eat its overhead. + bool exclusiveTasksAreWaiting = !m_exclusiveTaskScheduler.m_tasks.IsEmpty; + + // If there's no processing currently happening but there are waiting exclusive tasks, + // let's start processing those exclusive tasks. + Task processingTask = null; + if (m_processingCount == 0 && exclusiveTasksAreWaiting) + { + // Launch exclusive task processing + m_processingCount = EXCLUSIVE_PROCESSING_SENTINEL; // -1 + try + { + processingTask = new Task(thisPair => ((ConcurrentExclusiveSchedulerPair)thisPair).ProcessExclusiveTasks(), this, + default(CancellationToken), GetCreationOptionsForTask(fairly)); + processingTask.Start(m_underlyingTaskScheduler); + // When we call Start, if the underlying scheduler throws in QueueTask, TPL will fault the task and rethrow + // the exception. To deal with that, we need a reference to the task object, so that we can observe its exception. + // Hence, we separate creation and starting, so that we can store a reference to the task before we attempt QueueTask. + } + catch + { + m_processingCount = 0; + FaultWithTask(processingTask); + } + } + // If there are no waiting exclusive tasks, there are concurrent tasks, and we haven't reached our maximum + // concurrency level for processing, let's start processing more concurrent tasks. + else + { + int concurrentTasksWaitingCount = m_concurrentTaskScheduler.m_tasks.Count; + + if (concurrentTasksWaitingCount > 0 && !exclusiveTasksAreWaiting && m_processingCount < m_maxConcurrencyLevel) + { + // Launch concurrent task processing, up to the allowed limit + for (int i = 0; i < concurrentTasksWaitingCount && m_processingCount < m_maxConcurrencyLevel; ++i) + { + ++m_processingCount; + try + { + processingTask = new Task(thisPair => ((ConcurrentExclusiveSchedulerPair)thisPair).ProcessConcurrentTasks(), this, + default(CancellationToken), GetCreationOptionsForTask(fairly)); + processingTask.Start(m_underlyingTaskScheduler); // See above logic for why we use new + Start rather than StartNew + } + catch + { + --m_processingCount; + FaultWithTask(processingTask); + } + } + } + } + + // Check to see if all tasks have completed and if completion has been requested. + CleanupStateIfCompletingAndQuiesced(); + } + else Contract.Assert(m_processingCount == EXCLUSIVE_PROCESSING_SENTINEL, "The processing count must be the sentinel if it's not >= 0."); + } + + /// <summary> + /// Processes exclusive tasks serially until either there are no more to process + /// or we've reached our user-specified maximum limit. + /// </summary> + private void ProcessExclusiveTasks() + { + Contract.Requires(m_processingCount == EXCLUSIVE_PROCESSING_SENTINEL, "Processing exclusive tasks requires being in exclusive mode."); + Contract.Requires(!m_exclusiveTaskScheduler.m_tasks.IsEmpty, "Processing exclusive tasks requires tasks to be processed."); + ContractAssertMonitorStatus(ValueLock, held: false); + try + { + // Note that we're processing exclusive tasks on the current thread + Contract.Assert(!m_threadProcessingMapping.ContainsKey(Thread.CurrentThread.ManagedThreadId), + "This thread should not yet be involved in this pair's processing."); + m_threadProcessingMapping[Thread.CurrentThread.ManagedThreadId] = ProcessingMode.ProcessingExclusiveTask; + + // Process up to the maximum number of items per task allowed + for (int i = 0; i < m_maxItemsPerTask; i++) + { + // Get the next available exclusive task. If we can't find one, bail. + Task exclusiveTask; + if (!m_exclusiveTaskScheduler.m_tasks.TryDequeue(out exclusiveTask)) break; + + // Execute the task. If the scheduler was previously faulted, + // this task could have been faulted when it was queued; ignore such tasks. + if (!exclusiveTask.IsFaulted) m_exclusiveTaskScheduler.ExecuteTask(exclusiveTask); + } + } + finally + { + // We're no longer processing exclusive tasks on the current thread + ProcessingMode currentMode; + m_threadProcessingMapping.TryRemove(Thread.CurrentThread.ManagedThreadId, out currentMode); + Contract.Assert(currentMode == ProcessingMode.ProcessingExclusiveTask, + "Somehow we ended up escaping exclusive mode."); + + lock (ValueLock) + { + // When this task was launched, we tracked it by setting m_processingCount to WRITER_IN_PROGRESS. + // now reset it to 0. Then check to see whether there's more processing to be done. + // There might be more concurrent tasks available, for example, if concurrent tasks arrived + // after we exited the loop, or if we exited the loop while concurrent tasks were still + // available but we hit our maxItemsPerTask limit. + Contract.Assert(m_processingCount == EXCLUSIVE_PROCESSING_SENTINEL, "The processing mode should not have deviated from exclusive."); + m_processingCount = 0; + ProcessAsyncIfNecessary(true); + } + } + } + + /// <summary> + /// Processes concurrent tasks serially until either there are no more to process, + /// we've reached our user-specified maximum limit, or exclusive tasks have arrived. + /// </summary> + private void ProcessConcurrentTasks() + { + Contract.Requires(m_processingCount > 0, "Processing concurrent tasks requires us to be in concurrent mode."); + ContractAssertMonitorStatus(ValueLock, held: false); + try + { + // Note that we're processing concurrent tasks on the current thread + Contract.Assert(!m_threadProcessingMapping.ContainsKey(Thread.CurrentThread.ManagedThreadId), + "This thread should not yet be involved in this pair's processing."); + m_threadProcessingMapping[Thread.CurrentThread.ManagedThreadId] = ProcessingMode.ProcessingConcurrentTasks; + + // Process up to the maximum number of items per task allowed + for (int i = 0; i < m_maxItemsPerTask; i++) + { + // Get the next available concurrent task. If we can't find one, bail. + Task concurrentTask; + if (!m_concurrentTaskScheduler.m_tasks.TryDequeue(out concurrentTask)) break; + + // Execute the task. If the scheduler was previously faulted, + // this task could have been faulted when it was queued; ignore such tasks. + if (!concurrentTask.IsFaulted) m_concurrentTaskScheduler.ExecuteTask(concurrentTask); + + // Now check to see if exclusive tasks have arrived; if any have, they take priority + // so we'll bail out here. Note that we could have checked this condition + // in the for loop's condition, but that could lead to extra overhead + // in the case where a concurrent task arrives, this task is launched, and then + // before entering the loop an exclusive task arrives. If we didn't execute at + // least one task, we would have spent all of the overhead to launch a + // task but with none of the benefit. There's of course also an inherent + // race condition here with regards to exclusive tasks arriving, and we're ok with + // executing one more concurrent task than we should before giving priority to exclusive tasks. + if (!m_exclusiveTaskScheduler.m_tasks.IsEmpty) break; + } + } + finally + { + // We're no longer processing concurrent tasks on the current thread + ProcessingMode currentMode; + m_threadProcessingMapping.TryRemove(Thread.CurrentThread.ManagedThreadId, out currentMode); + Contract.Assert(currentMode == ProcessingMode.ProcessingConcurrentTasks, + "Somehow we ended up escaping concurrent mode."); + + lock (ValueLock) + { + // When this task was launched, we tracked it with a positive processing count; + // decrement that count. Then check to see whether there's more processing to be done. + // There might be more concurrent tasks available, for example, if concurrent tasks arrived + // after we exited the loop, or if we exited the loop while concurrent tasks were still + // available but we hit our maxItemsPerTask limit. + Contract.Assert(m_processingCount > 0, "The procesing mode should not have deviated from concurrent."); + if (m_processingCount > 0) --m_processingCount; + ProcessAsyncIfNecessary(true); + } + } + } + +#if PRENET45 + /// <summary> + /// Type used with TaskCompletionSource(Of TResult) as the TResult + /// to ensure that the resulting task can't be upcast to something + /// that in the future could lead to compat problems. + /// </summary> + [SuppressMessage("Microsoft.Performance", "CA1812:AvoidUninstantiatedInternalClasses")] + [DebuggerNonUserCode] + private struct VoidTaskResult { } +#endif + + /// <summary> + /// Holder for lazily-initialized state about the completion of a scheduler pair. + /// Completion is only triggered either by rare exceptional conditions or by + /// the user calling Complete, and as such we only lazily initialize this + /// state in one of those conditions or if the user explicitly asks for + /// the Completion. + /// </summary> + [SuppressMessage("Microsoft.Performance", "CA1812:AvoidUninstantiatedInternalClasses")] + private sealed class CompletionState : TaskCompletionSource<VoidTaskResult> + { + /// <summary>Whether the scheduler has had completion requested.</summary> + /// <remarks>This variable is not volatile, so to gurantee safe reading reads, Volatile.Read is used in TryExecuteTaskInline.</remarks> + internal bool m_completionRequested; + /// <summary>Whether completion processing has been queued.</summary> + internal bool m_completionQueued; + /// <summary>Unrecoverable exceptions incurred while processing.</summary> + internal List<Exception> m_exceptions; + } + + /// <summary> + /// A scheduler shim used to queue tasks to the pair and execute those tasks on request of the pair. + /// </summary> + [DebuggerDisplay("Count={CountForDebugger}, MaxConcurrencyLevel={m_maxConcurrencyLevel}, Id={Id}")] + [DebuggerTypeProxy(typeof(ConcurrentExclusiveTaskScheduler.DebugView))] + private sealed class ConcurrentExclusiveTaskScheduler : TaskScheduler + { + /// <summary>Cached delegate for invoking TryExecuteTaskShim.</summary> + private static readonly Func<object, bool> s_tryExecuteTaskShim = new Func<object, bool>(TryExecuteTaskShim); + /// <summary>The parent pair.</summary> + private readonly ConcurrentExclusiveSchedulerPair m_pair; + /// <summary>The maximum concurrency level for the scheduler.</summary> + private readonly int m_maxConcurrencyLevel; + /// <summary>The processing mode of this scheduler, exclusive or concurrent.</summary> + private readonly ProcessingMode m_processingMode; + /// <summary>Gets the queue of tasks for this scheduler.</summary> + internal readonly IProducerConsumerQueue<Task> m_tasks; + + /// <summary>Initializes the scheduler.</summary> + /// <param name="pair">The parent pair.</param> + /// <param name="maxConcurrencyLevel">The maximum degree of concurrency this scheduler may use.</param> + /// <param name="processingMode">The processing mode of this scheduler.</param> + internal ConcurrentExclusiveTaskScheduler(ConcurrentExclusiveSchedulerPair pair, int maxConcurrencyLevel, ProcessingMode processingMode) + { + Contract.Requires(pair != null, "Scheduler must be associated with a valid pair."); + Contract.Requires(processingMode == ProcessingMode.ProcessingConcurrentTasks || processingMode == ProcessingMode.ProcessingExclusiveTask, + "Scheduler must be for concurrent or exclusive processing."); + Contract.Requires( + (processingMode == ProcessingMode.ProcessingConcurrentTasks && (maxConcurrencyLevel >= 1 || maxConcurrencyLevel == UNLIMITED_PROCESSING)) || + (processingMode == ProcessingMode.ProcessingExclusiveTask && maxConcurrencyLevel == 1), + "If we're in concurrent mode, our concurrency level should be positive or unlimited. If exclusive, it should be 1."); + + m_pair = pair; + m_maxConcurrencyLevel = maxConcurrencyLevel; + m_processingMode = processingMode; + m_tasks = (processingMode == ProcessingMode.ProcessingExclusiveTask) ? + (IProducerConsumerQueue<Task>)new SingleProducerSingleConsumerQueue<Task>() : + (IProducerConsumerQueue<Task>)new MultiProducerMultiConsumerQueue<Task>(); + } + + /// <summary>Gets the maximum concurrency level this scheduler is able to support.</summary> + public override int MaximumConcurrencyLevel { get { return m_maxConcurrencyLevel; } } + + /// <summary>Queues a task to the scheduler.</summary> + /// <param name="task">The task to be queued.</param> + [SecurityCritical] + protected internal override void QueueTask(Task task) + { + Contract.Assert(task != null, "Infrastructure should have provided a non-null task."); + lock (m_pair.ValueLock) + { + // If the scheduler has already had completion requested, no new work is allowed to be scheduled + if (m_pair.CompletionRequested) throw new InvalidOperationException(GetType().Name); + + // Queue the task, and then let the pair know that more work is now available to be scheduled + m_tasks.Enqueue(task); + m_pair.ProcessAsyncIfNecessary(); + } + } + + /// <summary>Executes a task on this scheduler.</summary> + /// <param name="task">The task to be executed.</param> + [SecuritySafeCritical] + internal void ExecuteTask(Task task) + { + Contract.Assert(task != null, "Infrastructure should have provided a non-null task."); + base.TryExecuteTask(task); + } + + /// <summary>Tries to execute the task synchronously on this scheduler.</summary> + /// <param name="task">The task to execute.</param> + /// <param name="taskWasPreviouslyQueued">Whether the task was previously queued to the scheduler.</param> + /// <returns>true if the task could be executed; otherwise, false.</returns> + [SecurityCritical] + protected override bool TryExecuteTaskInline(Task task, bool taskWasPreviouslyQueued) + { + Contract.Assert(task != null, "Infrastructure should have provided a non-null task."); + + // If the scheduler has had completion requested, no new work is allowed to be scheduled. + // A non-locked read on m_completionRequested (in CompletionRequested) is acceptable here because: + // a) we don't need to be exact... a Complete call could come in later in the function anyway + // b) this is only a fast path escape hatch. To actually inline the task, + // we need to be inside of an already executing task, and in such a case, + // while completion may have been requested, we can't have shutdown yet. + if (!taskWasPreviouslyQueued && m_pair.CompletionRequested) return false; + + // We know the implementation of the default scheduler and how it will behave. + // As it's the most common underlying scheduler, we optimize for it. + bool isDefaultScheduler = m_pair.m_underlyingTaskScheduler == TaskScheduler.Default; + + // If we're targeting the default scheduler and taskWasPreviouslyQueued is true, + // we know that the default scheduler will only allow it to be inlined + // if we're on a thread pool thread (but it won't always allow it in that case, + // since it'll only allow inlining if it can find the task in the local queue). + // As such, if we're not on a thread pool thread, we know for sure the + // task won't be inlined, so let's not even try. + if (isDefaultScheduler && taskWasPreviouslyQueued && !Thread.CurrentThread.IsThreadPoolThread) + { + return false; + } + else + { + // If a task is already running on this thread, allow inline execution to proceed. + // If there's already a task from this scheduler running on the current thread, we know it's safe + // to run this task, in effect temporarily taking that task's count allocation. + ProcessingMode currentThreadMode; + if (m_pair.m_threadProcessingMapping.TryGetValue(Thread.CurrentThread.ManagedThreadId, out currentThreadMode) && + currentThreadMode == m_processingMode) + { + // If we're targeting the default scheduler and taskWasPreviouslyQueued is false, + // we know the default scheduler will allow it, so we can just execute it here. + // Otherwise, delegate to the target scheduler's inlining. + return (isDefaultScheduler && !taskWasPreviouslyQueued) ? + TryExecuteTask(task) : + TryExecuteTaskInlineOnTargetScheduler(task); + } + } + + // We're not in the context of a task already executing on this scheduler. Bail. + return false; + } + + /// <summary> + /// Implements a reasonable approximation for TryExecuteTaskInline on the underlying scheduler, + /// which we can't call directly on the underlying scheduler. + /// </summary> + /// <param name="task">The task to execute inline if possible.</param> + /// <returns>true if the task was inlined successfully; otherwise, false.</returns> + [SuppressMessage("Microsoft.Performance", "CA1804:RemoveUnusedLocals", MessageId = "ignored")] + private bool TryExecuteTaskInlineOnTargetScheduler(Task task) + { + // We'd like to simply call TryExecuteTaskInline here, but we can't. + // As there's no built-in API for this, a workaround is to create a new task that, + // when executed, will simply call TryExecuteTask to run the real task, and then + // we run our new shim task synchronously on the target scheduler. If all goes well, + // our synchronous invocation will succeed in running the shim task on the current thread, + // which will in turn run the real task on the current thread. If the scheduler + // doesn't allow that execution, RunSynchronously will block until the underlying scheduler + // is able to invoke the task, which might account for an additional but unavoidable delay. + // Once it's done, we can return whether the task executed by returning the + // shim task's Result, which is in turn the result of TryExecuteTask. + var t = new Task<bool>(s_tryExecuteTaskShim, Tuple.Create(this, task)); + try + { + t.RunSynchronously(m_pair.m_underlyingTaskScheduler); + return t.Result; + } + catch + { + Contract.Assert(t.IsFaulted, "Task should be faulted due to the scheduler faulting it and throwing the exception."); + var ignored = t.Exception; + throw; + } + finally { t.Dispose(); } + } + + /// <summary>Shim used to invoke this.TryExecuteTask(task).</summary> + /// <param name="state">A tuple of the ConcurrentExclusiveTaskScheduler and the task to execute.</param> + /// <returns>true if the task was successfully inlined; otherwise, false.</returns> + /// <remarks> + /// This method is separated out not because of performance reasons but so that + /// the SecuritySafeCritical attribute may be employed. + /// </remarks> + [SecuritySafeCritical] + private static bool TryExecuteTaskShim(object state) + { + var tuple = (Tuple<ConcurrentExclusiveTaskScheduler, Task>)state; + return tuple.Item1.TryExecuteTask(tuple.Item2); + } + + /// <summary>Gets for debugging purposes the tasks scheduled to this scheduler.</summary> + /// <returns>An enumerable of the tasks queued.</returns> + [SecurityCritical] + protected override IEnumerable<Task> GetScheduledTasks() { return m_tasks; } + + /// <summary>Gets the number of tasks queued to this scheduler.</summary> + [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + private int CountForDebugger { get { return m_tasks.Count; } } + + /// <summary>Provides a debug view for ConcurrentExclusiveTaskScheduler.</summary> + private sealed class DebugView + { + /// <summary>The scheduler being debugged.</summary> + private readonly ConcurrentExclusiveTaskScheduler m_taskScheduler; + + /// <summary>Initializes the debug view.</summary> + /// <param name="scheduler">The scheduler being debugged.</param> + public DebugView(ConcurrentExclusiveTaskScheduler scheduler) + { + Contract.Requires(scheduler != null, "Need a scheduler with which to construct the debug view."); + m_taskScheduler = scheduler; + } + + /// <summary>Gets this pair's maximum allowed concurrency level.</summary> + public int MaximumConcurrencyLevel { get { return m_taskScheduler.m_maxConcurrencyLevel; } } + /// <summary>Gets the tasks scheduled to this scheduler.</summary> + public IEnumerable<Task> ScheduledTasks { get { return m_taskScheduler.m_tasks; } } + /// <summary>Gets the scheduler pair with which this scheduler is associated.</summary> + public ConcurrentExclusiveSchedulerPair SchedulerPair { get { return m_taskScheduler.m_pair; } } + } + } + + /// <summary>Provides a debug view for ConcurrentExclusiveSchedulerPair.</summary> + private sealed class DebugView + { + /// <summary>The pair being debugged.</summary> + private readonly ConcurrentExclusiveSchedulerPair m_pair; + + /// <summary>Initializes the debug view.</summary> + /// <param name="pair">The pair being debugged.</param> + public DebugView(ConcurrentExclusiveSchedulerPair pair) + { + Contract.Requires(pair != null, "Need a pair with which to construct the debug view."); + m_pair = pair; + } + + /// <summary>Gets a representation of the execution state of the pair.</summary> + public ProcessingMode Mode { get { return m_pair.ModeForDebugger; } } + /// <summary>Gets the number of tasks waiting to run exclusively.</summary> + public IEnumerable<Task> ScheduledExclusive { get { return m_pair.m_exclusiveTaskScheduler.m_tasks; } } + /// <summary>Gets the number of tasks waiting to run concurrently.</summary> + public IEnumerable<Task> ScheduledConcurrent { get { return m_pair.m_concurrentTaskScheduler.m_tasks; } } + /// <summary>Gets the number of tasks currently being executed.</summary> + public int CurrentlyExecutingTaskCount + { + get { return (m_pair.m_processingCount == EXCLUSIVE_PROCESSING_SENTINEL) ? 1 : m_pair.m_processingCount; } + } + /// <summary>Gets the underlying task scheduler that actually executes the tasks.</summary> + public TaskScheduler TargetScheduler { get { return m_pair.m_underlyingTaskScheduler; } } + } + + /// <summary>Gets an enumeration for debugging that represents the current state of the scheduler pair.</summary> + /// <remarks>This is only for debugging. It does not take the necessary locks to be useful for runtime usage.</remarks> + private ProcessingMode ModeForDebugger + { + get + { + // If our completion task is done, so are we. + if (m_completionState != null && m_completionState.Task.IsCompleted) return ProcessingMode.Completed; + + // Otherwise, summarize our current state. + var mode = ProcessingMode.NotCurrentlyProcessing; + if (m_processingCount == EXCLUSIVE_PROCESSING_SENTINEL) mode |= ProcessingMode.ProcessingExclusiveTask; + if (m_processingCount >= 1) mode |= ProcessingMode.ProcessingConcurrentTasks; + if (CompletionRequested) mode |= ProcessingMode.Completing; + return mode; + } + } + + /// <summary>Asserts that a given synchronization object is either held or not held.</summary> + /// <param name="syncObj">The monitor to check.</param> + /// <param name="held">Whether we want to assert that it's currently held or not held.</param> + [Conditional("DEBUG")] + internal static void ContractAssertMonitorStatus(object syncObj, bool held) + { + Contract.Requires(syncObj != null, "The monitor object to check must be provided."); +#if PRENET45 +#if DEBUG + // This check is expensive, + // which is why it's protected by ShouldCheckMonitorStatus and controlled by an environment variable DEBUGSYNC. + if (ShouldCheckMonitorStatus) + { + bool exceptionThrown; + try + { + Monitor.Pulse(syncObj); // throws a SynchronizationLockException if the monitor isn't held by this thread + exceptionThrown = false; + } + catch (SynchronizationLockException) { exceptionThrown = true; } + Contract.Assert(held == !exceptionThrown, "The locking scheme was not correctly followed."); + } +#endif +#else + Contract.Assert(Monitor.IsEntered(syncObj) == held, "The locking scheme was not correctly followed."); +#endif + } + + /// <summary>Gets the options to use for tasks.</summary> + /// <param name="isReplacementReplica">If this task is being created to replace another.</param> + /// <remarks> + /// These options should be used for all tasks that have the potential to run user code or + /// that are repeatedly spawned and thus need a modicum of fair treatment. + /// </remarks> + /// <returns>The options to use.</returns> + internal static TaskCreationOptions GetCreationOptionsForTask(bool isReplacementReplica = false) + { + TaskCreationOptions options = +#if PRENET45 + TaskCreationOptions.None; +#else + TaskCreationOptions.DenyChildAttach; +#endif + if (isReplacementReplica) options |= TaskCreationOptions.PreferFairness; + return options; + } + + /// <summary>Provides an enumeration that represents the current state of the scheduler pair.</summary> + [Flags] + private enum ProcessingMode : byte + { + /// <summary>The scheduler pair is currently dormant, with no work scheduled.</summary> + NotCurrentlyProcessing = 0x0, + /// <summary>The scheduler pair has queued processing for exclusive tasks.</summary> + ProcessingExclusiveTask = 0x1, + /// <summary>The scheduler pair has queued processing for concurrent tasks.</summary> + ProcessingConcurrentTasks = 0x2, + /// <summary>Completion has been requested.</summary> + Completing = 0x4, + /// <summary>The scheduler pair is finished processing.</summary> + Completed = 0x8 + } + } + +} diff --git a/src/mscorlib/src/System/Threading/Tasks/FutureFactory.cs b/src/mscorlib/src/System/Threading/Tasks/FutureFactory.cs new file mode 100644 index 0000000000..b1f634c707 --- /dev/null +++ b/src/mscorlib/src/System/Threading/Tasks/FutureFactory.cs @@ -0,0 +1,2303 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +// =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ +// +// +// +// As with TaskFactory, TaskFactory<TResult> encodes common factory patterns into helper methods. +// +// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + +using System; +using System.Security; +using System.Security.Permissions; +using System.Runtime.CompilerServices; +using System.Threading; +using System.Diagnostics.Contracts; +using System.Runtime.Versioning; + +namespace System.Threading.Tasks +{ + /// <summary> + /// Provides support for creating and scheduling + /// <see cref="T:System.Threading.Tasks.Task{TResult}">Task{TResult}</see> objects. + /// </summary> + /// <typeparam name="TResult">The type of the results that are available though + /// the <see cref="T:System.Threading.Tasks.Task{TResult}">Task{TResult}</see> objects that are associated with + /// the methods in this class.</typeparam> + /// <remarks> + /// <para> + /// There are many common patterns for which tasks are relevant. The <see cref="TaskFactory{TResult}"/> + /// class encodes some of these patterns into methods that pick up default settings, which are + /// configurable through its constructors. + /// </para> + /// <para> + /// A default instance of <see cref="TaskFactory{TResult}"/> is available through the + /// <see cref="System.Threading.Tasks.Task{TResult}.Factory">Task{TResult}.Factory</see> property. + /// </para> + /// </remarks> + [HostProtection(Synchronization = true, ExternalThreading = true)] + public class TaskFactory<TResult> + { + // Member variables, DefaultScheduler, other properties and ctors + // copied right out of TaskFactory... Lots of duplication here... + // Should we be thinking about a TaskFactoryBase class? + + // member variables + private CancellationToken m_defaultCancellationToken; + private TaskScheduler m_defaultScheduler; + private TaskCreationOptions m_defaultCreationOptions; + private TaskContinuationOptions m_defaultContinuationOptions; + + private TaskScheduler DefaultScheduler + { + get + { + if (m_defaultScheduler == null) return TaskScheduler.Current; + else return m_defaultScheduler; + } + } + + // sister method to above property -- avoids a TLS lookup + private TaskScheduler GetDefaultScheduler(Task currTask) + { + if (m_defaultScheduler != null) return m_defaultScheduler; + else if ((currTask != null) + && ((currTask.CreationOptions & TaskCreationOptions.HideScheduler) == 0) + ) + return currTask.ExecutingTaskScheduler; + else return TaskScheduler.Default; + } + + /* Constructors */ + + /// <summary> + /// Initializes a <see cref="TaskFactory{TResult}"/> instance with the default configuration. + /// </summary> + /// <remarks> + /// This constructor creates a <see cref="TaskFactory{TResult}"/> instance with a default configuration. The + /// <see cref="TaskCreationOptions"/> property is initialized to + /// <see cref="System.Threading.Tasks.TaskCreationOptions.None">TaskCreationOptions.None</see>, the + /// <see cref="TaskContinuationOptions"/> property is initialized to <see + /// cref="System.Threading.Tasks.TaskContinuationOptions.None">TaskContinuationOptions.None</see>, + /// and the <see cref="System.Threading.Tasks.TaskScheduler">TaskScheduler</see> property is + /// initialized to the current scheduler (see <see + /// cref="System.Threading.Tasks.TaskScheduler.Current">TaskScheduler.Current</see>). + /// </remarks> + public TaskFactory() + : this(default(CancellationToken), TaskCreationOptions.None, TaskContinuationOptions.None, null) + { + } + + /// <summary> + /// Initializes a <see cref="TaskFactory{TResult}"/> instance with the default configuration. + /// </summary> + /// <param name="cancellationToken">The default <see cref="CancellationToken"/> that will be assigned + /// to tasks created by this <see cref="TaskFactory"/> unless another CancellationToken is explicitly specified + /// while calling the factory methods.</param> + /// <remarks> + /// This constructor creates a <see cref="TaskFactory{TResult}"/> instance with a default configuration. The + /// <see cref="TaskCreationOptions"/> property is initialized to + /// <see cref="System.Threading.Tasks.TaskCreationOptions.None">TaskCreationOptions.None</see>, the + /// <see cref="TaskContinuationOptions"/> property is initialized to <see + /// cref="System.Threading.Tasks.TaskContinuationOptions.None">TaskContinuationOptions.None</see>, + /// and the <see cref="System.Threading.Tasks.TaskScheduler">TaskScheduler</see> property is + /// initialized to the current scheduler (see <see + /// cref="System.Threading.Tasks.TaskScheduler.Current">TaskScheduler.Current</see>). + /// </remarks> + public TaskFactory(CancellationToken cancellationToken) + : this(cancellationToken, TaskCreationOptions.None, TaskContinuationOptions.None, null) + { + } + + /// <summary> + /// Initializes a <see cref="TaskFactory{TResult}"/> instance with the specified configuration. + /// </summary> + /// <param name="scheduler"> + /// The <see cref="System.Threading.Tasks.TaskScheduler"> + /// TaskScheduler</see> to use to schedule any tasks created with this TaskFactory{TResult}. A null value + /// indicates that the current TaskScheduler should be used. + /// </param> + /// <remarks> + /// With this constructor, the + /// <see cref="TaskCreationOptions"/> property is initialized to + /// <see cref="System.Threading.Tasks.TaskCreationOptions.None">TaskCreationOptions.None</see>, the + /// <see cref="TaskContinuationOptions"/> property is initialized to <see + /// cref="System.Threading.Tasks.TaskContinuationOptions.None">TaskContinuationOptions.None</see>, + /// and the <see cref="System.Threading.Tasks.TaskScheduler">TaskScheduler</see> property is + /// initialized to <paramref name="scheduler"/>, unless it's null, in which case the property is + /// initialized to the current scheduler (see <see + /// cref="System.Threading.Tasks.TaskScheduler.Current">TaskScheduler.Current</see>). + /// </remarks> + public TaskFactory(TaskScheduler scheduler) // null means to use TaskScheduler.Current + : this(default(CancellationToken), TaskCreationOptions.None, TaskContinuationOptions.None, scheduler) + { + } + + /// <summary> + /// Initializes a <see cref="TaskFactory{TResult}"/> instance with the specified configuration. + /// </summary> + /// <param name="creationOptions"> + /// The default <see cref="System.Threading.Tasks.TaskCreationOptions"> + /// TaskCreationOptions</see> to use when creating tasks with this TaskFactory{TResult}. + /// </param> + /// <param name="continuationOptions"> + /// The default <see cref="System.Threading.Tasks.TaskContinuationOptions"> + /// TaskContinuationOptions</see> to use when creating continuation tasks with this TaskFactory{TResult}. + /// </param> + /// <exception cref="T:System.ArgumentOutOfRangeException"> + /// The exception that is thrown when the + /// <paramref name="creationOptions"/> argument or the <paramref name="continuationOptions"/> + /// argument specifies an invalid value. + /// </exception> + /// <remarks> + /// With this constructor, the + /// <see cref="TaskCreationOptions"/> property is initialized to <paramref name="creationOptions"/>, + /// the + /// <see cref="TaskContinuationOptions"/> property is initialized to <paramref + /// name="continuationOptions"/>, and the <see + /// cref="System.Threading.Tasks.TaskScheduler">TaskScheduler</see> property is initialized to the + /// current scheduler (see <see + /// cref="System.Threading.Tasks.TaskScheduler.Current">TaskScheduler.Current</see>). + /// </remarks> + public TaskFactory(TaskCreationOptions creationOptions, TaskContinuationOptions continuationOptions) + : this(default(CancellationToken), creationOptions, continuationOptions, null) + { + } + + /// <summary> + /// Initializes a <see cref="TaskFactory{TResult}"/> instance with the specified configuration. + /// </summary> + /// <param name="cancellationToken">The default <see cref="CancellationToken"/> that will be assigned + /// to tasks created by this <see cref="TaskFactory"/> unless another CancellationToken is explicitly specified + /// while calling the factory methods.</param> + /// <param name="creationOptions"> + /// The default <see cref="System.Threading.Tasks.TaskCreationOptions"> + /// TaskCreationOptions</see> to use when creating tasks with this TaskFactory{TResult}. + /// </param> + /// <param name="continuationOptions"> + /// The default <see cref="System.Threading.Tasks.TaskContinuationOptions"> + /// TaskContinuationOptions</see> to use when creating continuation tasks with this TaskFactory{TResult}. + /// </param> + /// <param name="scheduler"> + /// The default <see cref="System.Threading.Tasks.TaskScheduler"> + /// TaskScheduler</see> to use to schedule any Tasks created with this TaskFactory{TResult}. A null value + /// indicates that TaskScheduler.Current should be used. + /// </param> + /// <exception cref="T:System.ArgumentOutOfRangeException"> + /// The exception that is thrown when the + /// <paramref name="creationOptions"/> argument or the <paramref name="continuationOptions"/> + /// argumentspecifies an invalid value. + /// </exception> + /// <remarks> + /// With this constructor, the + /// <see cref="TaskCreationOptions"/> property is initialized to <paramref name="creationOptions"/>, + /// the + /// <see cref="TaskContinuationOptions"/> property is initialized to <paramref + /// name="continuationOptions"/>, and the <see + /// cref="System.Threading.Tasks.TaskScheduler">TaskScheduler</see> property is initialized to + /// <paramref name="scheduler"/>, unless it's null, in which case the property is initialized to the + /// current scheduler (see <see + /// cref="System.Threading.Tasks.TaskScheduler.Current">TaskScheduler.Current</see>). + /// </remarks> + public TaskFactory(CancellationToken cancellationToken, TaskCreationOptions creationOptions, TaskContinuationOptions continuationOptions, TaskScheduler scheduler) + { + TaskFactory.CheckMultiTaskContinuationOptions(continuationOptions); + TaskFactory.CheckCreationOptions(creationOptions); + + m_defaultCancellationToken = cancellationToken; + m_defaultScheduler = scheduler; + m_defaultCreationOptions = creationOptions; + m_defaultContinuationOptions = continuationOptions; + } + + /* Properties */ + + /// <summary> + /// Gets the default <see cref="System.Threading.CancellationToken">CancellationToken</see> of this + /// TaskFactory. + /// </summary> + /// <remarks> + /// This property returns the default <see cref="CancellationToken"/> that will be assigned to all + /// tasks created by this factory unless another CancellationToken value is explicitly specified + /// during the call to the factory methods. + /// </remarks> + public CancellationToken CancellationToken { get { return m_defaultCancellationToken; } } + + /// <summary> + /// Gets the <see cref="System.Threading.Tasks.TaskScheduler">TaskScheduler</see> of this + /// TaskFactory{TResult}. + /// </summary> + /// <remarks> + /// This property returns the default scheduler for this factory. It will be used to schedule all + /// tasks unless another scheduler is explicitly specified during calls to this factory's methods. + /// If null, <see cref="System.Threading.Tasks.TaskScheduler.Current">TaskScheduler.Current</see> + /// will be used. + /// </remarks> + public TaskScheduler Scheduler { get { return m_defaultScheduler; } } + + /// <summary> + /// Gets the <see cref="System.Threading.Tasks.TaskCreationOptions">TaskCreationOptions + /// </see> value of this TaskFactory{TResult}. + /// </summary> + /// <remarks> + /// This property returns the default creation options for this factory. They will be used to create all + /// tasks unless other options are explicitly specified during calls to this factory's methods. + /// </remarks> + public TaskCreationOptions CreationOptions { get { return m_defaultCreationOptions; } } + + /// <summary> + /// Gets the <see cref="System.Threading.Tasks.TaskCreationOptions">TaskContinuationOptions + /// </see> value of this TaskFactory{TResult}. + /// </summary> + /// <remarks> + /// This property returns the default continuation options for this factory. They will be used to create + /// all continuation tasks unless other options are explicitly specified during calls to this factory's methods. + /// </remarks> + public TaskContinuationOptions ContinuationOptions { get { return m_defaultContinuationOptions; } } + + + /* StartNew */ + + /// <summary> + /// Creates and starts a <see cref="T:System.Threading.Tasks.Task{TResult}"/>. + /// </summary> + /// <param name="function">A function delegate that returns the future result to be available through + /// the <see cref="T:System.Threading.Tasks.Task{TResult}"/>.</param> + /// <returns>The started <see cref="T:System.Threading.Tasks.Task{TResult}"/>.</returns> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the <paramref + /// name="function"/> + /// argument is null.</exception> + /// <remarks> + /// Calling StartNew is functionally equivalent to creating a <see cref="Task{TResult}"/> using one + /// of its constructors and then calling + /// <see cref="System.Threading.Tasks.Task.Start()">Start</see> to schedule it for execution. + /// However, unless creation and scheduling must be separated, StartNew is the recommended approach + /// for both simplicity and performance. + /// </remarks> + [MethodImplAttribute(MethodImplOptions.NoInlining)] // Methods containing StackCrawlMark local var have to be marked non-inlineable + public Task<TResult> StartNew(Func<TResult> function) + { + StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller; + Task currTask = Task.InternalCurrent; + return Task<TResult>.StartNew(currTask, function, m_defaultCancellationToken, + m_defaultCreationOptions, InternalTaskOptions.None, GetDefaultScheduler(currTask), ref stackMark); + } + + /// <summary> + /// Creates and starts a <see cref="T:System.Threading.Tasks.Task{TResult}"/>. + /// </summary> + /// <param name="function">A function delegate that returns the future result to be available through + /// the <see cref="T:System.Threading.Tasks.Task{TResult}"/>.</param> + /// <param name="cancellationToken">The <see cref="CancellationToken"/> that will be assigned to the new task.</param> + /// <returns>The started <see cref="T:System.Threading.Tasks.Task{TResult}"/>.</returns> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the <paramref + /// name="function"/> + /// argument is null.</exception> + /// <exception cref="T:System.ObjectDisposedException">The provided <see cref="System.Threading.CancellationToken">CancellationToken</see> + /// has already been disposed. + /// </exception> + /// <remarks> + /// Calling StartNew is functionally equivalent to creating a <see cref="Task{TResult}"/> using one + /// of its constructors and then calling + /// <see cref="System.Threading.Tasks.Task.Start()">Start</see> to schedule it for execution. + /// However, unless creation and scheduling must be separated, StartNew is the recommended approach + /// for both simplicity and performance. + /// </remarks> + [MethodImplAttribute(MethodImplOptions.NoInlining)] // Methods containing StackCrawlMark local var have to be marked non-inlineable + public Task<TResult> StartNew(Func<TResult> function, CancellationToken cancellationToken) + { + StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller; + Task currTask = Task.InternalCurrent; + return Task<TResult>.StartNew(currTask, function, cancellationToken, + m_defaultCreationOptions, InternalTaskOptions.None, GetDefaultScheduler(currTask), ref stackMark); + } + + /// <summary> + /// Creates and starts a <see cref="T:System.Threading.Tasks.Task{TResult}"/>. + /// </summary> + /// <param name="function">A function delegate that returns the future result to be available through + /// the <see cref="T:System.Threading.Tasks.Task{TResult}"/>.</param> + /// <param name="creationOptions">A TaskCreationOptions value that controls the behavior of the + /// created + /// <see cref="T:System.Threading.Tasks.Task{TResult}"/>.</param> + /// <returns>The started <see cref="T:System.Threading.Tasks.Task{TResult}"/>.</returns> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the <paramref + /// name="function"/> + /// argument is null.</exception> + /// <exception cref="T:System.ArgumentOutOfRangeException">The exception that is thrown when the + /// <paramref name="creationOptions"/> argument specifies an invalid TaskCreationOptions + /// value.</exception> + /// <remarks> + /// Calling StartNew is functionally equivalent to creating a <see cref="Task{TResult}"/> using one + /// of its constructors and then calling + /// <see cref="System.Threading.Tasks.Task.Start()">Start</see> to schedule it for execution. + /// However, unless creation and scheduling must be separated, StartNew is the recommended approach + /// for both simplicity and performance. + /// </remarks> + [MethodImplAttribute(MethodImplOptions.NoInlining)] // Methods containing StackCrawlMark local var have to be marked non-inlineable + public Task<TResult> StartNew(Func<TResult> function, TaskCreationOptions creationOptions) + { + StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller; + Task currTask = Task.InternalCurrent; + return Task<TResult>.StartNew(currTask, function, m_defaultCancellationToken, + creationOptions, InternalTaskOptions.None, GetDefaultScheduler(currTask), ref stackMark); + } + + /// <summary> + /// Creates and starts a <see cref="T:System.Threading.Tasks.Task{TResult}"/>. + /// </summary> + /// <param name="function">A function delegate that returns the future result to be available through + /// the <see cref="T:System.Threading.Tasks.Task{TResult}"/>.</param> + /// <param name="creationOptions">A TaskCreationOptions value that controls the behavior of the + /// created + /// <see cref="T:System.Threading.Tasks.Task{TResult}"/>.</param> + /// <param name="cancellationToken">The <see cref="CancellationToken"/> that will be assigned to the new task.</param> + /// <param name="scheduler">The <see + /// cref="T:System.Threading.Tasks.TaskScheduler">TaskScheduler</see> + /// that is used to schedule the created <see cref="T:System.Threading.Tasks.Task{TResult}"> + /// Task{TResult}</see>.</param> + /// <returns>The started <see cref="T:System.Threading.Tasks.Task{TResult}"/>.</returns> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the <paramref + /// name="function"/> + /// argument is null.</exception> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the <paramref + /// name="scheduler"/> + /// argument is null.</exception> + /// <exception cref="T:System.ArgumentOutOfRangeException">The exception that is thrown when the + /// <paramref name="creationOptions"/> argument specifies an invalid TaskCreationOptions + /// value.</exception> + /// <exception cref="T:System.ObjectDisposedException">The provided <see cref="System.Threading.CancellationToken">CancellationToken</see> + /// has already been disposed. + /// </exception> + /// <remarks> + /// Calling StartNew is functionally equivalent to creating a <see cref="Task{TResult}"/> using one + /// of its constructors and then calling + /// <see cref="System.Threading.Tasks.Task.Start()">Start</see> to schedule it for execution. + /// However, unless creation and scheduling must be separated, StartNew is the recommended approach + /// for both simplicity and performance. + /// </remarks> + [MethodImplAttribute(MethodImplOptions.NoInlining)] // Methods containing StackCrawlMark local var have to be marked non-inlineable + public Task<TResult> StartNew(Func<TResult> function, CancellationToken cancellationToken, TaskCreationOptions creationOptions, TaskScheduler scheduler) + { + StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller; + return Task<TResult>.StartNew( + Task.InternalCurrentIfAttached(creationOptions), function, cancellationToken, + creationOptions, InternalTaskOptions.None, scheduler, ref stackMark); + } + + /// <summary> + /// Creates and starts a <see cref="T:System.Threading.Tasks.Task{TResult}"/>. + /// </summary> + /// <param name="function">A function delegate that returns the future result to be available through + /// the <see cref="T:System.Threading.Tasks.Task{TResult}"/>.</param> + /// <param name="state">An object containing data to be used by the <paramref name="function"/> + /// delegate.</param> + /// <returns>The started <see cref="T:System.Threading.Tasks.Task{TResult}"/>.</returns> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the <paramref + /// name="function"/> + /// argument is null.</exception> + /// <remarks> + /// Calling StartNew is functionally equivalent to creating a <see cref="Task{TResult}"/> using one + /// of its constructors and then calling + /// <see cref="System.Threading.Tasks.Task.Start()">Start</see> to schedule it for execution. + /// However, unless creation and scheduling must be separated, StartNew is the recommended approach + /// for both simplicity and performance. + /// </remarks> + [MethodImplAttribute(MethodImplOptions.NoInlining)] // Methods containing StackCrawlMark local var have to be marked non-inlineable + public Task<TResult> StartNew(Func<Object, TResult> function, Object state) + { + StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller; + Task currTask = Task.InternalCurrent; + return Task<TResult>.StartNew(currTask, function, state, m_defaultCancellationToken, + m_defaultCreationOptions, InternalTaskOptions.None, GetDefaultScheduler(currTask), ref stackMark); + } + + /// <summary> + /// Creates and starts a <see cref="T:System.Threading.Tasks.Task{TResult}"/>. + /// </summary> + /// <param name="function">A function delegate that returns the future result to be available through + /// the <see cref="T:System.Threading.Tasks.Task{TResult}"/>.</param> + /// <param name="state">An object containing data to be used by the <paramref name="function"/> + /// delegate.</param> + /// <param name="cancellationToken">The <see cref="CancellationToken"/> that will be assigned to the new task.</param> + /// <returns>The started <see cref="T:System.Threading.Tasks.Task{TResult}"/>.</returns> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the <paramref + /// name="function"/> + /// argument is null.</exception> + /// <exception cref="T:System.ObjectDisposedException">The provided <see cref="System.Threading.CancellationToken">CancellationToken</see> + /// has already been disposed. + /// </exception> + /// <remarks> + /// Calling StartNew is functionally equivalent to creating a <see cref="Task{TResult}"/> using one + /// of its constructors and then calling + /// <see cref="System.Threading.Tasks.Task.Start()">Start</see> to schedule it for execution. + /// However, unless creation and scheduling must be separated, StartNew is the recommended approach + /// for both simplicity and performance. + /// </remarks> + [MethodImplAttribute(MethodImplOptions.NoInlining)] // Methods containing StackCrawlMark local var have to be marked non-inlineable + public Task<TResult> StartNew(Func<Object, TResult> function, Object state, CancellationToken cancellationToken) + { + StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller; + Task currTask = Task.InternalCurrent; + return Task<TResult>.StartNew(currTask, function, state, cancellationToken, + m_defaultCreationOptions, InternalTaskOptions.None, GetDefaultScheduler(currTask), ref stackMark); + } + + /// <summary> + /// Creates and starts a <see cref="T:System.Threading.Tasks.Task{TResult}"/>. + /// </summary> + /// <param name="function">A function delegate that returns the future result to be available through + /// the <see cref="T:System.Threading.Tasks.Task{TResult}"/>.</param> + /// <param name="state">An object containing data to be used by the <paramref name="function"/> + /// delegate.</param> + /// <param name="creationOptions">A TaskCreationOptions value that controls the behavior of the + /// created + /// <see cref="T:System.Threading.Tasks.Task{TResult}"/>.</param> + /// <returns>The started <see cref="T:System.Threading.Tasks.Task{TResult}"/>.</returns> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the <paramref + /// name="function"/> + /// argument is null.</exception> + /// <exception cref="T:System.ArgumentOutOfRangeException">The exception that is thrown when the + /// <paramref name="creationOptions"/> argument specifies an invalid TaskCreationOptions + /// value.</exception> + /// <remarks> + /// Calling StartNew is functionally equivalent to creating a <see cref="Task{TResult}"/> using one + /// of its constructors and then calling + /// <see cref="System.Threading.Tasks.Task.Start()">Start</see> to schedule it for execution. + /// However, unless creation and scheduling must be separated, StartNew is the recommended approach + /// for both simplicity and performance. + /// </remarks> + [MethodImplAttribute(MethodImplOptions.NoInlining)] // Methods containing StackCrawlMark local var have to be marked non-inlineable + public Task<TResult> StartNew(Func<Object, TResult> function, Object state, TaskCreationOptions creationOptions) + { + StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller; + Task currTask = Task.InternalCurrent; + return Task<TResult>.StartNew(currTask, function, state, m_defaultCancellationToken, + creationOptions, InternalTaskOptions.None, GetDefaultScheduler(currTask), ref stackMark); + } + + /// <summary> + /// Creates and starts a <see cref="T:System.Threading.Tasks.Task{TResult}"/>. + /// </summary> + /// <param name="function">A function delegate that returns the future result to be available through + /// the <see cref="T:System.Threading.Tasks.Task{TResult}"/>.</param> + /// <param name="state">An object containing data to be used by the <paramref name="function"/> + /// delegate.</param> + /// <param name="cancellationToken">The <see cref="CancellationToken"/> that will be assigned to the new task.</param> + /// <param name="creationOptions">A TaskCreationOptions value that controls the behavior of the + /// created + /// <see cref="T:System.Threading.Tasks.Task{TResult}"/>.</param> + /// <param name="scheduler">The <see + /// cref="T:System.Threading.Tasks.TaskScheduler">TaskScheduler</see> + /// that is used to schedule the created <see cref="T:System.Threading.Tasks.Task{TResult}"> + /// Task{TResult}</see>.</param> + /// <returns>The started <see cref="T:System.Threading.Tasks.Task{TResult}"/>.</returns> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the <paramref + /// name="function"/> + /// argument is null.</exception> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the <paramref + /// name="scheduler"/> + /// argument is null.</exception> + /// <exception cref="T:System.ArgumentOutOfRangeException">The exception that is thrown when the + /// <paramref name="creationOptions"/> argument specifies an invalid TaskCreationOptions + /// value.</exception> + /// <exception cref="T:System.ObjectDisposedException">The provided <see cref="System.Threading.CancellationToken">CancellationToken</see> + /// has already been disposed. + /// </exception> + /// <remarks> + /// Calling StartNew is functionally equivalent to creating a <see cref="Task{TResult}"/> using one + /// of its constructors and then calling + /// <see cref="System.Threading.Tasks.Task.Start()">Start</see> to schedule it for execution. + /// However, unless creation and scheduling must be separated, StartNew is the recommended approach + /// for both simplicity and performance. + /// </remarks> + [MethodImplAttribute(MethodImplOptions.NoInlining)] // Methods containing StackCrawlMark local var have to be marked non-inlineable + public Task<TResult> StartNew(Func<Object, TResult> function, Object state, CancellationToken cancellationToken, TaskCreationOptions creationOptions, TaskScheduler scheduler) + { + StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller; + return Task<TResult>.StartNew(Task.InternalCurrentIfAttached(creationOptions), function, state, cancellationToken, + creationOptions, InternalTaskOptions.None, scheduler, ref stackMark); + } + + // + // APM Factory methods + // + + // Common core logic for FromAsync calls. This minimizes the chance of "drift" between overload implementations. + private static void FromAsyncCoreLogic( + IAsyncResult iar, + Func<IAsyncResult, TResult> endFunction, + Action<IAsyncResult> endAction, + Task<TResult> promise, + bool requiresSynchronization) + { + Contract.Requires((endFunction != null) != (endAction != null), "Expected exactly one of endFunction/endAction to be non-null"); + + Exception ex = null; + OperationCanceledException oce = null; + TResult result = default(TResult); + + try + { + if (endFunction != null) + { + result = endFunction(iar); + } + else + { + endAction(iar); + } + } + catch (OperationCanceledException _oce) { oce = _oce; } + catch (Exception e) { ex = e; } + finally + { + if (oce != null) + { + promise.TrySetCanceled(oce.CancellationToken, oce); + } + else if (ex != null) + { + bool bWonSetException = promise.TrySetException(ex); + if (bWonSetException && ex is ThreadAbortException) + { + promise.m_contingentProperties.m_exceptionsHolder.MarkAsHandled(false); + } + } + else + { + if (AsyncCausalityTracer.LoggingOn) + AsyncCausalityTracer.TraceOperationCompletion(CausalityTraceLevel.Required, promise.Id, AsyncCausalityStatus.Completed); + + if (Task.s_asyncDebuggingEnabled) + { + Task.RemoveFromActiveTasks(promise.Id); + } + if (requiresSynchronization) + { + promise.TrySetResult(result); + } + else + { + promise.DangerousSetResult(result); + } + } + + + } + } + + /// <summary> + /// Creates a <see cref="T:System.Threading.Tasks.Task{TResult}">Task</see> that executes an end + /// method function when a specified <see cref="T:System.IAsyncResult">IAsyncResult</see> completes. + /// </summary> + /// <param name="asyncResult">The IAsyncResult whose completion should trigger the processing of the + /// <paramref name="endMethod"/>.</param> + /// <param name="endMethod">The function delegate that processes the completed <paramref + /// name="asyncResult"/>.</param> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the + /// <paramref name="asyncResult"/> argument is null.</exception> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the + /// <paramref name="endMethod"/> argument is null.</exception> + /// <returns>A <see cref="T:System.Threading.Tasks.Task{TResult}">Task</see> that represents the + /// asynchronous operation.</returns> + [MethodImplAttribute(MethodImplOptions.NoInlining)] // Methods containing StackCrawlMark local var have to be marked non-inlineable + public Task<TResult> FromAsync(IAsyncResult asyncResult, Func<IAsyncResult, TResult> endMethod) + { + StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller; + return FromAsyncImpl(asyncResult, endMethod, null, m_defaultCreationOptions, DefaultScheduler, ref stackMark); + } + + /// <summary> + /// Creates a <see cref="T:System.Threading.Tasks.Task{TResult}">Task</see> that executes an end + /// method function when a specified <see cref="T:System.IAsyncResult">IAsyncResult</see> completes. + /// </summary> + /// <param name="asyncResult">The IAsyncResult whose completion should trigger the processing of the + /// <paramref name="endMethod"/>.</param> + /// <param name="endMethod">The function delegate that processes the completed <paramref + /// name="asyncResult"/>.</param> + /// <param name="creationOptions">The TaskCreationOptions value that controls the behavior of the + /// created <see cref="T:System.Threading.Tasks.Task{TResult}">Task</see>.</param> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the + /// <paramref name="asyncResult"/> argument is null.</exception> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the + /// <paramref name="endMethod"/> argument is null.</exception> + /// <exception cref="T:System.ArgumentOutOfRangeException">The exception that is thrown when the + /// <paramref name="creationOptions"/> argument specifies an invalid TaskCreationOptions + /// value.</exception> + /// <returns>A <see cref="T:System.Threading.Tasks.Task{TResult}">Task</see> that represents the + /// asynchronous operation.</returns> + [MethodImplAttribute(MethodImplOptions.NoInlining)] // Methods containing StackCrawlMark local var have to be marked non-inlineable + public Task<TResult> FromAsync( + IAsyncResult asyncResult, + Func<IAsyncResult, TResult> endMethod, + TaskCreationOptions creationOptions) + { + StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller; + return FromAsyncImpl(asyncResult, endMethod, null, creationOptions, DefaultScheduler, ref stackMark); + } + + + + /// <summary> + /// Creates a <see cref="T:System.Threading.Tasks.Task{TResult}">Task</see> that executes an end + /// method function when a specified <see cref="T:System.IAsyncResult">IAsyncResult</see> completes. + /// </summary> + /// <param name="asyncResult">The IAsyncResult whose completion should trigger the processing of the + /// <paramref name="endMethod"/>.</param> + /// <param name="endMethod">The function delegate that processes the completed <paramref + /// name="asyncResult"/>.</param> + /// <param name="scheduler">The <see cref="System.Threading.Tasks.TaskScheduler">TaskScheduler</see> + /// that is used to schedule the task that executes the end method.</param> + /// <param name="creationOptions">The TaskCreationOptions value that controls the behavior of the + /// created <see cref="T:System.Threading.Tasks.Task{TResult}">Task</see>.</param> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the + /// <paramref name="asyncResult"/> argument is null.</exception> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the + /// <paramref name="endMethod"/> argument is null.</exception> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the + /// <paramref name="scheduler"/> argument is null.</exception> + /// <exception cref="T:System.ArgumentOutOfRangeException">The exception that is thrown when the + /// <paramref name="creationOptions"/> argument specifies an invalid TaskCreationOptions + /// value.</exception> + /// <returns>A <see cref="T:System.Threading.Tasks.Task{TResult}">Task</see> that represents the + /// asynchronous operation.</returns> + [MethodImplAttribute(MethodImplOptions.NoInlining)] // Methods containing StackCrawlMark local var have to be marked non-inlineable + public Task<TResult> FromAsync( + IAsyncResult asyncResult, + Func<IAsyncResult, TResult> endMethod, + TaskCreationOptions creationOptions, + TaskScheduler scheduler) + { + StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller; + return FromAsyncImpl(asyncResult, endMethod, null, creationOptions, scheduler, ref stackMark); + } + + // internal overload that supports StackCrawlMark + // We also need this logic broken out into a static method so that the similar TaskFactory.FromAsync() + // method can access the logic w/o declaring a TaskFactory<TResult> instance. + internal static Task<TResult> FromAsyncImpl( + IAsyncResult asyncResult, + Func<IAsyncResult, TResult> endFunction, + Action<IAsyncResult> endAction, + TaskCreationOptions creationOptions, + TaskScheduler scheduler, + ref StackCrawlMark stackMark) + { + if (asyncResult == null) + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.asyncResult); + + if (endFunction == null && endAction == null) + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.endMethod); + + Contract.Requires((endFunction != null) != (endAction != null), "Both endFunction and endAction were non-null"); + + if (scheduler == null) + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.scheduler); + Contract.EndContractBlock(); + + TaskFactory.CheckFromAsyncOptions(creationOptions, false); + + Task<TResult> promise = new Task<TResult>((object)null, creationOptions); + + if (AsyncCausalityTracer.LoggingOn) + AsyncCausalityTracer.TraceOperationCreation(CausalityTraceLevel.Required, promise.Id, "TaskFactory.FromAsync", 0); + + if (Task.s_asyncDebuggingEnabled) + { + Task.AddToActiveTasks(promise); + } + + // Just specify this task as detached. No matter what happens, we want endMethod + // to be called -- even if the parent is canceled. So we don't want to flow + // RespectParentCancellation. + Task t = new Task(delegate + { + FromAsyncCoreLogic(asyncResult, endFunction, endAction, promise, requiresSynchronization:true); + }, + (object)null, null, + default(CancellationToken), TaskCreationOptions.None, InternalTaskOptions.None, null, ref stackMark); + + if (AsyncCausalityTracer.LoggingOn) + AsyncCausalityTracer.TraceOperationCreation(CausalityTraceLevel.Verbose, t.Id, "TaskFactory.FromAsync Callback", 0); + + if (Task.s_asyncDebuggingEnabled) + { + Task.AddToActiveTasks(t); + } + + if (asyncResult.IsCompleted) + { + try { t.InternalRunSynchronously(scheduler, waitForCompletion:false); } + catch (Exception e) { promise.TrySetException(e); } // catch and log any scheduler exceptions + } + else + { + ThreadPool.RegisterWaitForSingleObject( + asyncResult.AsyncWaitHandle, + delegate + { + try { t.InternalRunSynchronously(scheduler, waitForCompletion: false); } + catch (Exception e) { promise.TrySetException(e); } // catch and log any scheduler exceptions + }, + null, + Timeout.Infinite, + true); + } + + return promise; + } + + /// <summary> + /// Creates a <see cref="T:System.Threading.Tasks.Task{TResult}">Task</see> that represents a pair of + /// begin and end methods that conform to the Asynchronous Programming Model pattern. + /// </summary> + /// <param name="beginMethod">The delegate that begins the asynchronous operation.</param> + /// <param name="endMethod">The delegate that ends the asynchronous operation.</param> + /// <param name="state">An object containing data to be used by the <paramref name="beginMethod"/> + /// delegate.</param> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the + /// <paramref name="beginMethod"/> argument is null.</exception> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the + /// <paramref name="endMethod"/> argument is null.</exception> + /// <returns>The created <see cref="T:System.Threading.Tasks.Task{TResult}">Task</see> that + /// represents the asynchronous operation.</returns> + /// <remarks> + /// This method throws any exceptions thrown by the <paramref name="beginMethod"/>. + /// </remarks> + public Task<TResult> FromAsync( + Func<AsyncCallback, object, IAsyncResult> beginMethod, + Func<IAsyncResult, TResult> endMethod, object state) + { + return FromAsyncImpl(beginMethod, endMethod, null, state, m_defaultCreationOptions); + } + + /// <summary> + /// Creates a <see cref="T:System.Threading.Tasks.Task{TResult}">Task</see> that represents a pair of + /// begin and end methods that conform to the Asynchronous Programming Model pattern. + /// </summary> + /// <param name="beginMethod">The delegate that begins the asynchronous operation.</param> + /// <param name="endMethod">The delegate that ends the asynchronous operation.</param> + /// <param name="creationOptions">The TaskCreationOptions value that controls the behavior of the + /// created <see cref="T:System.Threading.Tasks.Task{TResult}">Task</see>.</param> + /// <param name="state">An object containing data to be used by the <paramref name="beginMethod"/> + /// delegate.</param> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the + /// <paramref name="beginMethod"/> argument is null.</exception> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the + /// <paramref name="endMethod"/> argument is null.</exception> + /// <exception cref="T:System.ArgumentOutOfRangeException">The exception that is thrown when the + /// <paramref name="creationOptions"/> argument specifies an invalid TaskCreationOptions + /// value.</exception> + /// <returns>The created <see cref="T:System.Threading.Tasks.Task{TResult}">Task</see> that + /// represents the asynchronous operation.</returns> + /// <remarks> + /// This method throws any exceptions thrown by the <paramref name="beginMethod"/>. + /// </remarks> + public Task<TResult> FromAsync( + Func<AsyncCallback, object, IAsyncResult> beginMethod, + Func<IAsyncResult, TResult> endMethod, object state, TaskCreationOptions creationOptions) + { + return FromAsyncImpl(beginMethod, endMethod, null, state, creationOptions); + } + + // We need this logic broken out into a static method so that the similar TaskFactory.FromAsync() + // method can access the logic w/o declaring a TaskFactory<TResult> instance. + internal static Task<TResult> FromAsyncImpl(Func<AsyncCallback, object, IAsyncResult> beginMethod, + Func<IAsyncResult, TResult> endFunction, Action<IAsyncResult> endAction, + object state, TaskCreationOptions creationOptions) + { + if (beginMethod == null) + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.beginMethod); + + if (endFunction == null && endAction == null) + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.endMethod); + + Contract.Requires((endFunction != null) != (endAction != null), "Both endFunction and endAction were non-null"); + + TaskFactory.CheckFromAsyncOptions(creationOptions, true); + + Task<TResult> promise = new Task<TResult>(state, creationOptions); + + if (AsyncCausalityTracer.LoggingOn) + AsyncCausalityTracer.TraceOperationCreation(CausalityTraceLevel.Required, promise.Id, "TaskFactory.FromAsync: " + beginMethod.Method.Name, 0); + + if (Task.s_asyncDebuggingEnabled) + { + Task.AddToActiveTasks(promise); + } + + try + { + // Do NOT change the code below. + // 4.5 relies on the fact that IAsyncResult CompletedSynchronously flag needs to be set correctly, + // sadly this has not been the case that is why the behaviour from 4.5 broke 4.0 buggy apps. Any other + // change will likely brake 4.5 behavior so if possible never touch this code again. + if (BinaryCompatibility.TargetsAtLeast_Desktop_V4_5) + { + //This is 4.5 behaviour + //if we don't require synchronization, a faster set result path is taken + var asyncResult = beginMethod(iar => + { + if (!iar.CompletedSynchronously) + FromAsyncCoreLogic(iar, endFunction, endAction, promise, requiresSynchronization: true); + }, state); + if (asyncResult.CompletedSynchronously) + { + Contract.Assert(asyncResult.IsCompleted, "If the operation completed synchronously, it must be completed."); + FromAsyncCoreLogic(asyncResult, endFunction, endAction, promise, requiresSynchronization: false); + } + } + else + { + //This is the original 4.0 behaviour + var asyncResult = beginMethod(iar => + { + FromAsyncCoreLogic(iar, endFunction, endAction, promise, requiresSynchronization: true); + }, state); + } + } + catch + { + if (AsyncCausalityTracer.LoggingOn) + AsyncCausalityTracer.TraceOperationCompletion(CausalityTraceLevel.Required, promise.Id, AsyncCausalityStatus.Error); + + if (Task.s_asyncDebuggingEnabled) + { + Task.RemoveFromActiveTasks(promise.Id); + } + + // Make sure we don't leave promise "dangling". + promise.TrySetResult(default(TResult)); + throw; + } + + return promise; + } + + /// <summary> + /// Creates a <see cref="T:System.Threading.Tasks.Task{TResult}">Task</see> that represents a pair of + /// begin and end methods that conform to the Asynchronous Programming Model pattern. + /// </summary> + /// <typeparam name="TArg1">The type of the first argument passed to the <paramref + /// name="beginMethod"/> delegate.</typeparam> + /// <param name="beginMethod">The delegate that begins the asynchronous operation.</param> + /// <param name="endMethod">The delegate that ends the asynchronous operation.</param> + /// <param name="arg1">The first argument passed to the <paramref name="beginMethod"/> + /// delegate.</param> + /// <param name="state">An object containing data to be used by the <paramref name="beginMethod"/> + /// delegate.</param> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the + /// <paramref name="beginMethod"/> argument is null.</exception> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the + /// <paramref name="endMethod"/> argument is null.</exception> + /// <returns>The created <see cref="T:System.Threading.Tasks.Task{TResult}">Task</see> that + /// represents the asynchronous operation.</returns> + /// <remarks> + /// This method throws any exceptions thrown by the <paramref name="beginMethod"/>. + /// </remarks> + public Task<TResult> FromAsync<TArg1>( + Func<TArg1, AsyncCallback, object, IAsyncResult> beginMethod, + Func<IAsyncResult, TResult> endMethod, + TArg1 arg1, object state) + { + return FromAsyncImpl(beginMethod, endMethod, null, arg1, state, m_defaultCreationOptions); + } + + /// <summary> + /// Creates a <see cref="T:System.Threading.Tasks.Task{TResult}">Task</see> that represents a pair of + /// begin and end methods that conform to the Asynchronous Programming Model pattern. + /// </summary> + /// <typeparam name="TArg1">The type of the first argument passed to the <paramref + /// name="beginMethod"/> delegate.</typeparam> + /// <param name="beginMethod">The delegate that begins the asynchronous operation.</param> + /// <param name="endMethod">The delegate that ends the asynchronous operation.</param> + /// <param name="arg1">The first argument passed to the <paramref name="beginMethod"/> + /// delegate.</param> + /// <param name="creationOptions">The TaskCreationOptions value that controls the behavior of the + /// created <see cref="T:System.Threading.Tasks.Task{TResult}">Task</see>.</param> + /// <param name="state">An object containing data to be used by the <paramref name="beginMethod"/> + /// delegate.</param> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the + /// <paramref name="beginMethod"/> argument is null.</exception> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the + /// <paramref name="endMethod"/> argument is null.</exception> + /// <exception cref="T:System.ArgumentOutOfRangeException">The exception that is thrown when the + /// <paramref name="creationOptions"/> argument specifies an invalid TaskCreationOptions + /// value.</exception> + /// <returns>The created <see cref="T:System.Threading.Tasks.Task{TResult}">Task</see> that + /// represents the asynchronous operation.</returns> + /// <remarks> + /// This method throws any exceptions thrown by the <paramref name="beginMethod"/>. + /// </remarks> + public Task<TResult> FromAsync<TArg1>( + Func<TArg1, AsyncCallback, object, IAsyncResult> beginMethod, + Func<IAsyncResult, TResult> endMethod, + TArg1 arg1, object state, TaskCreationOptions creationOptions) + { + return FromAsyncImpl(beginMethod, endMethod, null, arg1, state, creationOptions); + } + + // We need this logic broken out into a static method so that the similar TaskFactory.FromAsync() + // method can access the logic w/o declaring a TaskFactory<TResult> instance. + internal static Task<TResult> FromAsyncImpl<TArg1>(Func<TArg1, AsyncCallback, object, IAsyncResult> beginMethod, + Func<IAsyncResult, TResult> endFunction, Action<IAsyncResult> endAction, + TArg1 arg1, object state, TaskCreationOptions creationOptions) + { + if (beginMethod == null) + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.beginMethod); + + if (endFunction == null && endAction == null) + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.endFunction); + + Contract.Requires((endFunction != null) != (endAction != null), "Both endFunction and endAction were non-null"); + + TaskFactory.CheckFromAsyncOptions(creationOptions, true); + + Task<TResult> promise = new Task<TResult>(state, creationOptions); + + if (AsyncCausalityTracer.LoggingOn) + AsyncCausalityTracer.TraceOperationCreation(CausalityTraceLevel.Required, promise.Id, "TaskFactory.FromAsync: " + beginMethod.Method.Name, 0); + + if (Task.s_asyncDebuggingEnabled) + { + Task.AddToActiveTasks(promise); + } + + try + { + // Do NOT change the code below. + // 4.5 relies on the fact that IAsyncResult CompletedSynchronously flag needs to be set correctly, + // sadly this has not been the case that is why the behaviour from 4.5 broke 4.0 buggy apps. Any other + // change will likely brake 4.5 behavior so if possible never touch this code again. + if (BinaryCompatibility.TargetsAtLeast_Desktop_V4_5) + { + //if we don't require synchronization, a faster set result path is taken + var asyncResult = beginMethod(arg1, iar => + { + if (!iar.CompletedSynchronously) + FromAsyncCoreLogic(iar, endFunction, endAction, promise, requiresSynchronization: true); + }, state); + if (asyncResult.CompletedSynchronously) + { + Contract.Assert(asyncResult.IsCompleted, "If the operation completed synchronously, it must be completed."); + FromAsyncCoreLogic(asyncResult, endFunction, endAction, promise, requiresSynchronization: false); + } + } + else + { + //quirk for previous versions + var asyncResult = beginMethod(arg1, iar => + { + FromAsyncCoreLogic(iar, endFunction, endAction, promise, requiresSynchronization: true); + }, state); + } + } + catch + { + + if (AsyncCausalityTracer.LoggingOn) + AsyncCausalityTracer.TraceOperationCompletion(CausalityTraceLevel.Required, promise.Id, AsyncCausalityStatus.Error); + + if (Task.s_asyncDebuggingEnabled) + { + Task.RemoveFromActiveTasks(promise.Id); + } + + // Make sure we don't leave promise "dangling". + promise.TrySetResult(default(TResult)); + throw; + } + + return promise; + } + + /// <summary> + /// Creates a <see cref="T:System.Threading.Tasks.Task{TResult}">Task</see> that represents a pair of + /// begin and end methods that conform to the Asynchronous Programming Model pattern. + /// </summary> + /// <typeparam name="TArg1">The type of the first argument passed to the <paramref + /// name="beginMethod"/> delegate.</typeparam> + /// <typeparam name="TArg2">The type of the second argument passed to <paramref name="beginMethod"/> + /// delegate.</typeparam> + /// <param name="beginMethod">The delegate that begins the asynchronous operation.</param> + /// <param name="endMethod">The delegate that ends the asynchronous operation.</param> + /// <param name="arg1">The first argument passed to the <paramref name="beginMethod"/> + /// delegate.</param> + /// <param name="arg2">The second argument passed to the <paramref name="beginMethod"/> + /// delegate.</param> + /// <param name="state">An object containing data to be used by the <paramref name="beginMethod"/> + /// delegate.</param> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the + /// <paramref name="beginMethod"/> argument is null.</exception> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the + /// <paramref name="endMethod"/> argument is null.</exception> + /// <returns>The created <see cref="T:System.Threading.Tasks.Task{TResult}">Task</see> that + /// represents the asynchronous operation.</returns> + /// <remarks> + /// This method throws any exceptions thrown by the <paramref name="beginMethod"/>. + /// </remarks> + public Task<TResult> FromAsync<TArg1, TArg2>( + Func<TArg1, TArg2, AsyncCallback, object, IAsyncResult> beginMethod, + Func<IAsyncResult, TResult> endMethod, + TArg1 arg1, TArg2 arg2, object state) + { + return FromAsyncImpl(beginMethod, endMethod, null, arg1, arg2, state, m_defaultCreationOptions); + } + + /// <summary> + /// Creates a <see cref="T:System.Threading.Tasks.Task{TResult}">Task</see> that represents a pair of + /// begin and end methods that conform to the Asynchronous Programming Model pattern. + /// </summary> + /// <typeparam name="TArg1">The type of the first argument passed to the <paramref + /// name="beginMethod"/> delegate.</typeparam> + /// <typeparam name="TArg2">The type of the second argument passed to <paramref name="beginMethod"/> + /// delegate.</typeparam> + /// <param name="beginMethod">The delegate that begins the asynchronous operation.</param> + /// <param name="endMethod">The delegate that ends the asynchronous operation.</param> + /// <param name="arg1">The first argument passed to the <paramref name="beginMethod"/> + /// delegate.</param> + /// <param name="arg2">The second argument passed to the <paramref name="beginMethod"/> + /// delegate.</param> + /// <param name="creationOptions">The TaskCreationOptions value that controls the behavior of the + /// created <see cref="T:System.Threading.Tasks.Task{TResult}">Task</see>.</param> + /// <param name="state">An object containing data to be used by the <paramref name="beginMethod"/> + /// delegate.</param> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the + /// <paramref name="beginMethod"/> argument is null.</exception> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the + /// <paramref name="endMethod"/> argument is null.</exception> + /// <exception cref="T:System.ArgumentOutOfRangeException">The exception that is thrown when the + /// <paramref name="creationOptions"/> argument specifies an invalid TaskCreationOptions + /// value.</exception> + /// <returns>The created <see cref="T:System.Threading.Tasks.Task{TResult}">Task</see> that + /// represents the asynchronous operation.</returns> + /// <remarks> + /// This method throws any exceptions thrown by the <paramref name="beginMethod"/>. + /// </remarks> + public Task<TResult> FromAsync<TArg1, TArg2>( + Func<TArg1, TArg2, AsyncCallback, object, IAsyncResult> beginMethod, + Func<IAsyncResult, TResult> endMethod, + TArg1 arg1, TArg2 arg2, object state, TaskCreationOptions creationOptions) + { + return FromAsyncImpl(beginMethod, endMethod, null, arg1, arg2, state, creationOptions); + } + + // We need this logic broken out into a static method so that the similar TaskFactory.FromAsync() + // method can access the logic w/o declaring a TaskFactory<TResult> instance. + internal static Task<TResult> FromAsyncImpl<TArg1, TArg2>(Func<TArg1, TArg2, AsyncCallback, object, IAsyncResult> beginMethod, + Func<IAsyncResult, TResult> endFunction, Action<IAsyncResult> endAction, + TArg1 arg1, TArg2 arg2, object state, TaskCreationOptions creationOptions) + { + if (beginMethod == null) + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.beginMethod); + + if (endFunction == null && endAction == null) + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.endMethod); + + Contract.Requires((endFunction != null) != (endAction != null), "Both endFunction and endAction were non-null"); + + TaskFactory.CheckFromAsyncOptions(creationOptions, true); + + Task<TResult> promise = new Task<TResult>(state, creationOptions); + + if (AsyncCausalityTracer.LoggingOn) + AsyncCausalityTracer.TraceOperationCreation(CausalityTraceLevel.Required, promise.Id, "TaskFactory.FromAsync: " + beginMethod.Method.Name, 0); + + if (Task.s_asyncDebuggingEnabled) + { + Task.AddToActiveTasks(promise); + } + + try + { + // Do NOT change the code below. + // 4.5 relies on the fact that IAsyncResult CompletedSynchronously flag needs to be set correctly, + // sadly this has not been the case that is why the behaviour from 4.5 broke 4.0 buggy apps. Any other + // change will likely brake 4.5 behavior so if possible never touch this code again. + if (BinaryCompatibility.TargetsAtLeast_Desktop_V4_5) + { + //if we don't require synchronization, a faster set result path is taken + var asyncResult = beginMethod(arg1, arg2, iar => + { + if (!iar.CompletedSynchronously) + FromAsyncCoreLogic(iar, endFunction, endAction, promise, requiresSynchronization: true); + }, state); + if (asyncResult.CompletedSynchronously) + { + Contract.Assert(asyncResult.IsCompleted, "If the operation completed synchronously, it must be completed."); + FromAsyncCoreLogic(asyncResult, endFunction, endAction, promise, requiresSynchronization: false); + } + } + else + { + //quirk for previous versions + var asyncResult = beginMethod(arg1, arg2, iar => + { + FromAsyncCoreLogic(iar, endFunction, endAction, promise, requiresSynchronization: true); + }, state); + } + } + catch + { + if (AsyncCausalityTracer.LoggingOn) + AsyncCausalityTracer.TraceOperationCompletion(CausalityTraceLevel.Required, promise.Id, AsyncCausalityStatus.Error); + + if (Task.s_asyncDebuggingEnabled) + { + Task.RemoveFromActiveTasks(promise.Id); + } + + // Make sure we don't leave promise "dangling". + promise.TrySetResult(default(TResult)); + throw; + } + + return promise; + } + + /// <summary> + /// Creates a <see cref="T:System.Threading.Tasks.Task{TResult}">Task</see> that represents a pair of + /// begin and end methods that conform to the Asynchronous Programming Model pattern. + /// </summary> + /// <typeparam name="TArg1">The type of the first argument passed to the <paramref + /// name="beginMethod"/> delegate.</typeparam> + /// <typeparam name="TArg2">The type of the second argument passed to <paramref name="beginMethod"/> + /// delegate.</typeparam> + /// <typeparam name="TArg3">The type of the third argument passed to <paramref name="beginMethod"/> + /// delegate.</typeparam> + /// <param name="beginMethod">The delegate that begins the asynchronous operation.</param> + /// <param name="endMethod">The delegate that ends the asynchronous operation.</param> + /// <param name="arg1">The first argument passed to the <paramref name="beginMethod"/> + /// delegate.</param> + /// <param name="arg2">The second argument passed to the <paramref name="beginMethod"/> + /// delegate.</param> + /// <param name="arg3">The third argument passed to the <paramref name="beginMethod"/> + /// delegate.</param> + /// <param name="state">An object containing data to be used by the <paramref name="beginMethod"/> + /// delegate.</param> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the + /// <paramref name="beginMethod"/> argument is null.</exception> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the + /// <paramref name="endMethod"/> argument is null.</exception> + /// <returns>The created <see cref="T:System.Threading.Tasks.Task{TResult}">Task</see> that + /// represents the asynchronous operation.</returns> + /// <remarks> + /// This method throws any exceptions thrown by the <paramref name="beginMethod"/>. + /// </remarks> + public Task<TResult> FromAsync<TArg1, TArg2, TArg3>( + Func<TArg1, TArg2, TArg3, AsyncCallback, object, IAsyncResult> beginMethod, + Func<IAsyncResult, TResult> endMethod, + TArg1 arg1, TArg2 arg2, TArg3 arg3, object state) + { + return FromAsyncImpl(beginMethod, endMethod, null, arg1, arg2, arg3, state, m_defaultCreationOptions); + } + + /// <summary> + /// Creates a <see cref="T:System.Threading.Tasks.Task{TResult}">Task</see> that represents a pair of + /// begin and end methods that conform to the Asynchronous Programming Model pattern. + /// </summary> + /// <typeparam name="TArg1">The type of the first argument passed to the <paramref + /// name="beginMethod"/> delegate.</typeparam> + /// <typeparam name="TArg2">The type of the second argument passed to <paramref name="beginMethod"/> + /// delegate.</typeparam> + /// <typeparam name="TArg3">The type of the third argument passed to <paramref name="beginMethod"/> + /// delegate.</typeparam> + /// <param name="beginMethod">The delegate that begins the asynchronous operation.</param> + /// <param name="endMethod">The delegate that ends the asynchronous operation.</param> + /// <param name="arg1">The first argument passed to the <paramref name="beginMethod"/> + /// delegate.</param> + /// <param name="arg2">The second argument passed to the <paramref name="beginMethod"/> + /// delegate.</param> + /// <param name="arg3">The third argument passed to the <paramref name="beginMethod"/> + /// delegate.</param> + /// <param name="creationOptions">The TaskCreationOptions value that controls the behavior of the + /// created <see cref="T:System.Threading.Tasks.Task{TResult}">Task</see>.</param> + /// <param name="state">An object containing data to be used by the <paramref name="beginMethod"/> + /// delegate.</param> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the + /// <paramref name="beginMethod"/> argument is null.</exception> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the + /// <paramref name="endMethod"/> argument is null.</exception> + /// <exception cref="T:System.ArgumentOutOfRangeException">The exception that is thrown when the + /// <paramref name="creationOptions"/> argument specifies an invalid TaskCreationOptions + /// value.</exception> + /// <returns>The created <see cref="T:System.Threading.Tasks.Task{TResult}">Task</see> that + /// represents the asynchronous operation.</returns> + /// <remarks> + /// This method throws any exceptions thrown by the <paramref name="beginMethod"/>. + /// </remarks> + public Task<TResult> FromAsync<TArg1, TArg2, TArg3>( + Func<TArg1, TArg2, TArg3, AsyncCallback, object, IAsyncResult> beginMethod, + Func<IAsyncResult, TResult> endMethod, + TArg1 arg1, TArg2 arg2, TArg3 arg3, object state, TaskCreationOptions creationOptions) + { + return FromAsyncImpl(beginMethod, endMethod, null, arg1, arg2, arg3, state, creationOptions); + } + + // We need this logic broken out into a static method so that the similar TaskFactory.FromAsync() + // method can access the logic w/o declaring a TaskFactory<TResult> instance. + internal static Task<TResult> FromAsyncImpl<TArg1, TArg2, TArg3>(Func<TArg1, TArg2, TArg3, AsyncCallback, object, IAsyncResult> beginMethod, + Func<IAsyncResult, TResult> endFunction, Action<IAsyncResult> endAction, + TArg1 arg1, TArg2 arg2, TArg3 arg3, object state, TaskCreationOptions creationOptions) + { + if (beginMethod == null) + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.beginMethod); + + if (endFunction == null && endAction == null) + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.endMethod); + + Contract.Requires((endFunction != null) != (endAction != null), "Both endFunction and endAction were non-null"); + + TaskFactory.CheckFromAsyncOptions(creationOptions, true); + + Task<TResult> promise = new Task<TResult>(state, creationOptions); + + if (AsyncCausalityTracer.LoggingOn) + AsyncCausalityTracer.TraceOperationCreation(CausalityTraceLevel.Required, promise.Id, "TaskFactory.FromAsync: " + beginMethod.Method.Name, 0); + + if (Task.s_asyncDebuggingEnabled) + { + Task.AddToActiveTasks(promise); + } + + try + { + // Do NOT change the code below. + // 4.5 relies on the fact that IAsyncResult CompletedSynchronously flag needs to be set correctly, + // sadly this has not been the case that is why the behaviour from 4.5 broke 4.0 buggy apps. Any other + // change will likely brake 4.5 behavior so if possible never touch this code again. + if (BinaryCompatibility.TargetsAtLeast_Desktop_V4_5) + { + //if we don't require synchronization, a faster set result path is taken + var asyncResult = beginMethod(arg1, arg2, arg3, iar => + { + if (!iar.CompletedSynchronously) + FromAsyncCoreLogic(iar, endFunction, endAction, promise, requiresSynchronization: true); + }, state); + if (asyncResult.CompletedSynchronously) + { + Contract.Assert(asyncResult.IsCompleted, "If the operation completed synchronously, it must be completed."); + FromAsyncCoreLogic(asyncResult, endFunction, endAction, promise, requiresSynchronization: false); + } + } + else + { + //quirk for previous versions + var asyncResult = beginMethod(arg1, arg2, arg3, iar => + { + FromAsyncCoreLogic(iar, endFunction, endAction, promise, requiresSynchronization: true); + }, state); + } + } + catch + { + if (AsyncCausalityTracer.LoggingOn) + AsyncCausalityTracer.TraceOperationCompletion(CausalityTraceLevel.Required, promise.Id, AsyncCausalityStatus.Error); + + if (Task.s_asyncDebuggingEnabled) + { + Task.RemoveFromActiveTasks(promise.Id); + } + + // Make sure we don't leave the promise "dangling". + promise.TrySetResult(default(TResult)); + throw; + } + + return promise; + } + + /// <summary> + /// Special internal-only FromAsync support used by System.IO to wrap + /// APM implementations with minimal overhead, avoiding unnecessary closure + /// and delegate allocations. + /// </summary> + /// <typeparam name="TInstance">Specifies the type of the instance on which the APM implementation lives.</typeparam> + /// <typeparam name="TArg1">Specifies the type containing the arguments.</typeparam> + /// <param name="thisRef">The instance from which the begin and end methods are invoked.</param> + /// <param name="beginMethod">The begin method.</param> + /// <param name="endMethod">The end method.</param> + /// <param name="args">The arguments.</param> + /// <returns>A task representing the asynchronous operation.</returns> + internal static Task<TResult> FromAsyncTrim<TInstance, TArgs>( + TInstance thisRef, TArgs args, + Func<TInstance, TArgs, AsyncCallback, object, IAsyncResult> beginMethod, + Func<TInstance, IAsyncResult, TResult> endMethod) + where TInstance : class + { + // Validate arguments, but only with asserts, as this is an internal only implementation. + Contract.Assert(thisRef != null, "Expected a non-null thisRef"); + Contract.Assert(beginMethod != null, "Expected a non-null beginMethod"); + Contract.Assert(endMethod != null, "Expected a non-null endMethod"); + + // Create the promise and start the operation. + // No try/catch is necessary here as we want exceptions to bubble out, and because + // the task doesn't have AttachedToParent set on it, there's no need to complete it in + // case of an exception occurring... we can just let it go unresolved. + var promise = new FromAsyncTrimPromise<TInstance>(thisRef, endMethod); + var asyncResult = beginMethod(thisRef, args, FromAsyncTrimPromise<TInstance>.s_completeFromAsyncResult, promise); + + // If the IAsyncResult completed asynchronously, completing the promise will be handled by the callback. + // If it completed synchronously, we'll handle that here. + if (asyncResult.CompletedSynchronously) + { + Contract.Assert(asyncResult.IsCompleted, "If the operation completed synchronously, it must be completed."); + promise.Complete(thisRef, endMethod, asyncResult, requiresSynchronization: false); + } + + // Return the promise + return promise; + } + + /// <summary> + /// A specialized task used by FromAsyncTrim. Stores relevant information as instance + /// state so that we can avoid unnecessary closure/delegate allocations. + /// </summary> + /// <typeparam name="TInstance">Specifies the type of the instance on which the APM implementation lives.</typeparam> + private sealed class FromAsyncTrimPromise<TInstance> : Task<TResult> where TInstance : class + { + /// <summary>A cached delegate used as the callback for the BeginXx method.</summary> + internal readonly static AsyncCallback s_completeFromAsyncResult = CompleteFromAsyncResult; + + /// <summary>A reference to the object on which the begin/end methods are invoked.</summary> + private TInstance m_thisRef; + /// <summary>The end method.</summary> + private Func<TInstance, IAsyncResult, TResult> m_endMethod; + + /// <summary>Initializes the promise.</summary> + /// <param name="thisRef">A reference to the object on which the begin/end methods are invoked.</param> + /// <param name="endMethod">The end method.</param> + internal FromAsyncTrimPromise(TInstance thisRef, Func<TInstance, IAsyncResult, TResult> endMethod) : base() + { + Contract.Requires(thisRef != null, "Expected a non-null thisRef"); + Contract.Requires(endMethod != null, "Expected a non-null endMethod"); + m_thisRef = thisRef; + m_endMethod = endMethod; + } + + /// <summary> + /// Completes the asynchronous operation using information in the IAsyncResult. + /// IAsyncResult.AsyncState neeeds to be the FromAsyncTrimPromise to complete. + /// </summary> + /// <param name="asyncResult">The IAsyncResult for the async operation.</param> + internal static void CompleteFromAsyncResult(IAsyncResult asyncResult) + { + // Validate argument + if (asyncResult == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.asyncResult); + Contract.EndContractBlock(); + + var promise = asyncResult.AsyncState as FromAsyncTrimPromise<TInstance>; + if (promise == null) ThrowHelper.ThrowArgumentException(ExceptionResource.InvalidOperation_WrongAsyncResultOrEndCalledMultiple, ExceptionArgument.asyncResult); + + // Grab the relevant state and then null it out so that the task doesn't hold onto the state unnecessarily + var thisRef = promise.m_thisRef; + var endMethod = promise.m_endMethod; + promise.m_thisRef = default(TInstance); + promise.m_endMethod = null; + if (endMethod == null) ThrowHelper.ThrowArgumentException(ExceptionResource.InvalidOperation_WrongAsyncResultOrEndCalledMultiple, ExceptionArgument.asyncResult); + + // Complete the promise. If the IAsyncResult completed synchronously, + // we'll instead complete the promise at the call site. + if (!asyncResult.CompletedSynchronously) + { + promise.Complete(thisRef, endMethod, asyncResult, requiresSynchronization:true); + } + } + + /// <summary>Completes the promise.</summary> + /// <param name="requiresSynchronization"> + /// true if synchronization is needed to completed the task; + /// false if the task may be completed without synchronization + /// because it hasn't been handed out. + /// </param> + /// <param name="thisRef">The target instance on which the end method should be called.</param> + /// <param name="endMethod">The end method to call to retrieve the result.</param> + /// <param name="asyncResult">The IAsyncResult for the async operation.</param> + /// <permission cref="requiresSynchronization"> + /// Whether completing the task requires synchronization. This should be true + /// unless absolutely sure that the task has not yet been handed out to any consumers. + /// </permission> + internal void Complete( + TInstance thisRef, Func<TInstance, IAsyncResult, TResult> endMethod, IAsyncResult asyncResult, + bool requiresSynchronization) + { + Contract.Assert(!IsCompleted, "The task should not have been completed yet."); + + // Run the end method and complete the task + bool successfullySet = false; + try + { + var result = endMethod(thisRef, asyncResult); + if (requiresSynchronization) + { + successfullySet = TrySetResult(result); + } + else + { + // If requiresSynchronization is false, we can use the DangerousSetResult + // method, which uses no synchronization to complete the task. This is + // only valid when the operation is completing synchronously such + // that the task has not yet been handed out to any consumers. + DangerousSetResult(result); + successfullySet = true; + } + } + catch (OperationCanceledException oce) + { + successfullySet = TrySetCanceled(oce.CancellationToken, oce); + } + catch (Exception exc) + { + successfullySet = TrySetException(exc); + } + Contract.Assert(successfullySet, "Expected the task to not yet be completed"); + } + } + + // Utility method to create a canceled future-style task. + // Used by ContinueWhenAll/Any to bail out early on a pre-canceled token. + private static Task<TResult> CreateCanceledTask(TaskContinuationOptions continuationOptions, CancellationToken ct) + { + TaskCreationOptions tco; + InternalTaskOptions dontcare; + Task.CreationOptionsFromContinuationOptions(continuationOptions, out tco, out dontcare); + return new Task<TResult>(true, default(TResult), tco, ct); + } + + // + // ContinueWhenAll() methods + // + + /// <summary> + /// Creates a continuation <see cref="T:System.Threading.Tasks.Task{TResult}">Task</see> + /// that will be started upon the completion of a set of provided Tasks. + /// </summary> + /// <param name="tasks">The array of tasks from which to continue.</param> + /// <param name="continuationFunction">The function delegate to execute when all tasks in + /// the <paramref name="tasks"/> array have completed.</param> + /// <returns>The new continuation <see cref="T:System.Threading.Tasks.Task{TResult}">Task</see>.</returns> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the + /// <paramref name="tasks"/> array is null.</exception> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the + /// <paramref name="continuationFunction"/> argument is null.</exception> + /// <exception cref="T:System.ArgumentException">The exception that is thrown when the + /// <paramref name="tasks"/> array contains a null value.</exception> + /// <exception cref="T:System.ArgumentException">The exception that is thrown when the + /// <paramref name="tasks"/> array is empty.</exception> + [MethodImplAttribute(MethodImplOptions.NoInlining)] // Methods containing StackCrawlMark local var have to be marked non-inlineable + public Task<TResult> ContinueWhenAll(Task[] tasks, Func<Task[], TResult> continuationFunction) + { + if (continuationFunction == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.continuationFunction); + Contract.EndContractBlock(); + + StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller; + return ContinueWhenAllImpl(tasks, continuationFunction, null, m_defaultContinuationOptions, m_defaultCancellationToken, DefaultScheduler, ref stackMark); + } + + /// <summary> + /// Creates a continuation <see cref="T:System.Threading.Tasks.Task{TResult}">Task</see> + /// that will be started upon the completion of a set of provided Tasks. + /// </summary> + /// <param name="tasks">The array of tasks from which to continue.</param> + /// <param name="continuationFunction">The function delegate to execute when all tasks in + /// the <paramref name="tasks"/> array have completed.</param> + /// <param name="cancellationToken">The <see cref="System.Threading.CancellationToken">CancellationToken</see> + /// that will be assigned to the new continuation task.</param> + /// <returns>The new continuation <see cref="T:System.Threading.Tasks.Task{TResult}">Task</see>.</returns> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the + /// <paramref name="tasks"/> array is null.</exception> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the + /// <paramref name="continuationFunction"/> argument is null.</exception> + /// <exception cref="T:System.ArgumentException">The exception that is thrown when the + /// <paramref name="tasks"/> array contains a null value.</exception> + /// <exception cref="T:System.ArgumentException">The exception that is thrown when the + /// <paramref name="tasks"/> array is empty.</exception> + /// <exception cref="T:System.ObjectDisposedException">The provided <see cref="System.Threading.CancellationToken">CancellationToken</see> + /// has already been disposed. + /// </exception> + [MethodImplAttribute(MethodImplOptions.NoInlining)] // Methods containing StackCrawlMark local var have to be marked non-inlineable + public Task<TResult> ContinueWhenAll(Task[] tasks, Func<Task[], TResult> continuationFunction, CancellationToken cancellationToken) + { + if (continuationFunction == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.continuationFunction); + Contract.EndContractBlock(); + + StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller; + return ContinueWhenAllImpl(tasks, continuationFunction, null, m_defaultContinuationOptions, cancellationToken, DefaultScheduler, ref stackMark); + } + + /// <summary> + /// Creates a continuation <see cref="T:System.Threading.Tasks.Task{TResult}">Task</see> + /// that will be started upon the completion of a set of provided Tasks. + /// </summary> + /// <param name="tasks">The array of tasks from which to continue.</param> + /// <param name="continuationFunction">The function delegate to execute when all tasks in the <paramref + /// name="tasks"/> array have completed.</param> + /// <param name="continuationOptions">The <see cref="System.Threading.Tasks.TaskContinuationOptions"> + /// TaskContinuationOptions</see> value that controls the behavior of + /// the created continuation <see cref="T:System.Threading.Tasks.Task{TResult}">Task</see>.</param> + /// <returns>The new continuation <see cref="T:System.Threading.Tasks.Task{TResult}">Task</see>.</returns> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the + /// <paramref name="tasks"/> array is null.</exception> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the + /// <paramref name="continuationFunction"/> argument is null.</exception> + /// <exception cref="T:System.ArgumentException">The exception that is thrown when the + /// <paramref name="tasks"/> array contains a null value.</exception> + /// <exception cref="T:System.ArgumentException">The exception that is thrown when the + /// <paramref name="tasks"/> array is empty.</exception> + /// <exception cref="T:System.ArgumentOutOfRangeException">The exception that is thrown when the + /// <paramref name="continuationOptions"/> argument specifies an invalid TaskContinuationOptions + /// value.</exception> + /// <remarks> + /// The NotOn* and OnlyOn* <see cref="System.Threading.Tasks.TaskContinuationOptions">TaskContinuationOptions</see>, + /// which constrain for which <see cref="System.Threading.Tasks.TaskStatus">TaskStatus</see> states a continuation + /// will be executed, are illegal with ContinueWhenAll. + /// </remarks> + [MethodImplAttribute(MethodImplOptions.NoInlining)] // Methods containing StackCrawlMark local var have to be marked non-inlineable + public Task<TResult> ContinueWhenAll(Task[] tasks, Func<Task[], TResult> continuationFunction, TaskContinuationOptions continuationOptions) + { + if (continuationFunction == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.continuationFunction); + Contract.EndContractBlock(); + + StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller; + return ContinueWhenAllImpl(tasks, continuationFunction, null, continuationOptions, m_defaultCancellationToken, DefaultScheduler, ref stackMark); + } + + /// <summary> + /// Creates a continuation <see cref="T:System.Threading.Tasks.Task{TResult}">Task</see> + /// that will be started upon the completion of a set of provided Tasks. + /// </summary> + /// <param name="tasks">The array of tasks from which to continue.</param> + /// <param name="continuationFunction">The function delegate to execute when all tasks in the <paramref + /// name="tasks"/> array have completed.</param> + /// <param name="cancellationToken">The <see cref="System.Threading.CancellationToken">CancellationToken</see> + /// that will be assigned to the new continuation task.</param> + /// <param name="continuationOptions">The <see cref="System.Threading.Tasks.TaskContinuationOptions"> + /// TaskContinuationOptions</see> value that controls the behavior of + /// the created continuation <see cref="T:System.Threading.Tasks.Task{TResult}">Task</see>.</param> + /// <param name="scheduler">The <see cref="System.Threading.Tasks.TaskScheduler">TaskScheduler</see> + /// that is used to schedule the created continuation <see + /// cref="T:System.Threading.Tasks.Task">Task</see>.</param> + /// <returns>The new continuation <see cref="T:System.Threading.Tasks.Task{TResult}">Task</see>.</returns> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the + /// <paramref name="tasks"/> array is null.</exception> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the + /// <paramref name="continuationFunction"/> argument is null.</exception> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the + /// <paramref name="scheduler"/> argument is null.</exception> + /// <exception cref="T:System.ArgumentException">The exception that is thrown when the + /// <paramref name="tasks"/> array contains a null value.</exception> + /// <exception cref="T:System.ArgumentException">The exception that is thrown when the + /// <paramref name="tasks"/> array is empty.</exception> + /// <exception cref="T:System.ArgumentOutOfRangeException">The exception that is thrown when the + /// <paramref name="continuationOptions"/> argument specifies an invalid TaskContinuationOptions + /// value.</exception> + /// <exception cref="T:System.ObjectDisposedException">The provided <see cref="System.Threading.CancellationToken">CancellationToken</see> + /// has already been disposed. + /// </exception> + /// <remarks> + /// The NotOn* and OnlyOn* <see cref="System.Threading.Tasks.TaskContinuationOptions">TaskContinuationOptions</see>, + /// which constrain for which <see cref="System.Threading.Tasks.TaskStatus">TaskStatus</see> states a continuation + /// will be executed, are illegal with ContinueWhenAll. + /// </remarks> + [MethodImplAttribute(MethodImplOptions.NoInlining)] // Methods containing StackCrawlMark local var have to be marked non-inlineable + public Task<TResult> ContinueWhenAll(Task[] tasks, Func<Task[], TResult> continuationFunction, + CancellationToken cancellationToken, TaskContinuationOptions continuationOptions, TaskScheduler scheduler) + { + if (continuationFunction == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.continuationFunction); + Contract.EndContractBlock(); + + StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller; + return ContinueWhenAllImpl(tasks, continuationFunction, null, continuationOptions, cancellationToken, scheduler, ref stackMark); + } + + /// <summary> + /// Creates a continuation <see cref="T:System.Threading.Tasks.Task{TResult}">Task</see> + /// that will be started upon the completion of a set of provided Tasks. + /// </summary> + /// <typeparam name="TAntecedentResult">The type of the result of the antecedent <paramref name="tasks"/>.</typeparam> + /// <param name="tasks">The array of tasks from which to continue.</param> + /// <param name="continuationFunction">The function delegate to execute when all tasks in the + /// <paramref name="tasks"/> array have completed.</param> + /// <returns>The new continuation <see cref="T:System.Threading.Tasks.Task{TResult}"/>.</returns> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the + /// <paramref name="tasks"/> array is null.</exception> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the + /// <paramref name="continuationFunction"/> argument is null.</exception> + /// <exception cref="T:System.ArgumentException">The exception that is thrown when the + /// <paramref name="tasks"/> array contains a null value.</exception> + /// <exception cref="T:System.ArgumentException">The exception that is thrown when the + /// <paramref name="tasks"/> array is empty.</exception> + [MethodImplAttribute(MethodImplOptions.NoInlining)] // Methods containing StackCrawlMark local var have to be marked non-inlineable + public Task<TResult> ContinueWhenAll<TAntecedentResult>(Task<TAntecedentResult>[] tasks, Func<Task<TAntecedentResult>[], TResult> continuationFunction) + { + if (continuationFunction == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.continuationFunction); + Contract.EndContractBlock(); + + StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller; + return ContinueWhenAllImpl<TAntecedentResult>(tasks, continuationFunction, null, m_defaultContinuationOptions, m_defaultCancellationToken, DefaultScheduler, ref stackMark); + } + + /// <summary> + /// Creates a continuation <see cref="T:System.Threading.Tasks.Task{TResult}">Task</see> + /// that will be started upon the completion of a set of provided Tasks. + /// </summary> + /// <typeparam name="TAntecedentResult">The type of the result of the antecedent <paramref name="tasks"/>.</typeparam> + /// <param name="tasks">The array of tasks from which to continue.</param> + /// <param name="continuationFunction">The function delegate to execute when all tasks in the + /// <paramref name="tasks"/> array have completed.</param> + /// <param name="cancellationToken">The <see cref="System.Threading.CancellationToken">CancellationToken</see> + /// that will be assigned to the new continuation task.</param> + /// <returns>The new continuation <see cref="T:System.Threading.Tasks.Task{TResult}"/>.</returns> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the + /// <paramref name="tasks"/> array is null.</exception> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the + /// <paramref name="continuationFunction"/> argument is null.</exception> + /// <exception cref="T:System.ArgumentException">The exception that is thrown when the + /// <paramref name="tasks"/> array contains a null value.</exception> + /// <exception cref="T:System.ArgumentException">The exception that is thrown when the + /// <paramref name="tasks"/> array is empty.</exception> + /// <exception cref="T:System.ObjectDisposedException">The provided <see cref="System.Threading.CancellationToken">CancellationToken</see> + /// has already been disposed. + /// </exception> + [MethodImplAttribute(MethodImplOptions.NoInlining)] // Methods containing StackCrawlMark local var have to be marked non-inlineable + public Task<TResult> ContinueWhenAll<TAntecedentResult>(Task<TAntecedentResult>[] tasks, Func<Task<TAntecedentResult>[], TResult> continuationFunction, + CancellationToken cancellationToken) + { + if (continuationFunction == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.continuationFunction); + Contract.EndContractBlock(); + + StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller; + return ContinueWhenAllImpl<TAntecedentResult>(tasks, continuationFunction, null, m_defaultContinuationOptions, cancellationToken, DefaultScheduler, ref stackMark); + } + + /// <summary> + /// Creates a continuation <see cref="T:System.Threading.Tasks.Task{TResult}">Task</see> + /// that will be started upon the completion of a set of provided Tasks. + /// </summary> + /// <typeparam name="TAntecedentResult">The type of the result of the antecedent <paramref name="tasks"/>.</typeparam> + /// <param name="tasks">The array of tasks from which to continue.</param> + /// <param name="continuationFunction">The function delegate to execute when all tasks in the + /// <paramref name="tasks"/> array have completed.</param> + /// <param name="continuationOptions">The <see cref="System.Threading.Tasks.TaskContinuationOptions"> + /// TaskContinuationOptions</see> value that controls the behavior of + /// the created continuation <see cref="T:System.Threading.Tasks.Task{TResult}">Task</see>.</param> + /// <returns>The new continuation <see cref="T:System.Threading.Tasks.Task{TResult}"/>.</returns> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the + /// <paramref name="tasks"/> array is null.</exception> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the + /// <paramref name="continuationFunction"/> argument is null.</exception> + /// <exception cref="T:System.ArgumentException">The exception that is thrown when the + /// <paramref name="tasks"/> array contains a null value.</exception> + /// <exception cref="T:System.ArgumentException">The exception that is thrown when the + /// <paramref name="tasks"/> array is empty.</exception> + /// <exception cref="T:System.ArgumentOutOfRangeException">The exception that is thrown when the + /// <paramref name="continuationOptions"/> argument specifies an invalid TaskContinuationOptions + /// value.</exception> + /// <remarks> + /// The NotOn* and OnlyOn* <see cref="System.Threading.Tasks.TaskContinuationOptions">TaskContinuationOptions</see>, + /// which constrain for which <see cref="System.Threading.Tasks.TaskStatus">TaskStatus</see> states a continuation + /// will be executed, are illegal with ContinueWhenAll. + /// </remarks> + [MethodImplAttribute(MethodImplOptions.NoInlining)] // Methods containing StackCrawlMark local var have to be marked non-inlineable + public Task<TResult> ContinueWhenAll<TAntecedentResult>(Task<TAntecedentResult>[] tasks, Func<Task<TAntecedentResult>[], TResult> continuationFunction, + TaskContinuationOptions continuationOptions) + { + if (continuationFunction == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.continuationFunction); + Contract.EndContractBlock(); + + StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller; + return ContinueWhenAllImpl<TAntecedentResult>(tasks, continuationFunction, null, continuationOptions, m_defaultCancellationToken, DefaultScheduler, ref stackMark); + } + + /// <summary> + /// Creates a continuation <see cref="T:System.Threading.Tasks.Task{TResult}">Task</see> + /// that will be started upon the completion of a set of provided Tasks. + /// </summary> + /// <typeparam name="TAntecedentResult">The type of the result of the antecedent <paramref name="tasks"/>.</typeparam> + /// <param name="tasks">The array of tasks from which to continue.</param> + /// <param name="continuationFunction">The function delegate to execute when all tasks in the + /// <paramref name="tasks"/> array have completed.</param> + /// <param name="cancellationToken">The <see cref="System.Threading.CancellationToken">CancellationToken</see> + /// that will be assigned to the new continuation task.</param> + /// <param name="continuationOptions">The <see cref="System.Threading.Tasks.TaskContinuationOptions"> + /// TaskContinuationOptions</see> value that controls the behavior of + /// the created continuation <see cref="T:System.Threading.Tasks.Task{TResult}">Task</see>.</param> + /// <param name="scheduler">The <see cref="System.Threading.Tasks.TaskScheduler">TaskScheduler</see> + /// that is used to schedule the created continuation <see + /// cref="T:System.Threading.Tasks.Task{TResult}"/>.</param> + /// <returns>The new continuation <see cref="T:System.Threading.Tasks.Task{TResult}"/>.</returns> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the + /// <paramref name="tasks"/> array is null.</exception> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the + /// <paramref name="continuationFunction"/> argument is null.</exception> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the + /// <paramref name="scheduler"/> argument is null.</exception> + /// <exception cref="T:System.ArgumentException">The exception that is thrown when the + /// <paramref name="tasks"/> array contains a null value.</exception> + /// <exception cref="T:System.ArgumentException">The exception that is thrown when the + /// <paramref name="tasks"/> array is empty.</exception> + /// <exception cref="T:System.ArgumentOutOfRangeException">The exception that is thrown when the + /// <paramref name="continuationOptions"/> argument specifies an invalid TaskContinuationOptions + /// value.</exception> + /// <exception cref="T:System.ObjectDisposedException">The provided <see cref="System.Threading.CancellationToken">CancellationToken</see> + /// has already been disposed. + /// </exception> + /// <remarks> + /// The NotOn* and OnlyOn* <see cref="System.Threading.Tasks.TaskContinuationOptions">TaskContinuationOptions</see>, + /// which constrain for which <see cref="System.Threading.Tasks.TaskStatus">TaskStatus</see> states a continuation + /// will be executed, are illegal with ContinueWhenAll. + /// </remarks> + [MethodImplAttribute(MethodImplOptions.NoInlining)] // Methods containing StackCrawlMark local var have to be marked non-inlineable + public Task<TResult> ContinueWhenAll<TAntecedentResult>(Task<TAntecedentResult>[] tasks, Func<Task<TAntecedentResult>[], TResult> continuationFunction, + CancellationToken cancellationToken, TaskContinuationOptions continuationOptions, TaskScheduler scheduler) + { + if (continuationFunction == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.continuationFunction); + Contract.EndContractBlock(); + + StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller; + return ContinueWhenAllImpl<TAntecedentResult>(tasks, continuationFunction, null, continuationOptions, cancellationToken, scheduler, ref stackMark); + } + + + // Core implementation of ContinueWhenAll -- the generic version + // Note: if you make any changes to this method, please do the same to the non-generic version too. + internal static Task<TResult> ContinueWhenAllImpl<TAntecedentResult>(Task<TAntecedentResult>[] tasks, + Func<Task<TAntecedentResult>[], TResult> continuationFunction, Action<Task<TAntecedentResult>[]> continuationAction, + TaskContinuationOptions continuationOptions, CancellationToken cancellationToken, TaskScheduler scheduler, ref StackCrawlMark stackMark) + { + // check arguments + TaskFactory.CheckMultiTaskContinuationOptions(continuationOptions); + if (tasks == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.tasks); + //ArgumentNullException of continuationFunction or continuationAction is checked by the caller + Contract.Requires((continuationFunction != null) != (continuationAction != null), "Expected exactly one of endFunction/endAction to be non-null"); + if (scheduler == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.scheduler); + Contract.EndContractBlock(); + + // Check tasks array and make defensive copy + Task<TAntecedentResult>[] tasksCopy = TaskFactory.CheckMultiContinuationTasksAndCopy<TAntecedentResult>(tasks); + + // Bail early if cancellation has been requested. + if (cancellationToken.IsCancellationRequested + && ((continuationOptions & TaskContinuationOptions.LazyCancellation) == 0) + ) + { + return CreateCanceledTask(continuationOptions, cancellationToken); + } + + // Call common ContinueWhenAll() setup logic, extract starter task. + var starter = TaskFactory.CommonCWAllLogic(tasksCopy); + + // returned continuation task, off of starter + if (continuationFunction != null) + { + return starter.ContinueWith<TResult>( + // use a cached delegate + GenericDelegateCache<TAntecedentResult, TResult>.CWAllFuncDelegate, + continuationFunction, scheduler, cancellationToken, continuationOptions, ref stackMark); + } + else + { + Contract.Assert(continuationAction != null); + + return starter.ContinueWith<TResult>( + // use a cached delegate + GenericDelegateCache<TAntecedentResult, TResult>.CWAllActionDelegate, + continuationAction, scheduler, cancellationToken, continuationOptions, ref stackMark); + } + } + + // Core implementation of ContinueWhenAll -- the non-generic version + // Note: if you make any changes to this method, please do the same to the generic version too. + internal static Task<TResult> ContinueWhenAllImpl(Task[] tasks, + Func<Task[], TResult> continuationFunction, Action<Task[]> continuationAction, + TaskContinuationOptions continuationOptions, CancellationToken cancellationToken, TaskScheduler scheduler, ref StackCrawlMark stackMark) + { + // check arguments + TaskFactory.CheckMultiTaskContinuationOptions(continuationOptions); + if (tasks == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.tasks); + //ArgumentNullException of continuationFunction or continuationAction is checked by the caller + Contract.Requires((continuationFunction != null) != (continuationAction != null), "Expected exactly one of endFunction/endAction to be non-null"); + if (scheduler == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.scheduler); + Contract.EndContractBlock(); + + // Check tasks array and make defensive copy + Task[] tasksCopy = TaskFactory.CheckMultiContinuationTasksAndCopy(tasks); + + // Bail early if cancellation has been requested. + if (cancellationToken.IsCancellationRequested + && ((continuationOptions & TaskContinuationOptions.LazyCancellation) == 0) + ) + { + return CreateCanceledTask(continuationOptions, cancellationToken); + } + + // Perform common ContinueWhenAll() setup logic, extract starter task + var starter = TaskFactory.CommonCWAllLogic(tasksCopy); + + // returned continuation task, off of starter + if (continuationFunction != null) + { + return starter.ContinueWith( + //the following delegate avoids closure capture as much as possible + //completedTasks.Result == tasksCopy; + //state == continuationFunction + (completedTasks, state) => + { + completedTasks.NotifyDebuggerOfWaitCompletionIfNecessary(); + return ((Func<Task[], TResult>)state)(completedTasks.Result); + }, + continuationFunction, scheduler, cancellationToken, continuationOptions, ref stackMark); + } + else + { + Contract.Assert(continuationAction != null); + return starter.ContinueWith<TResult>( + //the following delegate avoids closure capture as much as possible + //completedTasks.Result == tasksCopy; + //state == continuationAction + (completedTasks, state) => + { + completedTasks.NotifyDebuggerOfWaitCompletionIfNecessary(); + ((Action<Task[]>)state)(completedTasks.Result); return default(TResult); + }, + continuationAction, scheduler, cancellationToken, continuationOptions, ref stackMark); + } + } + + // + // ContinueWhenAny() methods + // + + /// <summary> + /// Creates a continuation <see cref="T:System.Threading.Tasks.Task{TResult}">Task</see> + /// that will be started upon the completion of any Task in the provided set. + /// </summary> + /// <param name="tasks">The array of tasks from which to continue when one task completes.</param> + /// <param name="continuationFunction">The function delegate to execute when one task in the <paramref + /// name="tasks"/> array completes.</param> + /// <returns>The new continuation <see cref="T:System.Threading.Tasks.Task{TResult}">Task</see>.</returns> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the + /// <paramref name="tasks"/> array is null.</exception> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the + /// <paramref name="continuationFunction"/> argument is null.</exception> + /// <exception cref="T:System.ArgumentException">The exception that is thrown when the + /// <paramref name="tasks"/> array contains a null value.</exception> + /// <exception cref="T:System.ArgumentException">The exception that is thrown when the + /// <paramref name="tasks"/> array is empty.</exception> + [MethodImplAttribute(MethodImplOptions.NoInlining)] // Methods containing StackCrawlMark local var have to be marked non-inlineable + public Task<TResult> ContinueWhenAny(Task[] tasks, Func<Task, TResult> continuationFunction) + { + if (continuationFunction == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.continuationFunction); + Contract.EndContractBlock(); + + StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller; + return ContinueWhenAnyImpl(tasks, continuationFunction, null, m_defaultContinuationOptions, m_defaultCancellationToken, DefaultScheduler, ref stackMark); + } + + /// <summary> + /// Creates a continuation <see cref="T:System.Threading.Tasks.Task{TResult}">Task</see> + /// that will be started upon the completion of any Task in the provided set. + /// </summary> + /// <param name="tasks">The array of tasks from which to continue when one task completes.</param> + /// <param name="continuationFunction">The function delegate to execute when one task in the <paramref + /// name="tasks"/> array completes.</param> + /// <param name="cancellationToken">The <see cref="System.Threading.CancellationToken">CancellationToken</see> + /// that will be assigned to the new continuation task.</param> + /// <returns>The new continuation <see cref="T:System.Threading.Tasks.Task{TResult}">Task</see>.</returns> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the + /// <paramref name="tasks"/> array is null.</exception> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the + /// <paramref name="continuationFunction"/> argument is null.</exception> + /// <exception cref="T:System.ArgumentException">The exception that is thrown when the + /// <paramref name="tasks"/> array contains a null value.</exception> + /// <exception cref="T:System.ArgumentException">The exception that is thrown when the + /// <paramref name="tasks"/> array is empty.</exception> + /// <exception cref="T:System.ObjectDisposedException">The provided <see cref="System.Threading.CancellationToken">CancellationToken</see> + /// has already been disposed. + /// </exception> + [MethodImplAttribute(MethodImplOptions.NoInlining)] // Methods containing StackCrawlMark local var have to be marked non-inlineable + public Task<TResult> ContinueWhenAny(Task[] tasks, Func<Task, TResult> continuationFunction, CancellationToken cancellationToken) + { + if (continuationFunction == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.continuationFunction); + Contract.EndContractBlock(); + + StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller; + return ContinueWhenAnyImpl(tasks, continuationFunction, null, m_defaultContinuationOptions, cancellationToken, DefaultScheduler, ref stackMark); + } + + /// <summary> + /// Creates a continuation <see cref="T:System.Threading.Tasks.Task{TResult}">Task</see> + /// that will be started upon the completion of any Task in the provided set. + /// </summary> + /// <param name="tasks">The array of tasks from which to continue when one task completes.</param> + /// <param name="continuationFunction">The function delegate to execute when one task in the <paramref + /// name="tasks"/> array completes.</param> + /// <param name="continuationOptions">The <see cref="System.Threading.Tasks.TaskContinuationOptions"> + /// TaskContinuationOptions</see> value that controls the behavior of + /// the created continuation <see cref="T:System.Threading.Tasks.Task{TResult}">Task</see>.</param> + /// <returns>The new continuation <see cref="T:System.Threading.Tasks.Task{TResult}">Task</see>.</returns> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the + /// <paramref name="tasks"/> array is null.</exception> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the + /// <paramref name="continuationFunction"/> argument is null.</exception> + /// <exception cref="T:System.ArgumentException">The exception that is thrown when the + /// <paramref name="tasks"/> array contains a null value.</exception> + /// <exception cref="T:System.ArgumentException">The exception that is thrown when the + /// <paramref name="tasks"/> array is empty.</exception> + /// <exception cref="T:System.ArgumentOutOfRangeException">The exception that is thrown when the + /// <paramref name="continuationOptions"/> argument specifies an invalid TaskContinuationOptions + /// value.</exception> + /// <remarks> + /// The NotOn* and OnlyOn* <see cref="System.Threading.Tasks.TaskContinuationOptions">TaskContinuationOptions</see>, + /// which constrain for which <see cref="System.Threading.Tasks.TaskStatus">TaskStatus</see> states a continuation + /// will be executed, are illegal with ContinueWhenAny. + /// </remarks> + [MethodImplAttribute(MethodImplOptions.NoInlining)] // Methods containing StackCrawlMark local var have to be marked non-inlineable + public Task<TResult> ContinueWhenAny(Task[] tasks, Func<Task, TResult> continuationFunction, TaskContinuationOptions continuationOptions) + { + if (continuationFunction == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.continuationFunction); + Contract.EndContractBlock(); + + StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller; + return ContinueWhenAnyImpl(tasks, continuationFunction, null, continuationOptions, m_defaultCancellationToken, DefaultScheduler, ref stackMark); + } + + /// <summary> + /// Creates a continuation <see cref="T:System.Threading.Tasks.Task{TResult}">Task</see> + /// that will be started upon the completion of any Task in the provided set. + /// </summary> + /// <param name="tasks">The array of tasks from which to continue when one task completes.</param> + /// <param name="continuationFunction">The function delegate to execute when one task in the <paramref + /// name="tasks"/> array completes.</param> + /// <param name="cancellationToken">The <see cref="System.Threading.CancellationToken">CancellationToken</see> + /// that will be assigned to the new continuation task.</param> + /// <param name="continuationOptions">The <see cref="System.Threading.Tasks.TaskContinuationOptions"> + /// TaskContinuationOptions</see> value that controls the behavior of + /// the created continuation <see cref="T:System.Threading.Tasks.Task{TResult}">Task</see>.</param> + /// <param name="scheduler">The <see cref="System.Threading.Tasks.TaskScheduler">TaskScheduler</see> + /// that is used to schedule the created continuation <see + /// cref="T:System.Threading.Tasks.Task">Task</see>.</param> + /// <returns>The new continuation <see cref="T:System.Threading.Tasks.Task{TResult}">Task</see>.</returns> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the + /// <paramref name="tasks"/> array is null.</exception> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the + /// <paramref name="continuationFunction"/> argument is null.</exception> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the + /// <paramref name="scheduler"/> argument is null.</exception> + /// <exception cref="T:System.ArgumentException">The exception that is thrown when the + /// <paramref name="tasks"/> array contains a null value.</exception> + /// <exception cref="T:System.ArgumentException">The exception that is thrown when the + /// <paramref name="tasks"/> array is empty.</exception> + /// <exception cref="T:System.ArgumentOutOfRangeException">The exception that is thrown when the + /// <paramref name="continuationOptions"/> argument specifies an invalid TaskContinuationOptions + /// value.</exception> + /// <exception cref="T:System.ObjectDisposedException">The provided <see cref="System.Threading.CancellationToken">CancellationToken</see> + /// has already been disposed. + /// </exception> + /// <remarks> + /// The NotOn* and OnlyOn* <see cref="System.Threading.Tasks.TaskContinuationOptions">TaskContinuationOptions</see>, + /// which constrain for which <see cref="System.Threading.Tasks.TaskStatus">TaskStatus</see> states a continuation + /// will be executed, are illegal with ContinueWhenAny. + /// </remarks> + [MethodImplAttribute(MethodImplOptions.NoInlining)] // Methods containing StackCrawlMark local var have to be marked non-inlineable + public Task<TResult> ContinueWhenAny(Task[] tasks, Func<Task, TResult> continuationFunction, + CancellationToken cancellationToken, TaskContinuationOptions continuationOptions, TaskScheduler scheduler) + { + if (continuationFunction == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.continuationFunction); + Contract.EndContractBlock(); + + StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller; + return ContinueWhenAnyImpl(tasks, continuationFunction, null, continuationOptions, cancellationToken, scheduler, ref stackMark); + } + + /// <summary> + /// Creates a continuation <see cref="T:System.Threading.Tasks.Task{TResult}">Task</see> + /// that will be started upon the completion of any Task in the provided set. + /// </summary> + /// <typeparam name="TAntecedentResult">The type of the result of the antecedent <paramref name="tasks"/>.</typeparam> + /// <param name="tasks">The array of tasks from which to continue when one task completes.</param> + /// <param name="continuationFunction">The function delegate to execute when one task in the + /// <paramref name="tasks"/> array completes.</param> + /// <returns>The new continuation <see cref="T:System.Threading.Tasks.Task{TResult}"/>.</returns> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the + /// <paramref name="tasks"/> array is null.</exception> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the + /// <paramref name="continuationFunction"/> argument is null.</exception> + /// <exception cref="T:System.ArgumentException">The exception that is thrown when the + /// <paramref name="tasks"/> array contains a null value.</exception> + /// <exception cref="T:System.ArgumentException">The exception that is thrown when the + /// <paramref name="tasks"/> array is empty.</exception> + [MethodImplAttribute(MethodImplOptions.NoInlining)] // Methods containing StackCrawlMark local var have to be marked non-inlineable + public Task<TResult> ContinueWhenAny<TAntecedentResult>(Task<TAntecedentResult>[] tasks, Func<Task<TAntecedentResult>, TResult> continuationFunction) + { + if (continuationFunction == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.continuationFunction); + Contract.EndContractBlock(); + + StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller; + return ContinueWhenAnyImpl<TAntecedentResult>(tasks, continuationFunction, null, m_defaultContinuationOptions, m_defaultCancellationToken, DefaultScheduler, ref stackMark); + } + + /// <summary> + /// Creates a continuation <see cref="T:System.Threading.Tasks.Task{TResult}">Task</see> + /// that will be started upon the completion of any Task in the provided set. + /// </summary> + /// <typeparam name="TAntecedentResult">The type of the result of the antecedent <paramref name="tasks"/>.</typeparam> + /// <param name="tasks">The array of tasks from which to continue when one task completes.</param> + /// <param name="continuationFunction">The function delegate to execute when one task in the + /// <paramref name="tasks"/> array completes.</param> + /// <param name="cancellationToken">The <see cref="System.Threading.CancellationToken">CancellationToken</see> + /// that will be assigned to the new continuation task.</param> + /// <returns>The new continuation <see cref="T:System.Threading.Tasks.Task{TResult}"/>.</returns> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the + /// <paramref name="tasks"/> array is null.</exception> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the + /// <paramref name="continuationFunction"/> argument is null.</exception> + /// <exception cref="T:System.ArgumentException">The exception that is thrown when the + /// <paramref name="tasks"/> array contains a null value.</exception> + /// <exception cref="T:System.ArgumentException">The exception that is thrown when the + /// <paramref name="tasks"/> array is empty.</exception> + /// <exception cref="T:System.ObjectDisposedException">The provided <see cref="System.Threading.CancellationToken">CancellationToken</see> + /// has already been disposed. + /// </exception> + [MethodImplAttribute(MethodImplOptions.NoInlining)] // Methods containing StackCrawlMark local var have to be marked non-inlineable + public Task<TResult> ContinueWhenAny<TAntecedentResult>(Task<TAntecedentResult>[] tasks, Func<Task<TAntecedentResult>, TResult> continuationFunction, + CancellationToken cancellationToken) + { + if (continuationFunction == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.continuationFunction); + Contract.EndContractBlock(); + + StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller; + return ContinueWhenAnyImpl<TAntecedentResult>(tasks, continuationFunction, null, m_defaultContinuationOptions, cancellationToken, DefaultScheduler, ref stackMark); + } + + /// <summary> + /// Creates a continuation <see cref="T:System.Threading.Tasks.Task{TResult}">Task</see> + /// that will be started upon the completion of any Task in the provided set. + /// </summary> + /// <typeparam name="TAntecedentResult">The type of the result of the antecedent <paramref name="tasks"/>.</typeparam> + /// <param name="tasks">The array of tasks from which to continue when one task completes.</param> + /// <param name="continuationFunction">The function delegate to execute when one task in the + /// <paramref name="tasks"/> array completes.</param> + /// <param name="continuationOptions">The <see cref="System.Threading.Tasks.TaskContinuationOptions"> + /// TaskContinuationOptions</see> value that controls the behavior of + /// the created continuation <see cref="T:System.Threading.Tasks.Task{TResult}">Task</see>.</param> + /// <returns>The new continuation <see cref="T:System.Threading.Tasks.Task{TResult}"/>.</returns> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the + /// <paramref name="tasks"/> array is null.</exception> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the + /// <paramref name="continuationFunction"/> argument is null.</exception> + /// <exception cref="T:System.ArgumentException">The exception that is thrown when the + /// <paramref name="tasks"/> array contains a null value.</exception> + /// <exception cref="T:System.ArgumentException">The exception that is thrown when the + /// <paramref name="tasks"/> array is empty.</exception> + /// <exception cref="T:System.ArgumentOutOfRangeException">The exception that is thrown when the + /// <paramref name="continuationOptions"/> argument specifies an invalid TaskContinuationOptions + /// value.</exception> + /// <remarks> + /// The NotOn* and OnlyOn* <see cref="System.Threading.Tasks.TaskContinuationOptions">TaskContinuationOptions</see>, + /// which constrain for which <see cref="System.Threading.Tasks.TaskStatus">TaskStatus</see> states a continuation + /// will be executed, are illegal with ContinueWhenAny. + /// </remarks> + [MethodImplAttribute(MethodImplOptions.NoInlining)] // Methods containing StackCrawlMark local var have to be marked non-inlineable + public Task<TResult> ContinueWhenAny<TAntecedentResult>(Task<TAntecedentResult>[] tasks, Func<Task<TAntecedentResult>, TResult> continuationFunction, + TaskContinuationOptions continuationOptions) + { + if (continuationFunction == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.continuationFunction); + Contract.EndContractBlock(); + + StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller; + return ContinueWhenAnyImpl<TAntecedentResult>(tasks, continuationFunction, null, continuationOptions, m_defaultCancellationToken, DefaultScheduler, ref stackMark); + } + + /// <summary> + /// Creates a continuation <see cref="T:System.Threading.Tasks.Task{TResult}">Task</see> + /// that will be started upon the completion of any Task in the provided set. + /// </summary> + /// <typeparam name="TAntecedentResult">The type of the result of the antecedent <paramref name="tasks"/>.</typeparam> + /// <param name="tasks">The array of tasks from which to continue when one task completes.</param> + /// <param name="continuationFunction">The function delegate to execute when one task in the + /// <paramref name="tasks"/> array completes.</param> + /// <param name="cancellationToken">The <see cref="System.Threading.CancellationToken">CancellationToken</see> + /// that will be assigned to the new continuation task.</param> + /// <param name="continuationOptions">The <see cref="System.Threading.Tasks.TaskContinuationOptions"> + /// TaskContinuationOptions</see> value that controls the behavior of + /// the created continuation <see cref="T:System.Threading.Tasks.Task{TResult}">Task</see>.</param> + /// <param name="scheduler">The <see cref="System.Threading.Tasks.TaskScheduler">TaskScheduler</see> + /// that is used to schedule the created continuation <see + /// cref="T:System.Threading.Tasks.Task{TResult}"/>.</param> + /// <returns>The new continuation <see cref="T:System.Threading.Tasks.Task{TResult}"/>.</returns> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the + /// <paramref name="tasks"/> array is null.</exception> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the + /// <paramref name="continuationFunction"/> argument is null.</exception> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the + /// <paramref name="scheduler"/> argument is null.</exception> + /// <exception cref="T:System.ArgumentException">The exception that is thrown when the + /// <paramref name="tasks"/> array contains a null value.</exception> + /// <exception cref="T:System.ArgumentException">The exception that is thrown when the + /// <paramref name="tasks"/> array is empty.</exception> + /// <exception cref="T:System.ArgumentOutOfRangeException">The exception that is thrown when the + /// <paramref name="continuationOptions"/> argument specifies an invalid TaskContinuationOptions + /// value.</exception> + /// <exception cref="T:System.ObjectDisposedException">The provided <see cref="System.Threading.CancellationToken">CancellationToken</see> + /// has already been disposed. + /// </exception> + /// <remarks> + /// The NotOn* and OnlyOn* <see cref="System.Threading.Tasks.TaskContinuationOptions">TaskContinuationOptions</see>, + /// which constrain for which <see cref="System.Threading.Tasks.TaskStatus">TaskStatus</see> states a continuation + /// will be executed, are illegal with ContinueWhenAny. + /// </remarks> + [MethodImplAttribute(MethodImplOptions.NoInlining)] // Methods containing StackCrawlMark local var have to be marked non-inlineable + public Task<TResult> ContinueWhenAny<TAntecedentResult>(Task<TAntecedentResult>[] tasks, Func<Task<TAntecedentResult>, TResult> continuationFunction, + CancellationToken cancellationToken, TaskContinuationOptions continuationOptions, TaskScheduler scheduler) + { + if (continuationFunction == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.continuationFunction); + Contract.EndContractBlock(); + + StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller; + return ContinueWhenAnyImpl<TAntecedentResult>(tasks, continuationFunction, null, continuationOptions, cancellationToken, scheduler, ref stackMark); + } + + // Core implementation of ContinueWhenAny, non-generic version + // Note: if you make any changes to this method, be sure to do the same to the generic version + internal static Task<TResult> ContinueWhenAnyImpl(Task[] tasks, + Func<Task, TResult> continuationFunction, Action<Task> continuationAction, + TaskContinuationOptions continuationOptions, CancellationToken cancellationToken, TaskScheduler scheduler, ref StackCrawlMark stackMark) + { + // check arguments + TaskFactory.CheckMultiTaskContinuationOptions(continuationOptions); + if (tasks == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.tasks); + if(tasks.Length == 0) ThrowHelper.ThrowArgumentException( ExceptionResource.Task_MultiTaskContinuation_EmptyTaskList, ExceptionArgument.tasks); + + //ArgumentNullException of continuationFunction or continuationAction is checked by the caller + Contract.Requires((continuationFunction != null) != (continuationAction != null), "Expected exactly one of endFunction/endAction to be non-null"); + if (scheduler == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.scheduler); + Contract.EndContractBlock(); + + // Call common ContinueWhenAny() setup logic, extract starter + Task<Task> starter = TaskFactory.CommonCWAnyLogic(tasks); + + // Bail early if cancellation has been requested. + if (cancellationToken.IsCancellationRequested + && ((continuationOptions & TaskContinuationOptions.LazyCancellation) == 0) + ) + { + return CreateCanceledTask(continuationOptions, cancellationToken); + } + + // returned continuation task, off of starter + if (continuationFunction != null) + { + return starter.ContinueWith( + //the following delegate avoids closure capture as much as possible + //completedTask.Result is the winning task; state == continuationAction + (completedTask, state) => { return ((Func<Task, TResult>)state)(completedTask.Result); }, + continuationFunction, scheduler, cancellationToken, continuationOptions, ref stackMark); + } + else + { + Contract.Assert(continuationAction != null); + return starter.ContinueWith<TResult>( + //the following delegate avoids closure capture as much as possible + //completedTask.Result is the winning task; state == continuationAction + (completedTask, state) => { ((Action<Task>)state)(completedTask.Result); return default(TResult); }, + continuationAction, scheduler, cancellationToken, continuationOptions, ref stackMark); + } + } + + + // Core implementation of ContinueWhenAny, generic version + // Note: if you make any changes to this method, be sure to do the same to the non-generic version + internal static Task<TResult> ContinueWhenAnyImpl<TAntecedentResult>(Task<TAntecedentResult>[] tasks, + Func<Task<TAntecedentResult>, TResult> continuationFunction, Action<Task<TAntecedentResult>> continuationAction, + TaskContinuationOptions continuationOptions, CancellationToken cancellationToken, TaskScheduler scheduler, ref StackCrawlMark stackMark) + { + // check arguments + TaskFactory.CheckMultiTaskContinuationOptions(continuationOptions); + if (tasks == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.tasks); + if (tasks.Length == 0) ThrowHelper.ThrowArgumentException(ExceptionResource.Task_MultiTaskContinuation_EmptyTaskList, ExceptionArgument.tasks); + //ArgumentNullException of continuationFunction or continuationAction is checked by the caller + Contract.Requires((continuationFunction != null) != (continuationAction != null), "Expected exactly one of endFunction/endAction to be non-null"); + if (scheduler == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.scheduler); + Contract.EndContractBlock(); + + // Call common ContinueWhenAny setup logic, extract starter + var starter = TaskFactory.CommonCWAnyLogic(tasks); + + // Bail early if cancellation has been requested. + if (cancellationToken.IsCancellationRequested + && ((continuationOptions & TaskContinuationOptions.LazyCancellation) == 0) + ) + { + return CreateCanceledTask(continuationOptions, cancellationToken); + } + + // returned continuation task, off of starter + if (continuationFunction != null) + { + return starter.ContinueWith<TResult>( + // Use a cached delegate + GenericDelegateCache<TAntecedentResult, TResult>.CWAnyFuncDelegate, + continuationFunction, scheduler, cancellationToken, continuationOptions, ref stackMark); + } + else + { + Contract.Assert(continuationAction != null); + return starter.ContinueWith<TResult>( + // Use a cached delegate + GenericDelegateCache<TAntecedentResult,TResult>.CWAnyActionDelegate, + continuationAction, scheduler, cancellationToken, continuationOptions, ref stackMark); + } + } + } + + // For the ContinueWhenAnyImpl/ContinueWhenAllImpl methods that are generic on TAntecedentResult, + // the compiler won't cache the internal ContinueWith delegate because it is generic on both + // TAntecedentResult and TResult. The GenericDelegateCache serves as a cache for those delegates. + internal static class GenericDelegateCache<TAntecedentResult, TResult> + { + // ContinueWith delegate for TaskFactory<TResult>.ContinueWhenAnyImpl<TAntecedentResult>(non-null continuationFunction) + internal static Func<Task<Task>, object, TResult> CWAnyFuncDelegate = + (Task<Task> wrappedWinner, object state) => + { + var func = (Func<Task<TAntecedentResult>, TResult>)state; + var arg = (Task<TAntecedentResult>)wrappedWinner.Result; + return func(arg); + }; + + // ContinueWith delegate for TaskFactory<TResult>.ContinueWhenAnyImpl<TAntecedentResult>(non-null continuationAction) + internal static Func<Task<Task>, object, TResult> CWAnyActionDelegate = + (Task<Task> wrappedWinner, object state) => + { + var action = (Action<Task<TAntecedentResult>>)state; + var arg = (Task<TAntecedentResult>)wrappedWinner.Result; + action(arg); + return default(TResult); + }; + + // ContinueWith delegate for TaskFactory<TResult>.ContinueWhenAllImpl<TAntecedentResult>(non-null continuationFunction) + internal static Func<Task<Task<TAntecedentResult>[]>, object, TResult> CWAllFuncDelegate = + (Task<Task<TAntecedentResult>[]> wrappedAntecedents, object state) => + { + wrappedAntecedents.NotifyDebuggerOfWaitCompletionIfNecessary(); + var func = (Func<Task<TAntecedentResult>[], TResult>)state; + return func(wrappedAntecedents.Result); + }; + + // ContinueWith delegate for TaskFactory<TResult>.ContinueWhenAllImpl<TAntecedentResult>(non-null continuationAction) + internal static Func<Task<Task<TAntecedentResult>[]>, object, TResult> CWAllActionDelegate = + (Task<Task<TAntecedentResult>[]> wrappedAntecedents, object state) => + { + wrappedAntecedents.NotifyDebuggerOfWaitCompletionIfNecessary(); + var action = (Action<Task<TAntecedentResult>[]>)state; + action(wrappedAntecedents.Result); + return default(TResult); + }; + + } + +} diff --git a/src/mscorlib/src/System/Threading/Tasks/IAsyncCausalityTracerStatics.cs b/src/mscorlib/src/System/Threading/Tasks/IAsyncCausalityTracerStatics.cs new file mode 100644 index 0000000000..b8155d017e --- /dev/null +++ b/src/mscorlib/src/System/Threading/Tasks/IAsyncCausalityTracerStatics.cs @@ -0,0 +1,101 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +// +// + +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Runtime.InteropServices.WindowsRuntime; + +// Windows.Foundation.Diagnostics cannot be referenced from managed code because +// they're hidden by the metadata adapter. We redeclare the interfaces manually +// to be able to talk to native WinRT objects. +namespace Windows.Foundation.Diagnostics +{ + [ComImport] + [Guid("50850B26-267E-451B-A890-AB6A370245EE")] + [WindowsRuntimeImport] + internal interface IAsyncCausalityTracerStatics + { + void TraceOperationCreation(CausalityTraceLevel traceLevel, CausalitySource source, Guid platformId, ulong operationId, string operationName, ulong relatedContext); + void TraceOperationCompletion(CausalityTraceLevel traceLevel, CausalitySource source, Guid platformId, ulong operationId, AsyncCausalityStatus status); + void TraceOperationRelation(CausalityTraceLevel traceLevel, CausalitySource source, Guid platformId, ulong operationId, CausalityRelation relation); + void TraceSynchronousWorkStart(CausalityTraceLevel traceLevel, CausalitySource source, Guid platformId, ulong operationId, CausalitySynchronousWork work); + void TraceSynchronousWorkCompletion(CausalityTraceLevel traceLevel, CausalitySource source, CausalitySynchronousWork work); + //These next 2 functions could've been represented as an event except that the EventRegistrationToken wasn't being propagated to WinRT + EventRegistrationToken add_TracingStatusChanged(System.EventHandler<TracingStatusChangedEventArgs> eventHandler); + void remove_TracingStatusChanged(EventRegistrationToken token); + } + + [ComImport] + [Guid("410B7711-FF3B-477F-9C9A-D2EFDA302DC3")] + [WindowsRuntimeImport] + internal interface ITracingStatusChangedEventArgs + { + bool Enabled { get; } + CausalityTraceLevel TraceLevel { get; } + } + + // We need this dummy class to satisfy a QI when the TracingStatusChangedHandler + // after being stored in a GIT cookie and then called by the WinRT API. This usually + // happens when calling a MAnaged WinMD which access this feature. + [ComImport] + [Guid("410B7711-FF3B-477F-9C9A-D2EFDA302DC3")] + [WindowsRuntimeImport] + internal sealed class TracingStatusChangedEventArgs : ITracingStatusChangedEventArgs + { + public extern bool Enabled + { + [MethodImpl(MethodImplOptions.InternalCall)] + get; + } + + public extern CausalityTraceLevel TraceLevel + { + [MethodImpl(MethodImplOptions.InternalCall)] + get; + } + } + + internal enum CausalityRelation + { + AssignDelegate, + Join, + Choice, + Cancel, + Error + } + + internal enum CausalitySource + { + Application, + Library, + System + } + + internal enum CausalitySynchronousWork + { + CompletionNotification, + ProgressNotification, + Execution + } + + internal enum CausalityTraceLevel + { + Required, + Important, + Verbose + } + + internal enum AsyncCausalityStatus + { + Canceled = 2, + Completed = 1, + Error = 3, + Started = 0 + } + +} diff --git a/src/mscorlib/src/System/Threading/Tasks/Parallel.cs b/src/mscorlib/src/System/Threading/Tasks/Parallel.cs new file mode 100644 index 0000000000..5ec2ae33c0 --- /dev/null +++ b/src/mscorlib/src/System/Threading/Tasks/Parallel.cs @@ -0,0 +1,3594 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +// =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ +// +// +// +// A helper class that contains parallel versions of various looping constructs. This +// internally uses the task parallel library, but takes care to expose very little +// evidence of this infrastructure being used. +// +// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + +using System; +using System.Diagnostics; +using System.Collections; +using System.Collections.Generic; +using System.Collections.Concurrent; +using System.Security.Permissions; +using System.Threading; +using System.Threading.Tasks; +using System.Diagnostics.Contracts; + + +namespace System.Threading.Tasks +{ + /// <summary> + /// Stores options that configure the operation of methods on the + /// <see cref="T:System.Threading.Tasks.Parallel">Parallel</see> class. + /// </summary> + /// <remarks> + /// By default, methods on the Parallel class attempt to utilize all available processors, are non-cancelable, and target + /// the default TaskScheduler (TaskScheduler.Default). <see cref="ParallelOptions"/> enables + /// overriding these defaults. + /// </remarks> + public class ParallelOptions + { + private TaskScheduler m_scheduler; + private int m_maxDegreeOfParallelism; + private CancellationToken m_cancellationToken; + + /// <summary> + /// Initializes a new instance of the <see cref="ParallelOptions"/> class. + /// </summary> + /// <remarks> + /// This constructor initializes the instance with default values. <see cref="MaxDegreeOfParallelism"/> + /// is initialized to -1, signifying that there is no upper bound set on how much parallelism should + /// be employed. <see cref="CancellationToken"/> is initialized to a non-cancelable token, + /// and <see cref="TaskScheduler"/> is initialized to the default scheduler (TaskScheduler.Default). + /// All of these defaults may be overwritten using the property set accessors on the instance. + /// </remarks> + public ParallelOptions() + { + m_scheduler = TaskScheduler.Default; + m_maxDegreeOfParallelism = -1; + m_cancellationToken = CancellationToken.None; + } + + /// <summary> + /// Gets or sets the <see cref="T:System.Threading.Tasks.TaskScheduler">TaskScheduler</see> + /// associated with this <see cref="ParallelOptions"/> instance. Setting this property to null + /// indicates that the current scheduler should be used. + /// </summary> + public TaskScheduler TaskScheduler + { + get { return m_scheduler; } + set { m_scheduler = value; } + } + + // Convenience property used by TPL logic + internal TaskScheduler EffectiveTaskScheduler + { + get + { + if (m_scheduler == null) return TaskScheduler.Current; + else return m_scheduler; + } + } + + /// <summary> + /// Gets or sets the maximum degree of parallelism enabled by this ParallelOptions instance. + /// </summary> + /// <remarks> + /// The <see cref="MaxDegreeOfParallelism"/> limits the number of concurrent operations run by <see + /// cref="T:System.Threading.Tasks.Parallel">Parallel</see> method calls that are passed this + /// ParallelOptions instance to the set value, if it is positive. If <see + /// cref="MaxDegreeOfParallelism"/> is -1, then there is no limit placed on the number of concurrently + /// running operations. + /// </remarks> + /// <exception cref="T:System.ArgumentOutOfRangeException"> + /// The exception that is thrown when this <see cref="MaxDegreeOfParallelism"/> is set to 0 or some + /// value less than -1. + /// </exception> + public int MaxDegreeOfParallelism + { + get { return m_maxDegreeOfParallelism; } + set + { + if ((value == 0) || (value < -1)) + throw new ArgumentOutOfRangeException("MaxDegreeOfParallelism"); + m_maxDegreeOfParallelism = value; + } + } + + /// <summary> + /// Gets or sets the <see cref="T:System.Threading.CancellationToken">CancellationToken</see> + /// associated with this <see cref="ParallelOptions"/> instance. + /// </summary> + /// <remarks> + /// Providing a <see cref="T:System.Threading.CancellationToken">CancellationToken</see> + /// to a <see cref="T:System.Threading.Tasks.Parallel">Parallel</see> method enables the operation to be + /// exited early. Code external to the operation may cancel the token, and if the operation observes the + /// token being set, it may exit early by throwing an + /// <see cref="T:System.OperationCanceledException"/>. + /// </remarks> + public CancellationToken CancellationToken + { + get { return m_cancellationToken; } + set { m_cancellationToken = value; } + } + + internal int EffectiveMaxConcurrencyLevel + { + get + { + int rval = MaxDegreeOfParallelism; + int schedulerMax = EffectiveTaskScheduler.MaximumConcurrencyLevel; + if ((schedulerMax > 0) && (schedulerMax != Int32.MaxValue)) + { + rval = (rval == -1) ? schedulerMax : Math.Min(schedulerMax, rval); + } + return rval; + } + } + } + + /// <summary> + /// Provides support for parallel loops and regions. + /// </summary> + /// <remarks> + /// The <see cref="T:System.Threading.Tasks.Parallel"/> class provides library-based data parallel replacements + /// for common operations such as for loops, for each loops, and execution of a set of statements. + /// </remarks> + [HostProtection(Synchronization = true, ExternalThreading = true)] + public static class Parallel + { + // static counter for generating unique Fork/Join Context IDs to be used in ETW events + internal static int s_forkJoinContextID; + + // We use a stride for loops to amortize the frequency of interlocked operations. + internal const int DEFAULT_LOOP_STRIDE = 16; + + // Static variable to hold default parallel options + internal static ParallelOptions s_defaultParallelOptions = new ParallelOptions(); + + /// <summary> + /// Executes each of the provided actions, possibly in parallel. + /// </summary> + /// <param name="actions">An array of <see cref="T:System.Action">Actions</see> to execute.</param> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the + /// <paramref name="actions"/> argument is null.</exception> + /// <exception cref="T:System.ArgumentException">The exception that is thrown when the + /// <paramref name="actions"/> array contains a null element.</exception> + /// <exception cref="T:System.AggregateException">The exception that is thrown when any + /// action in the <paramref name="actions"/> array throws an exception.</exception> + /// <remarks> + /// This method can be used to execute a set of operations, potentially in parallel. + /// No guarantees are made about the order in which the operations execute or whether + /// they execute in parallel. This method does not return until each of the + /// provided operations has completed, regardless of whether completion + /// occurs due to normal or exceptional termination. + /// </remarks> + public static void Invoke(params Action[] actions) + { + Invoke(s_defaultParallelOptions, actions); + } + + /// <summary> + /// Executes each of the provided actions, possibly in parallel. + /// </summary> + /// <param name="parallelOptions">A <see cref="T:System.Threading.Tasks.ParallelOptions">ParallelOptions</see> + /// instance that configures the behavior of this operation.</param> + /// <param name="actions">An array of <see cref="T:System.Action">Actions</see> to execute.</param> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the + /// <paramref name="actions"/> argument is null.</exception> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the + /// <paramref name="parallelOptions"/> argument is null.</exception> + /// <exception cref="T:System.ArgumentException">The exception that is thrown when the + /// <paramref name="actions"/> array contains a null element.</exception> + /// <exception cref="T:System.OperationCanceledException">The exception that is thrown when + /// the <see cref="T:System.Threading.CancellationToken">CancellationToken</see> in the + /// <paramref name="parallelOptions"/> is set.</exception> + /// <exception cref="T:System.AggregateException">The exception that is thrown when any + /// action in the <paramref name="actions"/> array throws an exception.</exception> + /// <exception cref="T:System.ObjectDisposedException">The exception that is thrown when the + /// the <see cref="T:System.Threading.CancellationTokenSource">CancellationTokenSource</see> associated with the + /// the <see cref="T:System.Threading.CancellationToken">CancellationToken</see> in the + /// <paramref name="parallelOptions"/> has been disposed.</exception> + /// <remarks> + /// This method can be used to execute a set of operations, potentially in parallel. + /// No guarantees are made about the order in which the operations execute or whether + /// the they execute in parallel. This method does not return until each of the + /// provided operations has completed, regardless of whether completion + /// occurs due to normal or exceptional termination. + /// </remarks> + public static void Invoke(ParallelOptions parallelOptions, params Action[] actions) + { + if (actions == null) + { + throw new ArgumentNullException("actions"); + } + if (parallelOptions == null) + { + throw new ArgumentNullException("parallelOptions"); + } + + // Throw an ODE if we're passed a disposed CancellationToken. + if (parallelOptions.CancellationToken.CanBeCanceled + && AppContextSwitches.ThrowExceptionIfDisposedCancellationTokenSource) + { + parallelOptions.CancellationToken.ThrowIfSourceDisposed(); + } + // Quit early if we're already canceled -- avoid a bunch of work. + if (parallelOptions.CancellationToken.IsCancellationRequested) + throw new OperationCanceledException(parallelOptions.CancellationToken); + + // We must validate that the actions array contains no null elements, and also + // make a defensive copy of the actions array. + Action[] actionsCopy = new Action[actions.Length]; + for (int i = 0; i < actionsCopy.Length; i++) + { + actionsCopy[i] = actions[i]; + if (actionsCopy[i] == null) + { + throw new ArgumentException(Environment.GetResourceString("Parallel_Invoke_ActionNull")); + } + } + + // ETW event for Parallel Invoke Begin + int forkJoinContextID = 0; + Task callerTask = null; + if (TplEtwProvider.Log.IsEnabled()) + { + forkJoinContextID = Interlocked.Increment(ref s_forkJoinContextID); + callerTask = Task.InternalCurrent; + TplEtwProvider.Log.ParallelInvokeBegin((callerTask != null ? callerTask.m_taskScheduler.Id : TaskScheduler.Current.Id), (callerTask != null ? callerTask.Id : 0), + forkJoinContextID, TplEtwProvider.ForkJoinOperationType.ParallelInvoke, + actionsCopy.Length); + } + +#if DEBUG + actions = null; // Ensure we don't accidentally use this below. +#endif + + // If we have no work to do, we are done. + if (actionsCopy.Length < 1) return; + + // In the algorithm below, if the number of actions is greater than this, we automatically + // use Parallel.For() to handle the actions, rather than the Task-per-Action strategy. + const int SMALL_ACTIONCOUNT_LIMIT = 10; + + try + { + // If we've gotten this far, it's time to process the actions. + if ((actionsCopy.Length > SMALL_ACTIONCOUNT_LIMIT) || + (parallelOptions.MaxDegreeOfParallelism != -1 && parallelOptions.MaxDegreeOfParallelism < actionsCopy.Length)) + { + // Used to hold any exceptions encountered during action processing + ConcurrentQueue<Exception> exceptionQ = null; // will be lazily initialized if necessary + + // This is more efficient for a large number of actions, or for enforcing MaxDegreeOfParallelism. + try + { + // Launch a self-replicating task to handle the execution of all actions. + // The use of a self-replicating task allows us to use as many cores + // as are available, and no more. The exception to this rule is + // that, in the case of a blocked action, the ThreadPool may inject + // extra threads, which means extra tasks can run. + int actionIndex = 0; + ParallelForReplicatingTask rootTask = new ParallelForReplicatingTask(parallelOptions, delegate + { + // Each for-task will pull an action at a time from the list + int myIndex = Interlocked.Increment(ref actionIndex); // = index to use + 1 + while (myIndex <= actionsCopy.Length) + { + // Catch and store any exceptions. If we don't catch them, the self-replicating + // task will exit, and that may cause other SR-tasks to exit. + // And (absent cancellation) we want all actions to execute. + try + { + actionsCopy[myIndex - 1](); + } + catch (Exception e) + { + LazyInitializer.EnsureInitialized<ConcurrentQueue<Exception>>(ref exceptionQ, () => { return new ConcurrentQueue<Exception>(); }); + exceptionQ.Enqueue(e); + } + + // Check for cancellation. If it is encountered, then exit the delegate. + if (parallelOptions.CancellationToken.IsCancellationRequested) + throw new OperationCanceledException(parallelOptions.CancellationToken); + + // You're still in the game. Grab your next action index. + myIndex = Interlocked.Increment(ref actionIndex); + } + }, TaskCreationOptions.None, InternalTaskOptions.SelfReplicating); + + rootTask.RunSynchronously(parallelOptions.EffectiveTaskScheduler); + rootTask.Wait(); + } + catch (Exception e) + { + LazyInitializer.EnsureInitialized<ConcurrentQueue<Exception>>(ref exceptionQ, () => { return new ConcurrentQueue<Exception>(); }); + + // Since we're consuming all action exceptions, there are very few reasons that + // we would see an exception here. Two that come to mind: + // (1) An OCE thrown by one or more actions (AggregateException thrown) + // (2) An exception thrown from the ParallelForReplicatingTask constructor + // (regular exception thrown). + // We'll need to cover them both. + AggregateException ae = e as AggregateException; + if (ae != null) + { + // Strip off outer container of an AggregateException, because downstream + // logic needs OCEs to be at the top level. + foreach (Exception exc in ae.InnerExceptions) exceptionQ.Enqueue(exc); + } + else + { + exceptionQ.Enqueue(e); + } + } + + // If we have encountered any exceptions, then throw. + if ((exceptionQ != null) && (exceptionQ.Count > 0)) + { + ThrowIfReducableToSingleOCE(exceptionQ, parallelOptions.CancellationToken); + throw new AggregateException(exceptionQ); + } + } + else + { + // This is more efficient for a small number of actions and no DOP support + + // Initialize our array of tasks, one per action. + Task[] tasks = new Task[actionsCopy.Length]; + + // One more check before we begin... + if (parallelOptions.CancellationToken.IsCancellationRequested) + throw new OperationCanceledException(parallelOptions.CancellationToken); + + // Launch all actions as tasks + for (int i = 1; i < tasks.Length; i++) + { + tasks[i] = Task.Factory.StartNew(actionsCopy[i], parallelOptions.CancellationToken, TaskCreationOptions.None, + InternalTaskOptions.None, parallelOptions.EffectiveTaskScheduler); + } + + // Optimization: Use current thread to run something before we block waiting for all tasks. + tasks[0] = new Task(actionsCopy[0]); + tasks[0].RunSynchronously(parallelOptions.EffectiveTaskScheduler); + + // Now wait for the tasks to complete. This will not unblock until all of + // them complete, and it will throw an exception if one or more of them also + // threw an exception. We let such exceptions go completely unhandled. + try + { + if (tasks.Length <= 4) + { + // for 4 or less tasks, the sequential waitall version is faster + Task.FastWaitAll(tasks); + } + else + { + // otherwise we revert to the regular WaitAll which delegates the multiple wait to the cooperative event. + Task.WaitAll(tasks); + } + } + catch (AggregateException aggExp) + { + // see if we can combine it into a single OCE. If not propagate the original exception + ThrowIfReducableToSingleOCE(aggExp.InnerExceptions, parallelOptions.CancellationToken); + throw; + } + finally + { + for (int i = 0; i < tasks.Length; i++) + { + if (tasks[i].IsCompleted) tasks[i].Dispose(); + } + } + } + } + finally + { + // ETW event for Parallel Invoke End + if (TplEtwProvider.Log.IsEnabled()) + { + TplEtwProvider.Log.ParallelInvokeEnd((callerTask != null ? callerTask.m_taskScheduler.Id : TaskScheduler.Current.Id), (callerTask != null ? callerTask.Id : 0), + forkJoinContextID); + } + } + } + + /// <summary> + /// Executes a for loop in which iterations may run in parallel. + /// </summary> + /// <param name="fromInclusive">The start index, inclusive.</param> + /// <param name="toExclusive">The end index, exclusive.</param> + /// <param name="body">The delegate that is invoked once per iteration.</param> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the <paramref name="body"/> + /// argument is null.</exception> + /// <exception cref="T:System.AggregateException">The exception that is thrown to contain an exception + /// thrown from one of the specified delegates.</exception> + /// <returns>A <see cref="T:System.Threading.Tasks.ParallelLoopResult">ParallelLoopResult</see> structure + /// that contains information on what portion of the loop completed.</returns> + /// <remarks> + /// The <paramref name="body"/> delegate is invoked once for each value in the iteration range: + /// [fromInclusive, toExclusive). It is provided with the iteration count (an Int32) as a parameter. + /// </remarks> + public static ParallelLoopResult For(int fromInclusive, int toExclusive, Action<int> body) + { + if (body == null) + { + throw new ArgumentNullException("body"); + } + + return ForWorker<object>( + fromInclusive, toExclusive, + s_defaultParallelOptions, + body, null, null, null, null); + } + + /// <summary> + /// Executes a for loop in which iterations may run in parallel. + /// </summary> + /// <param name="fromInclusive">The start index, inclusive.</param> + /// <param name="toExclusive">The end index, exclusive.</param> + /// <param name="body">The delegate that is invoked once per iteration.</param> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the <paramref name="body"/> + /// argument is null.</exception> + /// <exception cref="T:System.AggregateException">The exception that is thrown to contain an exception + /// thrown from one of the specified delegates.</exception> + /// <returns>A <see cref="T:System.Threading.Tasks.ParallelLoopResult">ParallelLoopResult</see> structure + /// that contains information on what portion of the loop completed.</returns> + /// <remarks> + /// The <paramref name="body"/> delegate is invoked once for each value in the iteration range: + /// [fromInclusive, toExclusive). It is provided with the iteration count (an Int64) as a parameter. + /// </remarks> + public static ParallelLoopResult For(long fromInclusive, long toExclusive, Action<long> body) + { + if (body == null) + { + throw new ArgumentNullException("body"); + } + + return ForWorker64<object>( + fromInclusive, toExclusive, s_defaultParallelOptions, + body, null, null, null, null); + } + + /// <summary> + /// Executes a for loop in which iterations may run in parallel. + /// </summary> + /// <param name="fromInclusive">The start index, inclusive.</param> + /// <param name="toExclusive">The end index, exclusive.</param> + /// <param name="parallelOptions">A <see cref="T:System.Threading.Tasks.ParallelOptions">ParallelOptions</see> + /// instance that configures the behavior of this operation.</param> + /// <param name="body">The delegate that is invoked once per iteration.</param> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the <paramref name="body"/> + /// argument is null.</exception> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the + /// <paramref name="parallelOptions"/> argument is null.</exception> + /// <exception cref="T:System.OperationCanceledException">The exception that is thrown when the + /// <see cref="T:System.Threading.CancellationToken">CancellationToken</see> in the <paramref name="parallelOptions"/> + /// argument is set.</exception> + /// <exception cref="T:System.AggregateException">The exception that is thrown to contain an exception + /// thrown from one of the specified delegates.</exception> + /// <exception cref="T:System.ObjectDisposedException">The exception that is thrown when the + /// the <see cref="T:System.Threading.CancellationTokenSource">CancellationTokenSource</see> associated with the + /// the <see cref="T:System.Threading.CancellationToken">CancellationToken</see> in the + /// <paramref name="parallelOptions"/> has been disposed.</exception> + /// <returns>A <see cref="T:System.Threading.Tasks.ParallelLoopResult">ParallelLoopResult</see> structure + /// that contains information on what portion of the loop completed.</returns> + /// <remarks> + /// The <paramref name="body"/> delegate is invoked once for each value in the iteration range: + /// [fromInclusive, toExclusive). It is provided with the iteration count (an Int32) as a parameter. + /// </remarks> + public static ParallelLoopResult For(int fromInclusive, int toExclusive, ParallelOptions parallelOptions, Action<int> body) + { + if (body == null) + { + throw new ArgumentNullException("body"); + } + if (parallelOptions == null) + { + throw new ArgumentNullException("parallelOptions"); + } + + return ForWorker<object>( + fromInclusive, toExclusive, parallelOptions, + body, null, null, null, null); + } + + /// <summary> + /// Executes a for loop in which iterations may run in parallel. + /// </summary> + /// <param name="fromInclusive">The start index, inclusive.</param> + /// <param name="toExclusive">The end index, exclusive.</param> + /// <param name="parallelOptions">A <see cref="T:System.Threading.Tasks.ParallelOptions">ParallelOptions</see> + /// instance that configures the behavior of this operation.</param> + /// <param name="body">The delegate that is invoked once per iteration.</param> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the <paramref name="body"/> + /// argument is null.</exception> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the + /// <paramref name="parallelOptions"/> argument is null.</exception> + /// <exception cref="T:System.OperationCanceledException">The exception that is thrown when the + /// <see cref="T:System.Threading.CancellationToken">CancellationToken</see> in the <paramref name="parallelOptions"/> + /// argument is set.</exception> + /// <exception cref="T:System.AggregateException">The exception that is thrown to contain an exception + /// thrown from one of the specified delegates.</exception> + /// <exception cref="T:System.ObjectDisposedException">The exception that is thrown when the + /// the <see cref="T:System.Threading.CancellationTokenSource">CancellationTokenSource</see> associated with the + /// the <see cref="T:System.Threading.CancellationToken">CancellationToken</see> in the + /// <paramref name="parallelOptions"/> has been disposed.</exception> + /// <returns>A <see cref="T:System.Threading.Tasks.ParallelLoopResult">ParallelLoopResult</see> structure + /// that contains information on what portion of the loop completed.</returns> + /// <remarks> + /// The <paramref name="body"/> delegate is invoked once for each value in the iteration range: + /// [fromInclusive, toExclusive). It is provided with the iteration count (an Int64) as a parameter. + /// </remarks> + public static ParallelLoopResult For(long fromInclusive, long toExclusive, ParallelOptions parallelOptions, Action<long> body) + { + if (body == null) + { + throw new ArgumentNullException("body"); + } + if (parallelOptions == null) + { + throw new ArgumentNullException("parallelOptions"); + } + + return ForWorker64<object>( + fromInclusive, toExclusive, parallelOptions, + body, null, null, null, null); + } + + /// <summary> + /// Executes a for loop in which iterations may run in parallel. + /// </summary> + /// <param name="fromInclusive">The start index, inclusive.</param> + /// <param name="toExclusive">The end index, exclusive.</param> + /// <param name="body">The delegate that is invoked once per iteration.</param> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the <paramref name="body"/> + /// argument is null.</exception> + /// <exception cref="T:System.AggregateException">The exception that is thrown to contain an exception + /// thrown from one of the specified delegates.</exception> + /// <returns>A <see cref="T:System.Threading.Tasks.ParallelLoopResult">ParallelLoopResult</see> structure + /// that contains information on what portion of the loop completed.</returns> + /// <remarks> + /// <para> + /// The <paramref name="body"/> delegate is invoked once for each value in the iteration range: + /// [fromInclusive, toExclusive). It is provided with the following parameters: the iteration count (an Int32), + /// and a <see cref="System.Threading.Tasks.ParallelLoopState">ParallelLoopState</see> instance that may be + /// used to break out of the loop prematurely. + /// </para> + /// <para> + /// Calling <see cref="System.Threading.Tasks.ParallelLoopState.Break()">ParallelLoopState.Break()</see> + /// informs the For operation that iterations after the current one need not + /// execute. However, all iterations before the current one will still need to be executed if they have not already. + /// Therefore, calling Break is similar to using a break operation within a + /// conventional for loop in a language like C#, but it is not a perfect substitute: for example, there is no guarantee that iterations + /// after the current one will definitely not execute. + /// </para> + /// <para> + /// If executing all iterations before the current one is not necessary, + /// <see cref="System.Threading.Tasks.ParallelLoopState.Stop()">ParallelLoopState.Stop()</see> + /// should be preferred to using Break. Calling Stop informs the For loop that it may abandon all remaining + /// iterations, regardless of whether they're for interations above or below the current, + /// since all required work has already been completed. As with Break, however, there are no guarantees regarding + /// which other iterations will not execute. + /// </para> + /// <para> + /// When a loop is ended prematurely, the <see cref="T:ParallelLoopState"/> that's returned will contain + /// relevant information about the loop's completion. + /// </para> + /// </remarks> + public static ParallelLoopResult For(int fromInclusive, int toExclusive, Action<int, ParallelLoopState> body) + { + if (body == null) + { + throw new ArgumentNullException("body"); + } + + return ForWorker<object>( + fromInclusive, toExclusive, s_defaultParallelOptions, + null, body, null, null, null); + } + + /// <summary> + /// Executes a for loop in which iterations may run in parallel. + /// </summary> + /// <param name="fromInclusive">The start index, inclusive.</param> + /// <param name="toExclusive">The end index, exclusive.</param> + /// <param name="body">The delegate that is invoked once per iteration.</param> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the <paramref name="body"/> + /// argument is null.</exception> + /// <exception cref="T:System.AggregateException">The exception that is thrown to contain an exception + /// thrown from one of the specified delegates.</exception> + /// <returns>A <see cref="T:System.Threading.Tasks.ParallelLoopResult">ParallelLoopResult</see> structure + /// that contains information on what portion of the loop completed.</returns> + /// <remarks> + /// The <paramref name="body"/> delegate is invoked once for each value in the iteration range: + /// [fromInclusive, toExclusive). It is provided with the following parameters: the iteration count (an Int64), + /// and a <see cref="System.Threading.Tasks.ParallelLoopState">ParallelLoopState</see> instance that may be + /// used to break out of the loop prematurely. + /// </remarks> + public static ParallelLoopResult For(long fromInclusive, long toExclusive, Action<long, ParallelLoopState> body) + { + if (body == null) + { + throw new ArgumentNullException("body"); + } + + return ForWorker64<object>( + fromInclusive, toExclusive, s_defaultParallelOptions, + null, body, null, null, null); + } + + /// <summary> + /// Executes a for loop in which iterations may run in parallel. + /// </summary> + /// <param name="fromInclusive">The start index, inclusive.</param> + /// <param name="toExclusive">The end index, exclusive.</param> + /// <param name="parallelOptions">A <see cref="T:System.Threading.Tasks.ParallelOptions">ParallelOptions</see> + /// instance that configures the behavior of this operation.</param> + /// <param name="body">The delegate that is invoked once per iteration.</param> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the <paramref name="body"/> + /// argument is null.</exception> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the + /// <paramref name="parallelOptions"/> argument is null.</exception> + /// <exception cref="T:System.OperationCanceledException">The exception that is thrown when the + /// <see cref="T:System.Threading.CancellationToken">CancellationToken</see> in the <paramref name="parallelOptions"/> + /// argument is set.</exception> + /// <exception cref="T:System.AggregateException">The exception that is thrown to contain an exception + /// thrown from one of the specified delegates.</exception> + /// <exception cref="T:System.ObjectDisposedException">The exception that is thrown when the + /// the <see cref="T:System.Threading.CancellationTokenSource">CancellationTokenSource</see> associated with the + /// the <see cref="T:System.Threading.CancellationToken">CancellationToken</see> in the + /// <paramref name="parallelOptions"/> has been disposed.</exception> + /// <returns>A <see cref="T:System.Threading.Tasks.ParallelLoopResult">ParallelLoopResult</see> structure + /// that contains information on what portion of the loop completed.</returns> + /// <remarks> + /// The <paramref name="body"/> delegate is invoked once for each value in the iteration range: + /// [fromInclusive, toExclusive). It is provided with the following parameters: the iteration count (an Int32), + /// and a <see cref="System.Threading.Tasks.ParallelLoopState">ParallelLoopState</see> instance that may be + /// used to break out of the loop prematurely. + /// </remarks> + public static ParallelLoopResult For(int fromInclusive, int toExclusive, ParallelOptions parallelOptions, Action<int, ParallelLoopState> body) + { + if (body == null) + { + throw new ArgumentNullException("body"); + } + if (parallelOptions == null) + { + throw new ArgumentNullException("parallelOptions"); + } + + return ForWorker<object>( + fromInclusive, toExclusive, parallelOptions, + null, body, null, null, null); + } + + /// <summary> + /// Executes a for loop in which iterations may run in parallel. + /// </summary> + /// <param name="fromInclusive">The start index, inclusive.</param> + /// <param name="toExclusive">The end index, exclusive.</param> + /// <param name="parallelOptions">A <see cref="T:System.Threading.Tasks.ParallelOptions">ParallelOptions</see> + /// instance that configures the behavior of this operation.</param> + /// <param name="body">The delegate that is invoked once per iteration.</param> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the <paramref name="body"/> + /// argument is null.</exception> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the + /// <paramref name="parallelOptions"/> argument is null.</exception> + /// <exception cref="T:System.OperationCanceledException">The exception that is thrown when the + /// <see cref="T:System.Threading.CancellationToken">CancellationToken</see> in the <paramref name="parallelOptions"/> + /// argument is set.</exception> + /// <exception cref="T:System.AggregateException">The exception that is thrown to contain an exception + /// thrown from one of the specified delegates.</exception> + /// <exception cref="T:System.ObjectDisposedException">The exception that is thrown when the + /// the <see cref="T:System.Threading.CancellationTokenSource">CancellationTokenSource</see> associated with the + /// the <see cref="T:System.Threading.CancellationToken">CancellationToken</see> in the + /// <paramref name="parallelOptions"/> has been disposed.</exception> + /// <returns>A <see cref="T:System.Threading.Tasks.ParallelLoopResult">ParallelLoopResult</see> structure + /// that contains information on what portion of the loop completed.</returns> + /// <remarks> + /// The <paramref name="body"/> delegate is invoked once for each value in the iteration range: + /// [fromInclusive, toExclusive). It is provided with the following parameters: the iteration count (an Int64), + /// and a <see cref="System.Threading.Tasks.ParallelLoopState">ParallelLoopState</see> instance that may be + /// used to break out of the loop prematurely. + /// </remarks> + public static ParallelLoopResult For(long fromInclusive, long toExclusive, ParallelOptions parallelOptions, + Action<long, ParallelLoopState> body) + { + if (body == null) + { + throw new ArgumentNullException("body"); + } + if (parallelOptions == null) + { + throw new ArgumentNullException("parallelOptions"); + } + + return ForWorker64<object>( + fromInclusive, toExclusive, parallelOptions, + null, body, null, null, null); + } + + /// <summary> + /// Executes a for loop in which iterations may run in parallel. + /// </summary> + /// <typeparam name="TLocal">The type of the thread-local data.</typeparam> + /// <param name="fromInclusive">The start index, inclusive.</param> + /// <param name="toExclusive">The end index, exclusive.</param> + /// <param name="localInit">The function delegate that returns the initial state of the local data + /// for each thread.</param> + /// <param name="body">The delegate that is invoked once per iteration.</param> + /// <param name="localFinally">The delegate that performs a final action on the local state of each + /// thread.</param> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the <paramref name="body"/> + /// argument is null.</exception> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the + /// <paramref name="localInit"/> argument is null.</exception> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the + /// <paramref name="localFinally"/> argument is null.</exception> + /// <exception cref="T:System.AggregateException">The exception that is thrown to contain an exception + /// thrown from one of the specified delegates.</exception> + /// <returns>A <see cref="T:System.Threading.Tasks.ParallelLoopResult">ParallelLoopResult</see> structure + /// that contains information on what portion of the loop completed.</returns> + /// <remarks> + /// <para> + /// The <paramref name="body"/> delegate is invoked once for each value in the iteration range: + /// [fromInclusive, toExclusive). It is provided with the following parameters: the iteration count (an Int32), + /// a <see cref="System.Threading.Tasks.ParallelLoopState">ParallelLoopState</see> instance that may be + /// used to break out of the loop prematurely, and some local state that may be shared amongst iterations + /// that execute on the same thread. + /// </para> + /// <para> + /// The <paramref name="localInit"/> delegate is invoked once for each thread that participates in the loop's + /// execution and returns the initial local state for each of those threads. These initial states are passed to the first + /// <paramref name="body"/> invocations on each thread. Then, every subsequent body invocation returns a possibly + /// modified state value that is passed to the next body invocation. Finally, the last body invocation on each thread returns a state value + /// that is passed to the <paramref name="localFinally"/> delegate. The localFinally delegate is invoked once per thread to perform a final + /// action on each thread's local state. + /// </para> + /// </remarks> + public static ParallelLoopResult For<TLocal>( + int fromInclusive, int toExclusive, + Func<TLocal> localInit, + Func<int, ParallelLoopState, TLocal, TLocal> body, + Action<TLocal> localFinally) + { + if (body == null) + { + throw new ArgumentNullException("body"); + } + if (localInit == null) + { + throw new ArgumentNullException("localInit"); + } + if (localFinally == null) + { + throw new ArgumentNullException("localFinally"); + } + + return ForWorker( + fromInclusive, toExclusive, s_defaultParallelOptions, + null, null, body, localInit, localFinally); + } + + /// <summary> + /// Executes a for loop in which iterations may run in parallel. Supports 64-bit indices. + /// </summary> + /// <typeparam name="TLocal">The type of the thread-local data.</typeparam> + /// <param name="fromInclusive">The start index, inclusive.</param> + /// <param name="toExclusive">The end index, exclusive.</param> + /// <param name="localInit">The function delegate that returns the initial state of the local data + /// for each thread.</param> + /// <param name="body">The delegate that is invoked once per iteration.</param> + /// <param name="localFinally">The delegate that performs a final action on the local state of each + /// thread.</param> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the <paramref name="body"/> + /// argument is null.</exception> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the + /// <paramref name="localInit"/> argument is null.</exception> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the + /// <paramref name="localFinally"/> argument is null.</exception> + /// <exception cref="T:System.AggregateException">The exception that is thrown to contain an exception + /// thrown from one of the specified delegates.</exception> + /// <returns>A <see cref="T:System.Threading.Tasks.ParallelLoopResult">ParallelLoopResult</see> structure + /// that contains information on what portion of the loop completed.</returns> + /// <remarks> + /// <para> + /// The <paramref name="body"/> delegate is invoked once for each value in the iteration range: + /// [fromInclusive, toExclusive). It is provided with the following parameters: the iteration count (an Int64), + /// a <see cref="System.Threading.Tasks.ParallelLoopState">ParallelLoopState</see> instance that may be + /// used to break out of the loop prematurely, and some local state that may be shared amongst iterations + /// that execute on the same thread. + /// </para> + /// <para> + /// The <paramref name="localInit"/> delegate is invoked once for each thread that participates in the loop's + /// execution and returns the initial local state for each of those threads. These initial states are passed to the first + /// <paramref name="body"/> invocations on each thread. Then, every subsequent body invocation returns a possibly + /// modified state value that is passed to the next body invocation. Finally, the last body invocation on each thread returns a state value + /// that is passed to the <paramref name="localFinally"/> delegate. The localFinally delegate is invoked once per thread to perform a final + /// action on each thread's local state. + /// </para> + /// </remarks> + public static ParallelLoopResult For<TLocal>( + long fromInclusive, long toExclusive, + Func<TLocal> localInit, + Func<long, ParallelLoopState, TLocal, TLocal> body, + Action<TLocal> localFinally) + { + if (body == null) + { + throw new ArgumentNullException("body"); + } + if (localInit == null) + { + throw new ArgumentNullException("localInit"); + } + if (localFinally == null) + { + throw new ArgumentNullException("localFinally"); + } + + return ForWorker64( + fromInclusive, toExclusive, s_defaultParallelOptions, + null, null, body, localInit, localFinally); + } + + /// <summary> + /// Executes a for loop in which iterations may run in parallel. + /// </summary> + /// <typeparam name="TLocal">The type of the thread-local data.</typeparam> + /// <param name="fromInclusive">The start index, inclusive.</param> + /// <param name="toExclusive">The end index, exclusive.</param> + /// <param name="parallelOptions">A <see cref="T:System.Threading.Tasks.ParallelOptions">ParallelOptions</see> + /// instance that configures the behavior of this operation.</param> + /// <param name="localInit">The function delegate that returns the initial state of the local data + /// for each thread.</param> + /// <param name="body">The delegate that is invoked once per iteration.</param> + /// <param name="localFinally">The delegate that performs a final action on the local state of each + /// thread.</param> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the <paramref name="body"/> + /// argument is null.</exception> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the + /// <paramref name="localInit"/> argument is null.</exception> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the + /// <paramref name="localFinally"/> argument is null.</exception> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the + /// <paramref name="parallelOptions"/> argument is null.</exception> + /// <exception cref="T:System.OperationCanceledException">The exception that is thrown when the + /// <see cref="T:System.Threading.CancellationToken">CancellationToken</see> in the <paramref name="parallelOptions"/> + /// argument is set.</exception> + /// <exception cref="T:System.AggregateException">The exception that is thrown to contain an exception + /// thrown from one of the specified delegates.</exception> + /// <exception cref="T:System.ObjectDisposedException">The exception that is thrown when the + /// the <see cref="T:System.Threading.CancellationTokenSource">CancellationTokenSource</see> associated with the + /// the <see cref="T:System.Threading.CancellationToken">CancellationToken</see> in the + /// <paramref name="parallelOptions"/> has been disposed.</exception> + /// <returns>A <see cref="T:System.Threading.Tasks.ParallelLoopResult">ParallelLoopResult</see> structure + /// that contains information on what portion of the loop completed.</returns> + /// <remarks> + /// <para> + /// The <paramref name="body"/> delegate is invoked once for each value in the iteration range: + /// [fromInclusive, toExclusive). It is provided with the following parameters: the iteration count (an Int32), + /// a <see cref="System.Threading.Tasks.ParallelLoopState">ParallelLoopState</see> instance that may be + /// used to break out of the loop prematurely, and some local state that may be shared amongst iterations + /// that execute on the same thread. + /// </para> + /// <para> + /// The <paramref name="localInit"/> delegate is invoked once for each thread that participates in the loop's + /// execution and returns the initial local state for each of those threads. These initial states are passed to the first + /// <paramref name="body"/> invocations on each thread. Then, every subsequent body invocation returns a possibly + /// modified state value that is passed to the next body invocation. Finally, the last body invocation on each thread returns a state value + /// that is passed to the <paramref name="localFinally"/> delegate. The localFinally delegate is invoked once per thread to perform a final + /// action on each thread's local state. + /// </para> + /// </remarks> + public static ParallelLoopResult For<TLocal>( + int fromInclusive, int toExclusive, ParallelOptions parallelOptions, + Func<TLocal> localInit, + Func<int, ParallelLoopState, TLocal, TLocal> body, + Action<TLocal> localFinally) + { + if (body == null) + { + throw new ArgumentNullException("body"); + } + if (localInit == null) + { + throw new ArgumentNullException("localInit"); + } + if (localFinally == null) + { + throw new ArgumentNullException("localFinally"); + } + if (parallelOptions == null) + { + throw new ArgumentNullException("parallelOptions"); + } + + return ForWorker( + fromInclusive, toExclusive, parallelOptions, + null, null, body, localInit, localFinally); + } + + /// <summary> + /// Executes a for loop in which iterations may run in parallel. + /// </summary> + /// <typeparam name="TLocal">The type of the thread-local data.</typeparam> + /// <param name="fromInclusive">The start index, inclusive.</param> + /// <param name="toExclusive">The end index, exclusive.</param> + /// <param name="parallelOptions">A <see cref="T:System.Threading.Tasks.ParallelOptions">ParallelOptions</see> + /// instance that configures the behavior of this operation.</param> + /// <param name="localInit">The function delegate that returns the initial state of the local data + /// for each thread.</param> + /// <param name="body">The delegate that is invoked once per iteration.</param> + /// <param name="localFinally">The delegate that performs a final action on the local state of each + /// thread.</param> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the <paramref name="body"/> + /// argument is null.</exception> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the + /// <paramref name="localInit"/> argument is null.</exception> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the + /// <paramref name="localFinally"/> argument is null.</exception> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the + /// <paramref name="parallelOptions"/> argument is null.</exception> + /// <exception cref="T:System.OperationCanceledException">The exception that is thrown when the + /// <see cref="T:System.Threading.CancellationToken">CancellationToken</see> in the <paramref name="parallelOptions"/> + /// argument is set.</exception> + /// <exception cref="T:System.AggregateException">The exception that is thrown to contain an exception + /// thrown from one of the specified delegates.</exception> + /// <exception cref="T:System.ObjectDisposedException">The exception that is thrown when the + /// the <see cref="T:System.Threading.CancellationTokenSource">CancellationTokenSource</see> associated with the + /// the <see cref="T:System.Threading.CancellationToken">CancellationToken</see> in the + /// <paramref name="parallelOptions"/> has been disposed.</exception> + /// <returns>A <see cref="T:System.Threading.Tasks.ParallelLoopResult">ParallelLoopResult</see> structure + /// that contains information on what portion of the loop completed.</returns> + /// <remarks> + /// <para> + /// The <paramref name="body"/> delegate is invoked once for each value in the iteration range: + /// [fromInclusive, toExclusive). It is provided with the following parameters: the iteration count (an Int64), + /// a <see cref="System.Threading.Tasks.ParallelLoopState">ParallelLoopState</see> instance that may be + /// used to break out of the loop prematurely, and some local state that may be shared amongst iterations + /// that execute on the same thread. + /// </para> + /// <para> + /// The <paramref name="localInit"/> delegate is invoked once for each thread that participates in the loop's + /// execution and returns the initial local state for each of those threads. These initial states are passed to the first + /// <paramref name="body"/> invocations on each thread. Then, every subsequent body invocation returns a possibly + /// modified state value that is passed to the next body invocation. Finally, the last body invocation on each thread returns a state value + /// that is passed to the <paramref name="localFinally"/> delegate. The localFinally delegate is invoked once per thread to perform a final + /// action on each thread's local state. + /// </para> + /// </remarks> + public static ParallelLoopResult For<TLocal>( + long fromInclusive, long toExclusive, ParallelOptions parallelOptions, + Func<TLocal> localInit, + Func<long, ParallelLoopState, TLocal, TLocal> body, + Action<TLocal> localFinally) + { + if (body == null) + { + throw new ArgumentNullException("body"); + } + if (localInit == null) + { + throw new ArgumentNullException("localInit"); + } + if (localFinally == null) + { + throw new ArgumentNullException("localFinally"); + } + if (parallelOptions == null) + { + throw new ArgumentNullException("parallelOptions"); + } + + + return ForWorker64( + fromInclusive, toExclusive, parallelOptions, + null, null, body, localInit, localFinally); + } + + + + + + + + /// <summary> + /// Performs the major work of the parallel for loop. It assumes that argument validation has already + /// been performed by the caller. This function's whole purpose in life is to enable as much reuse of + /// common implementation details for the various For overloads we offer. Without it, we'd end up + /// with lots of duplicate code. It handles: (1) simple for loops, (2) for loops that depend on + /// ParallelState, and (3) for loops with thread local data. + /// + /// </summary> + /// <typeparam name="TLocal">The type of the local data.</typeparam> + /// <param name="fromInclusive">The loop's start index, inclusive.</param> + /// <param name="toExclusive">The loop's end index, exclusive.</param> + /// <param name="parallelOptions">A ParallelOptions instance.</param> + /// <param name="body">The simple loop body.</param> + /// <param name="bodyWithState">The loop body for ParallelState overloads.</param> + /// <param name="bodyWithLocal">The loop body for thread local state overloads.</param> + /// <param name="localInit">A selector function that returns new thread local state.</param> + /// <param name="localFinally">A cleanup function to destroy thread local state.</param> + /// <remarks>Only one of the body arguments may be supplied (i.e. they are exclusive).</remarks> + /// <returns>A <see cref="T:System.Threading.Tasks.ParallelLoopResult"/> structure.</returns> + private static ParallelLoopResult ForWorker<TLocal>( + int fromInclusive, int toExclusive, + ParallelOptions parallelOptions, + Action<int> body, + Action<int, ParallelLoopState> bodyWithState, + Func<int, ParallelLoopState, TLocal, TLocal> bodyWithLocal, + Func<TLocal> localInit, Action<TLocal> localFinally) + { + Contract.Assert(((body == null ? 0 : 1) + (bodyWithState == null ? 0 : 1) + (bodyWithLocal == null ? 0 : 1)) == 1, + "expected exactly one body function to be supplied"); + Contract.Assert(bodyWithLocal != null || (localInit == null && localFinally == null), + "thread local functions should only be supplied for loops w/ thread local bodies"); + + // Instantiate our result. Specifics will be filled in later. + ParallelLoopResult result = new ParallelLoopResult(); + + // We just return immediately if 'to' is smaller (or equal to) 'from'. + if (toExclusive <= fromInclusive) + { + result.m_completed = true; + return result; + } + + // For all loops we need a shared flag even though we don't have a body with state, + // because the shared flag contains the exceptional bool, which triggers other workers + // to exit their loops if one worker catches an exception + ParallelLoopStateFlags32 sharedPStateFlags = new ParallelLoopStateFlags32(); + + TaskCreationOptions creationOptions = TaskCreationOptions.None; + InternalTaskOptions internalOptions = InternalTaskOptions.SelfReplicating; + + // Before getting started, do a quick peek to see if we have been canceled already + if (parallelOptions.CancellationToken.IsCancellationRequested) + { + throw new OperationCanceledException(parallelOptions.CancellationToken); + } + + // initialize ranges with passed in loop arguments and expected number of workers + int numExpectedWorkers = (parallelOptions.EffectiveMaxConcurrencyLevel == -1) ? + PlatformHelper.ProcessorCount : + parallelOptions.EffectiveMaxConcurrencyLevel; + RangeManager rangeManager = new RangeManager(fromInclusive, toExclusive, 1, numExpectedWorkers); + + // Keep track of any cancellations + OperationCanceledException oce = null; + + CancellationTokenRegistration ctr = new CancellationTokenRegistration(); + + // if cancellation is enabled, we need to register a callback to stop the loop when it gets signaled + if (parallelOptions.CancellationToken.CanBeCanceled) + { + ctr = parallelOptions.CancellationToken.InternalRegisterWithoutEC((o) => + { + // Cause processing to stop + sharedPStateFlags.Cancel(); + // Record our cancellation + oce = new OperationCanceledException(parallelOptions.CancellationToken); + }, null); + } + + // ETW event for Parallel For begin + int forkJoinContextID = 0; + Task callingTask = null; + if (TplEtwProvider.Log.IsEnabled()) + { + forkJoinContextID = Interlocked.Increment(ref s_forkJoinContextID); + callingTask = Task.InternalCurrent; + TplEtwProvider.Log.ParallelLoopBegin((callingTask != null ? callingTask.m_taskScheduler.Id : TaskScheduler.Current.Id), (callingTask != null ? callingTask.Id : 0), + forkJoinContextID, TplEtwProvider.ForkJoinOperationType.ParallelFor, + fromInclusive, toExclusive); + } + + ParallelForReplicatingTask rootTask = null; + + try + { + // this needs to be in try-block because it can throw in BuggyScheduler.MaxConcurrencyLevel + rootTask = new ParallelForReplicatingTask( + parallelOptions, + delegate + { + // + // first thing we do upon enterying the task is to register as a new "RangeWorker" with the + // shared RangeManager instance. + // + // If this call returns a RangeWorker struct which wraps the state needed by this task + // + // We need to call FindNewWork32() on it to see whether there's a chunk available. + // + + + // Cache some information about the current task + Task currentWorkerTask = Task.InternalCurrent; + bool bIsRootTask = (currentWorkerTask == rootTask); + + RangeWorker currentWorker = new RangeWorker(); + Object savedStateFromPreviousReplica = currentWorkerTask.SavedStateFromPreviousReplica; + + if (savedStateFromPreviousReplica is RangeWorker) + currentWorker = (RangeWorker)savedStateFromPreviousReplica; + else + currentWorker = rangeManager.RegisterNewWorker(); + + + + // These are the local index values to be used in the sequential loop. + // Their values filled in by FindNewWork32 + int nFromInclusiveLocal; + int nToExclusiveLocal; + + if (currentWorker.FindNewWork32(out nFromInclusiveLocal, out nToExclusiveLocal) == false || + sharedPStateFlags.ShouldExitLoop(nFromInclusiveLocal) == true) + { + return; // no need to run + } + + // ETW event for ParallelFor Worker Fork + if (TplEtwProvider.Log.IsEnabled()) + { + TplEtwProvider.Log.ParallelFork((currentWorkerTask != null ? currentWorkerTask.m_taskScheduler.Id : TaskScheduler.Current.Id), (currentWorkerTask != null ? currentWorkerTask.Id : 0), + forkJoinContextID); + } + + TLocal localValue = default(TLocal); + bool bLocalValueInitialized = false; // Tracks whether localInit ran without exceptions, so that we can skip localFinally if it wasn't + + try + { + // Create a new state object that references the shared "stopped" and "exceptional" flags + // If needed, it will contain a new instance of thread-local state by invoking the selector. + ParallelLoopState32 state = null; + + if (bodyWithState != null) + { + Contract.Assert(sharedPStateFlags != null); + state = new ParallelLoopState32(sharedPStateFlags); + } + else if (bodyWithLocal != null) + { + Contract.Assert(sharedPStateFlags != null); + state = new ParallelLoopState32(sharedPStateFlags); + if (localInit != null) + { + localValue = localInit(); + bLocalValueInitialized = true; + } + } + + // initialize a loop timer which will help us decide whether we should exit early + LoopTimer loopTimer = new LoopTimer(rootTask.ActiveChildCount); + + // Now perform the loop itself. + do + { + if (body != null) + { + for (int j = nFromInclusiveLocal; + j < nToExclusiveLocal && (sharedPStateFlags.LoopStateFlags == ParallelLoopStateFlags.PLS_NONE // fast path check as SEL() doesn't inline + || !sharedPStateFlags.ShouldExitLoop()); // the no-arg version is used since we have no state + j += 1) + { + + body(j); + } + } + else if (bodyWithState != null) + { + for (int j = nFromInclusiveLocal; + j < nToExclusiveLocal && (sharedPStateFlags.LoopStateFlags == ParallelLoopStateFlags.PLS_NONE // fast path check as SEL() doesn't inline + || !sharedPStateFlags.ShouldExitLoop(j)); + j += 1) + { + + state.CurrentIteration = j; + bodyWithState(j, state); + } + } + else + { + for (int j = nFromInclusiveLocal; + j < nToExclusiveLocal && (sharedPStateFlags.LoopStateFlags == ParallelLoopStateFlags.PLS_NONE // fast path check as SEL() doesn't inline + || !sharedPStateFlags.ShouldExitLoop(j)); + j += 1) + { + state.CurrentIteration = j; + localValue = bodyWithLocal(j, state, localValue); + } + } + + // Cooperative multitasking workaround for AppDomain fairness. + // Check if allowed loop time is exceeded, if so save current state and return. The self replicating task logic + // will detect this, and queue up a replacement task. Note that we don't do this on the root task. + if (!bIsRootTask && loopTimer.LimitExceeded()) + { + currentWorkerTask.SavedStateForNextReplica = (object)currentWorker; + break; + } + + } + // Exit if we can't find new work, or if the loop was stoppped. + while (currentWorker.FindNewWork32(out nFromInclusiveLocal, out nToExclusiveLocal) && + ((sharedPStateFlags.LoopStateFlags == ParallelLoopStateFlags.PLS_NONE) || + !sharedPStateFlags.ShouldExitLoop(nFromInclusiveLocal))); + } + catch + { + // if we catch an exception in a worker, we signal the other workers to exit the loop, and we rethrow + sharedPStateFlags.SetExceptional(); + throw; + } + finally + { + // If a cleanup function was specified, call it. Otherwise, if the type is + // IDisposable, we will invoke Dispose on behalf of the user. + if (localFinally != null && bLocalValueInitialized) + { + localFinally(localValue); + } + + // ETW event for ParallelFor Worker Join + if (TplEtwProvider.Log.IsEnabled()) + { + TplEtwProvider.Log.ParallelJoin((currentWorkerTask != null ? currentWorkerTask.m_taskScheduler.Id : TaskScheduler.Current.Id), (currentWorkerTask != null ? currentWorkerTask.Id : 0), + forkJoinContextID); + } + } + }, + creationOptions, internalOptions); + + rootTask.RunSynchronously(parallelOptions.EffectiveTaskScheduler); // might throw TSE + rootTask.Wait(); + + // If we made a cancellation registration, we need to clean it up now before observing the OCE + // Otherwise we could be caught in the middle of a callback, and observe PLS_STOPPED, but oce = null + if (parallelOptions.CancellationToken.CanBeCanceled) + { + ctr.Dispose(); + } + + // If we got through that with no exceptions, and we were canceled, then + // throw our cancellation exception + if (oce != null) throw oce; + } + catch (AggregateException aggExp) + { + // if we made a cancellation registration, and rootTask.Wait threw, we need to clean it up here + if (parallelOptions.CancellationToken.CanBeCanceled) + { + ctr.Dispose(); + } + + // see if we can combine it into a single OCE. If not propagate the original exception + ThrowIfReducableToSingleOCE(aggExp.InnerExceptions, parallelOptions.CancellationToken); + throw; + } + catch (TaskSchedulerException) + { + // if we made a cancellation registration, and rootTask.RunSynchronously threw, we need to clean it up here + if (parallelOptions.CancellationToken.CanBeCanceled) + { + ctr.Dispose(); + } + throw; + } + finally + { + int sb_status = sharedPStateFlags.LoopStateFlags; + result.m_completed = (sb_status == ParallelLoopStateFlags.PLS_NONE); + if ((sb_status & ParallelLoopStateFlags.PLS_BROKEN) != 0) + { + result.m_lowestBreakIteration = sharedPStateFlags.LowestBreakIteration; + } + + if ((rootTask != null) && rootTask.IsCompleted) rootTask.Dispose(); + + // ETW event for Parallel For End + if (TplEtwProvider.Log.IsEnabled()) + { + int nTotalIterations = 0; + + // calculate how many iterations we ran in total + if (sb_status == ParallelLoopStateFlags.PLS_NONE) + nTotalIterations = toExclusive - fromInclusive; + else if ((sb_status & ParallelLoopStateFlags.PLS_BROKEN) != 0) + nTotalIterations = sharedPStateFlags.LowestBreakIteration - fromInclusive; + else + nTotalIterations = -1; //PLS_STOPPED! We can't determine this if we were stopped.. + + TplEtwProvider.Log.ParallelLoopEnd((callingTask != null ? callingTask.m_taskScheduler.Id : TaskScheduler.Current.Id), (callingTask != null ? callingTask.Id : 0), + forkJoinContextID, nTotalIterations); + } + } + + return result; + } + + /// <summary> + /// Performs the major work of the 64-bit parallel for loop. It assumes that argument validation has already + /// been performed by the caller. This function's whole purpose in life is to enable as much reuse of + /// common implementation details for the various For overloads we offer. Without it, we'd end up + /// with lots of duplicate code. It handles: (1) simple for loops, (2) for loops that depend on + /// ParallelState, and (3) for loops with thread local data. + /// + /// </summary> + /// <typeparam name="TLocal">The type of the local data.</typeparam> + /// <param name="fromInclusive">The loop's start index, inclusive.</param> + /// <param name="toExclusive">The loop's end index, exclusive.</param> + /// <param name="parallelOptions">A ParallelOptions instance.</param> + /// <param name="body">The simple loop body.</param> + /// <param name="bodyWithState">The loop body for ParallelState overloads.</param> + /// <param name="bodyWithLocal">The loop body for thread local state overloads.</param> + /// <param name="localInit">A selector function that returns new thread local state.</param> + /// <param name="localFinally">A cleanup function to destroy thread local state.</param> + /// <remarks>Only one of the body arguments may be supplied (i.e. they are exclusive).</remarks> + /// <returns>A <see cref="T:System.Threading.Tasks.ParallelLoopResult"/> structure.</returns> + private static ParallelLoopResult ForWorker64<TLocal>( + long fromInclusive, long toExclusive, + ParallelOptions parallelOptions, + Action<long> body, + Action<long, ParallelLoopState> bodyWithState, + Func<long, ParallelLoopState, TLocal, TLocal> bodyWithLocal, + Func<TLocal> localInit, Action<TLocal> localFinally) + { + Contract.Assert(((body == null ? 0 : 1) + (bodyWithState == null ? 0 : 1) + (bodyWithLocal == null ? 0 : 1)) == 1, + "expected exactly one body function to be supplied"); + Contract.Assert(bodyWithLocal != null || (localInit == null && localFinally == null), + "thread local functions should only be supplied for loops w/ thread local bodies"); + + // Instantiate our result. Specifics will be filled in later. + ParallelLoopResult result = new ParallelLoopResult(); + + // We just return immediately if 'to' is smaller (or equal to) 'from'. + if (toExclusive <= fromInclusive) + { + result.m_completed = true; + return result; + } + + // For all loops we need a shared flag even though we don't have a body with state, + // because the shared flag contains the exceptional bool, which triggers other workers + // to exit their loops if one worker catches an exception + ParallelLoopStateFlags64 sharedPStateFlags = new ParallelLoopStateFlags64(); + + TaskCreationOptions creationOptions = TaskCreationOptions.None; + InternalTaskOptions internalOptions = InternalTaskOptions.SelfReplicating; + + // Before getting started, do a quick peek to see if we have been canceled already + if (parallelOptions.CancellationToken.IsCancellationRequested) + { + throw new OperationCanceledException(parallelOptions.CancellationToken); + } + + // initialize ranges with passed in loop arguments and expected number of workers + int numExpectedWorkers = (parallelOptions.EffectiveMaxConcurrencyLevel == -1) ? + PlatformHelper.ProcessorCount : + parallelOptions.EffectiveMaxConcurrencyLevel; + RangeManager rangeManager = new RangeManager(fromInclusive, toExclusive, 1, numExpectedWorkers); + + // Keep track of any cancellations + OperationCanceledException oce = null; + + CancellationTokenRegistration ctr = new CancellationTokenRegistration(); + + // if cancellation is enabled, we need to register a callback to stop the loop when it gets signaled + if (parallelOptions.CancellationToken.CanBeCanceled) + { + ctr = parallelOptions.CancellationToken.InternalRegisterWithoutEC((o) => + { + // Cause processing to stop + sharedPStateFlags.Cancel(); + // Record our cancellation + oce = new OperationCanceledException(parallelOptions.CancellationToken); + }, null); + } + + // ETW event for Parallel For begin + Task callerTask = null; + int forkJoinContextID = 0; + if (TplEtwProvider.Log.IsEnabled()) + { + forkJoinContextID = Interlocked.Increment(ref s_forkJoinContextID); + callerTask = Task.InternalCurrent; + TplEtwProvider.Log.ParallelLoopBegin((callerTask != null ? callerTask.m_taskScheduler.Id : TaskScheduler.Current.Id), (callerTask != null ? callerTask.Id : 0), + forkJoinContextID, TplEtwProvider.ForkJoinOperationType.ParallelFor, + fromInclusive, toExclusive); + } + + ParallelForReplicatingTask rootTask = null; + + try + { + // this needs to be in try-block because it can throw in BuggyScheduler.MaxConcurrencyLevel + rootTask = new ParallelForReplicatingTask( + parallelOptions, + delegate + { + // + // first thing we do upon enterying the task is to register as a new "RangeWorker" with the + // shared RangeManager instance. + // + // If this call returns a RangeWorker struct which wraps the state needed by this task + // + // We need to call FindNewWork() on it to see whether there's a chunk available. + // + + // Cache some information about the current task + Task currentWorkerTask = Task.InternalCurrent; + bool bIsRootTask = (currentWorkerTask == rootTask); + + RangeWorker currentWorker = new RangeWorker(); + Object savedStateFromPreviousReplica = currentWorkerTask.SavedStateFromPreviousReplica; + + if (savedStateFromPreviousReplica is RangeWorker) + currentWorker = (RangeWorker)savedStateFromPreviousReplica; + else + currentWorker = rangeManager.RegisterNewWorker(); + + + // These are the local index values to be used in the sequential loop. + // Their values filled in by FindNewWork + long nFromInclusiveLocal; + long nToExclusiveLocal; + + if (currentWorker.FindNewWork(out nFromInclusiveLocal, out nToExclusiveLocal) == false || + sharedPStateFlags.ShouldExitLoop(nFromInclusiveLocal) == true) + { + return; // no need to run + } + + // ETW event for ParallelFor Worker Fork + if (TplEtwProvider.Log.IsEnabled()) + { + TplEtwProvider.Log.ParallelFork((currentWorkerTask != null ? currentWorkerTask.m_taskScheduler.Id : TaskScheduler.Current.Id), (currentWorkerTask != null ? currentWorkerTask.Id : 0), + forkJoinContextID); + } + + TLocal localValue = default(TLocal); + bool bLocalValueInitialized = false; // Tracks whether localInit ran without exceptions, so that we can skip localFinally if it wasn't + + try + { + + // Create a new state object that references the shared "stopped" and "exceptional" flags + // If needed, it will contain a new instance of thread-local state by invoking the selector. + ParallelLoopState64 state = null; + + if (bodyWithState != null) + { + Contract.Assert(sharedPStateFlags != null); + state = new ParallelLoopState64(sharedPStateFlags); + } + else if (bodyWithLocal != null) + { + Contract.Assert(sharedPStateFlags != null); + state = new ParallelLoopState64(sharedPStateFlags); + + // If a thread-local selector was supplied, invoke it. Otherwise, use the default. + if (localInit != null) + { + localValue = localInit(); + bLocalValueInitialized = true; + } + } + + // initialize a loop timer which will help us decide whether we should exit early + LoopTimer loopTimer = new LoopTimer(rootTask.ActiveChildCount); + + // Now perform the loop itself. + do + { + if (body != null) + { + for (long j = nFromInclusiveLocal; + j < nToExclusiveLocal && (sharedPStateFlags.LoopStateFlags == ParallelLoopStateFlags.PLS_NONE // fast path check as SEL() doesn't inline + || !sharedPStateFlags.ShouldExitLoop()); // the no-arg version is used since we have no state + j += 1) + { + body(j); + } + } + else if (bodyWithState != null) + { + for (long j = nFromInclusiveLocal; + j < nToExclusiveLocal && (sharedPStateFlags.LoopStateFlags == ParallelLoopStateFlags.PLS_NONE // fast path check as SEL() doesn't inline + || !sharedPStateFlags.ShouldExitLoop(j)); + j += 1) + { + state.CurrentIteration = j; + bodyWithState(j, state); + } + } + else + { + for (long j = nFromInclusiveLocal; + j < nToExclusiveLocal && (sharedPStateFlags.LoopStateFlags == ParallelLoopStateFlags.PLS_NONE // fast path check as SEL() doesn't inline + || !sharedPStateFlags.ShouldExitLoop(j)); + j += 1) + { + state.CurrentIteration = j; + localValue = bodyWithLocal(j, state, localValue); + } + } + + // Cooperative multitasking workaround for AppDomain fairness. + // Check if allowed loop time is exceeded, if so save current state and return. The self replicating task logic + // will detect this, and queue up a replacement task. Note that we don't do this on the root task. + if (!bIsRootTask && loopTimer.LimitExceeded()) + { + currentWorkerTask.SavedStateForNextReplica = (object)currentWorker; + break; + } + } + // Exit if we can't find new work, or if the loop was stoppped. + while (currentWorker.FindNewWork(out nFromInclusiveLocal, out nToExclusiveLocal) && + ((sharedPStateFlags.LoopStateFlags == ParallelLoopStateFlags.PLS_NONE) || + !sharedPStateFlags.ShouldExitLoop(nFromInclusiveLocal))); + } + catch + { + // if we catch an exception in a worker, we signal the other workers to exit the loop, and we rethrow + sharedPStateFlags.SetExceptional(); + throw; + } + finally + { + // If a cleanup function was specified, call it. Otherwise, if the type is + // IDisposable, we will invoke Dispose on behalf of the user. + if (localFinally != null && bLocalValueInitialized) + { + localFinally(localValue); + } + + // ETW event for ParallelFor Worker Join + if (TplEtwProvider.Log.IsEnabled()) + { + TplEtwProvider.Log.ParallelJoin((currentWorkerTask != null ? currentWorkerTask.m_taskScheduler.Id : TaskScheduler.Current.Id), (currentWorkerTask != null ? currentWorkerTask.Id : 0), + forkJoinContextID); + } + } + }, + creationOptions, internalOptions); + + rootTask.RunSynchronously(parallelOptions.EffectiveTaskScheduler); // might throw TSE + rootTask.Wait(); + + // If we made a cancellation registration, we need to clean it up now before observing the OCE + // Otherwise we could be caught in the middle of a callback, and observe PLS_STOPPED, but oce = null + if (parallelOptions.CancellationToken.CanBeCanceled) + { + ctr.Dispose(); + } + + // If we got through that with no exceptions, and we were canceled, then + // throw our cancellation exception + if (oce != null) throw oce; + } + catch (AggregateException aggExp) + { + // if we made a cancellation registration, and rootTask.Wait threw, we need to clean it up here + if (parallelOptions.CancellationToken.CanBeCanceled) + { + ctr.Dispose(); + } + + // see if we can combine it into a single OCE. If not propagate the original exception + ThrowIfReducableToSingleOCE(aggExp.InnerExceptions, parallelOptions.CancellationToken); + throw; + } + catch (TaskSchedulerException) + { + // if we made a cancellation registration, and rootTask.RunSynchronously threw, we need to clean it up here + if (parallelOptions.CancellationToken.CanBeCanceled) + { + ctr.Dispose(); + } + throw; + } + finally + { + int sb_status = sharedPStateFlags.LoopStateFlags; + result.m_completed = (sb_status == ParallelLoopStateFlags.PLS_NONE); + if ((sb_status & ParallelLoopStateFlags.PLS_BROKEN) != 0) + { + result.m_lowestBreakIteration = sharedPStateFlags.LowestBreakIteration; + } + + if ((rootTask != null) && rootTask.IsCompleted) rootTask.Dispose(); + + // ETW event for Parallel For End + if (TplEtwProvider.Log.IsEnabled()) + { + long nTotalIterations = 0; + + // calculate how many iterations we ran in total + if (sb_status == ParallelLoopStateFlags.PLS_NONE) + nTotalIterations = toExclusive - fromInclusive; + else if ((sb_status & ParallelLoopStateFlags.PLS_BROKEN) != 0) + nTotalIterations = sharedPStateFlags.LowestBreakIteration - fromInclusive; + else + nTotalIterations = -1; //PLS_STOPPED! We can't determine this if we were stopped.. + + TplEtwProvider.Log.ParallelLoopEnd((callerTask != null ? callerTask.m_taskScheduler.Id : TaskScheduler.Current.Id), (callerTask != null ? callerTask.Id : 0), + forkJoinContextID, nTotalIterations); + } + } + + return result; + } + + + /// <summary> + /// Executes a for each operation on an <see cref="T:System.Collections.IEnumerable{TSource}"/> + /// in which iterations may run in parallel. + /// </summary> + /// <typeparam name="TSource">The type of the data in the source.</typeparam> + /// <param name="source">An enumerable data source.</param> + /// <param name="body">The delegate that is invoked once per iteration.</param> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the <paramref name="source"/> + /// argument is null.</exception> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the <paramref name="body"/> + /// argument is null.</exception> + /// <exception cref="T:System.AggregateException">The exception that is thrown to contain an exception + /// thrown from one of the specified delegates.</exception> + /// <returns>A <see cref="T:System.Threading.Tasks.ParallelLoopResult">ParallelLoopResult</see> structure + /// that contains information on what portion of the loop completed.</returns> + /// <remarks> + /// The <paramref name="body"/> delegate is invoked once for each element in the <paramref name="source"/> + /// enumerable. It is provided with the current element as a parameter. + /// </remarks> + public static ParallelLoopResult ForEach<TSource>(IEnumerable<TSource> source, Action<TSource> body) + { + if (source == null) + { + throw new ArgumentNullException("source"); + } + if (body == null) + { + throw new ArgumentNullException("body"); + } + + return ForEachWorker<TSource, object>( + source, s_defaultParallelOptions, body, null, null, null, null, null, null); + } + + /// <summary> + /// Executes a for each operation on an <see cref="T:System.Collections.IEnumerable{TSource}"/> + /// in which iterations may run in parallel. + /// </summary> + /// <typeparam name="TSource">The type of the data in the source.</typeparam> + /// <param name="source">An enumerable data source.</param> + /// <param name="parallelOptions">A <see cref="T:System.Threading.Tasks.ParallelOptions">ParallelOptions</see> + /// instance that configures the behavior of this operation.</param> + /// <param name="body">The delegate that is invoked once per iteration.</param> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the <paramref name="source"/> + /// argument is null.</exception> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the + /// <paramref name="parallelOptions"/> argument is null.</exception> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the <paramref name="body"/> + /// argument is null.</exception> + /// <exception cref="T:System.OperationCanceledException">The exception that is thrown when the + /// <see cref="T:System.Threading.CancellationToken">CancellationToken</see> in the <paramref name="parallelOptions"/> + /// argument is set</exception> + /// <exception cref="T:System.AggregateException">The exception that is thrown to contain an exception + /// thrown from one of the specified delegates.</exception> + /// <exception cref="T:System.ObjectDisposedException">The exception that is thrown when the + /// the <see cref="T:System.Threading.CancellationTokenSource">CancellationTokenSource</see> associated with the + /// the <see cref="T:System.Threading.CancellationToken">CancellationToken</see> in the + /// <paramref name="parallelOptions"/> has been disposed.</exception> + /// <returns>A <see cref="T:System.Threading.Tasks.ParallelLoopResult">ParallelLoopResult</see> structure + /// that contains information on what portion of the loop completed.</returns> + /// <remarks> + /// The <paramref name="body"/> delegate is invoked once for each element in the <paramref name="source"/> + /// enumerable. It is provided with the current element as a parameter. + /// </remarks> + public static ParallelLoopResult ForEach<TSource>(IEnumerable<TSource> source, ParallelOptions parallelOptions, Action<TSource> body) + { + if (source == null) + { + throw new ArgumentNullException("source"); + } + if (body == null) + { + throw new ArgumentNullException("body"); + } + if (parallelOptions == null) + { + throw new ArgumentNullException("parallelOptions"); + } + + return ForEachWorker<TSource, object>( + source, parallelOptions, body, null, null, null, null, null, null); + } + + /// <summary> + /// Executes a for each operation on an <see cref="T:System.Collections.IEnumerable{TSource}"/> + /// in which iterations may run in parallel. + /// </summary> + /// <typeparam name="TSource">The type of the data in the source.</typeparam> + /// <param name="source">An enumerable data source.</param> + /// <param name="body">The delegate that is invoked once per iteration.</param> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the <paramref name="source"/> + /// argument is null.</exception> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the <paramref name="body"/> + /// argument is null.</exception> + /// <exception cref="T:System.AggregateException">The exception that is thrown to contain an exception + /// thrown from one of the specified delegates.</exception> + /// <returns>A <see cref="T:System.Threading.Tasks.ParallelLoopResult">ParallelLoopResult</see> structure + /// that contains information on what portion of the loop completed.</returns> + /// <remarks> + /// The <paramref name="body"/> delegate is invoked once for each element in the <paramref name="source"/> + /// enumerable. It is provided with the following parameters: the current element, + /// and a <see cref="System.Threading.Tasks.ParallelLoopState">ParallelLoopState</see> instance that may be + /// used to break out of the loop prematurely. + /// </remarks> + public static ParallelLoopResult ForEach<TSource>(IEnumerable<TSource> source, Action<TSource, ParallelLoopState> body) + { + if (source == null) + { + throw new ArgumentNullException("source"); + } + if (body == null) + { + throw new ArgumentNullException("body"); + } + + return ForEachWorker<TSource, object>( + source, s_defaultParallelOptions, null, body, null, null, null, null, null); + } + + /// <summary> + /// Executes a for each operation on an <see cref="T:System.Collections.IEnumerable{TSource}"/> + /// in which iterations may run in parallel. + /// </summary> + /// <typeparam name="TSource">The type of the data in the source.</typeparam> + /// <param name="source">An enumerable data source.</param> + /// <param name="parallelOptions">A <see cref="T:System.Threading.Tasks.ParallelOptions">ParallelOptions</see> + /// instance that configures the behavior of this operation.</param> + /// <param name="body">The delegate that is invoked once per iteration.</param> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the <paramref name="source"/> + /// argument is null.</exception> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the + /// <paramref name="parallelOptions"/> argument is null.</exception> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the <paramref name="body"/> + /// argument is null.</exception> + /// <exception cref="T:System.OperationCanceledException">The exception that is thrown when the + /// <see cref="T:System.Threading.CancellationToken">CancellationToken</see> in the <paramref name="parallelOptions"/> + /// argument is set</exception> + /// <exception cref="T:System.AggregateException">The exception that is thrown to contain an exception + /// thrown from one of the specified delegates.</exception> + /// <exception cref="T:System.ObjectDisposedException">The exception that is thrown when the + /// the <see cref="T:System.Threading.CancellationTokenSource">CancellationTokenSource</see> associated with the + /// the <see cref="T:System.Threading.CancellationToken">CancellationToken</see> in the + /// <paramref name="parallelOptions"/> has been disposed.</exception> + /// <returns>A <see cref="T:System.Threading.Tasks.ParallelLoopResult">ParallelLoopResult</see> structure + /// that contains information on what portion of the loop completed.</returns> + /// <remarks> + /// The <paramref name="body"/> delegate is invoked once for each element in the <paramref name="source"/> + /// enumerable. It is provided with the following parameters: the current element, + /// and a <see cref="System.Threading.Tasks.ParallelLoopState">ParallelLoopState</see> instance that may be + /// used to break out of the loop prematurely. + /// </remarks> + public static ParallelLoopResult ForEach<TSource>(IEnumerable<TSource> source, ParallelOptions parallelOptions, Action<TSource, ParallelLoopState> body) + { + if (source == null) + { + throw new ArgumentNullException("source"); + } + if (body == null) + { + throw new ArgumentNullException("body"); + } + if (parallelOptions == null) + { + throw new ArgumentNullException("parallelOptions"); + } + + return ForEachWorker<TSource, object>( + source, parallelOptions, null, body, null, null, null, null, null); + } + + /// <summary> + /// Executes a for each operation on an <see cref="T:System.Collections.IEnumerable{TSource}"/> + /// in which iterations may run in parallel. + /// </summary> + /// <typeparam name="TSource">The type of the data in the source.</typeparam> + /// <param name="source">An enumerable data source.</param> + /// <param name="body">The delegate that is invoked once per iteration.</param> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the <paramref name="source"/> + /// argument is null.</exception> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the <paramref name="body"/> + /// argument is null.</exception> + /// <exception cref="T:System.AggregateException">The exception that is thrown to contain an exception + /// thrown from one of the specified delegates.</exception> + /// <returns>A <see cref="T:System.Threading.Tasks.ParallelLoopResult">ParallelLoopResult</see> structure + /// that contains information on what portion of the loop completed.</returns> + /// <remarks> + /// The <paramref name="body"/> delegate is invoked once for each element in the <paramref name="source"/> + /// enumerable. It is provided with the following parameters: the current element, + /// a <see cref="System.Threading.Tasks.ParallelLoopState">ParallelLoopState</see> instance that may be + /// used to break out of the loop prematurely, and the current element's index (an Int64). + /// </remarks> + public static ParallelLoopResult ForEach<TSource>(IEnumerable<TSource> source, Action<TSource, ParallelLoopState, long> body) + { + if (source == null) + { + throw new ArgumentNullException("source"); + } + if (body == null) + { + throw new ArgumentNullException("body"); + } + + return ForEachWorker<TSource, object>( + source, s_defaultParallelOptions, null, null, body, null, null, null, null); + } + + /// <summary> + /// Executes a for each operation on an <see cref="T:System.Collections.IEnumerable{TSource}"/> + /// in which iterations may run in parallel. + /// </summary> + /// <typeparam name="TSource">The type of the data in the source.</typeparam> + /// <param name="source">An enumerable data source.</param> + /// <param name="parallelOptions">A <see cref="T:System.Threading.Tasks.ParallelOptions">ParallelOptions</see> + /// instance that configures the behavior of this operation.</param> + /// <param name="body">The delegate that is invoked once per iteration.</param> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the <paramref name="source"/> + /// argument is null.</exception> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the + /// <paramref name="parallelOptions"/> argument is null.</exception> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the <paramref name="body"/> + /// argument is null.</exception> + /// <exception cref="T:System.OperationCanceledException">The exception that is thrown when the + /// <see cref="T:System.Threading.CancellationToken">CancellationToken</see> in the <paramref name="parallelOptions"/> + /// argument is set</exception> + /// <exception cref="T:System.AggregateException">The exception that is thrown to contain an exception + /// thrown from one of the specified delegates.</exception> + /// <exception cref="T:System.ObjectDisposedException">The exception that is thrown when the + /// the <see cref="T:System.Threading.CancellationTokenSource">CancellationTokenSource</see> associated with the + /// the <see cref="T:System.Threading.CancellationToken">CancellationToken</see> in the + /// <paramref name="parallelOptions"/> has been disposed.</exception> + /// <returns>A <see cref="T:System.Threading.Tasks.ParallelLoopResult">ParallelLoopResult</see> structure + /// that contains information on what portion of the loop completed.</returns> + /// <remarks> + /// The <paramref name="body"/> delegate is invoked once for each element in the <paramref name="source"/> + /// enumerable. It is provided with the following parameters: the current element, + /// a <see cref="System.Threading.Tasks.ParallelLoopState">ParallelLoopState</see> instance that may be + /// used to break out of the loop prematurely, and the current element's index (an Int64). + /// </remarks> + public static ParallelLoopResult ForEach<TSource>(IEnumerable<TSource> source, ParallelOptions parallelOptions, Action<TSource, ParallelLoopState, long> body) + { + if (source == null) + { + throw new ArgumentNullException("source"); + } + if (body == null) + { + throw new ArgumentNullException("body"); + } + if (parallelOptions == null) + { + throw new ArgumentNullException("parallelOptions"); + } + + return ForEachWorker<TSource, object>( + source, parallelOptions, null, null, body, null, null, null, null); + } + + /// <summary> + /// Executes a for each operation on an <see cref="T:System.Collections.IEnumerable{TSource}"/> + /// in which iterations may run in parallel. + /// </summary> + /// <typeparam name="TSource">The type of the data in the source.</typeparam> + /// <typeparam name="TLocal">The type of the thread-local data.</typeparam> + /// <param name="source">An enumerable data source.</param> + /// <param name="localInit">The function delegate that returns the initial state of the local data + /// for each thread.</param> + /// <param name="body">The delegate that is invoked once per iteration.</param> + /// <param name="localFinally">The delegate that performs a final action on the local state of each + /// thread.</param> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the <paramref name="source"/> + /// argument is null.</exception> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the <paramref name="body"/> + /// argument is null.</exception> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the + /// <paramref name="localInit"/> argument is null.</exception> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the + /// <paramref name="localFinally"/> argument is null.</exception> + /// <exception cref="T:System.AggregateException">The exception that is thrown to contain an exception + /// thrown from one of the specified delegates.</exception> + /// <returns>A <see cref="T:System.Threading.Tasks.ParallelLoopResult">ParallelLoopResult</see> structure + /// that contains information on what portion of the loop completed.</returns> + /// <remarks> + /// <para> + /// The <paramref name="body"/> delegate is invoked once for each element in the <paramref name="source"/> + /// enumerable. It is provided with the following parameters: the current element, + /// a <see cref="System.Threading.Tasks.ParallelLoopState">ParallelLoopState</see> instance that may be + /// used to break out of the loop prematurely, and some local state that may be shared amongst iterations + /// that execute on the same thread. + /// </para> + /// <para> + /// The <paramref name="localInit"/> delegate is invoked once for each thread that participates in the loop's + /// execution and returns the initial local state for each of those threads. These initial states are passed to the first + /// <paramref name="body"/> invocations on each thread. Then, every subsequent body invocation returns a possibly + /// modified state value that is passed to the next body invocation. Finally, the last body invocation on each thread returns a state value + /// that is passed to the <paramref name="localFinally"/> delegate. The localFinally delegate is invoked once per thread to perform a final + /// action on each thread's local state. + /// </para> + /// </remarks> + public static ParallelLoopResult ForEach<TSource, TLocal>(IEnumerable<TSource> source, Func<TLocal> localInit, + Func<TSource, ParallelLoopState, TLocal, TLocal> body, Action<TLocal> localFinally) + { + if (source == null) + { + throw new ArgumentNullException("source"); + } + if (body == null) + { + throw new ArgumentNullException("body"); + } + if (localInit == null) + { + throw new ArgumentNullException("localInit"); + } + if (localFinally == null) + { + throw new ArgumentNullException("localFinally"); + } + + return ForEachWorker<TSource, TLocal>( + source, s_defaultParallelOptions, null, null, null, body, null, localInit, localFinally); + } + + /// <summary> + /// Executes a for each operation on an <see cref="T:System.Collections.IEnumerable{TSource}"/> + /// in which iterations may run in parallel. + /// </summary> + /// <typeparam name="TSource">The type of the data in the source.</typeparam> + /// <typeparam name="TLocal">The type of the thread-local data.</typeparam> + /// <param name="source">An enumerable data source.</param> + /// <param name="parallelOptions">A <see cref="T:System.Threading.Tasks.ParallelOptions">ParallelOptions</see> + /// instance that configures the behavior of this operation.</param> + /// <param name="localInit">The function delegate that returns the initial state of the local data + /// for each thread.</param> + /// <param name="body">The delegate that is invoked once per iteration.</param> + /// <param name="localFinally">The delegate that performs a final action on the local state of each + /// thread.</param> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the <paramref name="source"/> + /// argument is null.</exception> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the + /// <paramref name="parallelOptions"/> argument is null.</exception> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the <paramref name="body"/> + /// argument is null.</exception> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the + /// <paramref name="localInit"/> argument is null.</exception> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the + /// <paramref name="localFinally"/> argument is null.</exception> + /// <exception cref="T:System.OperationCanceledException">The exception that is thrown when the + /// <see cref="T:System.Threading.CancellationToken">CancellationToken</see> in the <paramref name="parallelOptions"/> + /// argument is set</exception> + /// <exception cref="T:System.AggregateException">The exception that is thrown to contain an exception + /// thrown from one of the specified delegates.</exception> + /// <exception cref="T:System.ObjectDisposedException">The exception that is thrown when the + /// the <see cref="T:System.Threading.CancellationTokenSource">CancellationTokenSource</see> associated with the + /// the <see cref="T:System.Threading.CancellationToken">CancellationToken</see> in the + /// <paramref name="parallelOptions"/> has been disposed.</exception> + /// <returns>A <see cref="T:System.Threading.Tasks.ParallelLoopResult">ParallelLoopResult</see> structure + /// that contains information on what portion of the loop completed.</returns> + /// <remarks> + /// <para> + /// The <paramref name="body"/> delegate is invoked once for each element in the <paramref name="source"/> + /// enumerable. It is provided with the following parameters: the current element, + /// a <see cref="System.Threading.Tasks.ParallelLoopState">ParallelLoopState</see> instance that may be + /// used to break out of the loop prematurely, and some local state that may be shared amongst iterations + /// that execute on the same thread. + /// </para> + /// <para> + /// The <paramref name="localInit"/> delegate is invoked once for each thread that participates in the loop's + /// execution and returns the initial local state for each of those threads. These initial states are passed to the first + /// <paramref name="body"/> invocations on each thread. Then, every subsequent body invocation returns a possibly + /// modified state value that is passed to the next body invocation. Finally, the last body invocation on each thread returns a state value + /// that is passed to the <paramref name="localFinally"/> delegate. The localFinally delegate is invoked once per thread to perform a final + /// action on each thread's local state. + /// </para> + /// </remarks> + public static ParallelLoopResult ForEach<TSource, TLocal>(IEnumerable<TSource> source, + ParallelOptions parallelOptions, Func<TLocal> localInit, + Func<TSource, ParallelLoopState, TLocal, TLocal> body, Action<TLocal> localFinally) + { + if (source == null) + { + throw new ArgumentNullException("source"); + } + if (body == null) + { + throw new ArgumentNullException("body"); + } + if (localInit == null) + { + throw new ArgumentNullException("localInit"); + } + if (localFinally == null) + { + throw new ArgumentNullException("localFinally"); + } + if (parallelOptions == null) + { + throw new ArgumentNullException("parallelOptions"); + } + + return ForEachWorker<TSource, TLocal>( + source, parallelOptions, null, null, null, body, null, localInit, localFinally); + } + + /// <summary> + /// Executes a for each operation on an <see cref="T:System.Collections.IEnumerable{TSource}"/> + /// in which iterations may run in parallel. + /// </summary> + /// <typeparam name="TSource">The type of the data in the source.</typeparam> + /// <typeparam name="TLocal">The type of the thread-local data.</typeparam> + /// <param name="source">An enumerable data source.</param> + /// <param name="localInit">The function delegate that returns the initial state of the local data + /// for each thread.</param> + /// <param name="body">The delegate that is invoked once per iteration.</param> + /// <param name="localFinally">The delegate that performs a final action on the local state of each + /// thread.</param> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the <paramref name="source"/> + /// argument is null.</exception> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the <paramref name="body"/> + /// argument is null.</exception> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the + /// <paramref name="localInit"/> argument is null.</exception> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the + /// <paramref name="localFinally"/> argument is null.</exception> + /// <exception cref="T:System.AggregateException">The exception that is thrown to contain an exception + /// thrown from one of the specified delegates.</exception> + /// <returns>A <see cref="T:System.Threading.Tasks.ParallelLoopResult">ParallelLoopResult</see> structure + /// that contains information on what portion of the loop completed.</returns> + /// <remarks> + /// <para> + /// The <paramref name="body"/> delegate is invoked once for each element in the <paramref name="source"/> + /// enumerable. It is provided with the following parameters: the current element, + /// a <see cref="System.Threading.Tasks.ParallelLoopState">ParallelLoopState</see> instance that may be + /// used to break out of the loop prematurely, the current element's index (an Int64), and some local + /// state that may be shared amongst iterations that execute on the same thread. + /// </para> + /// <para> + /// The <paramref name="localInit"/> delegate is invoked once for each thread that participates in the loop's + /// execution and returns the initial local state for each of those threads. These initial states are passed to the first + /// <paramref name="body"/> invocations on each thread. Then, every subsequent body invocation returns a possibly + /// modified state value that is passed to the next body invocation. Finally, the last body invocation on each thread returns a state value + /// that is passed to the <paramref name="localFinally"/> delegate. The localFinally delegate is invoked once per thread to perform a final + /// action on each thread's local state. + /// </para> + /// </remarks> + public static ParallelLoopResult ForEach<TSource, TLocal>(IEnumerable<TSource> source, Func<TLocal> localInit, + Func<TSource, ParallelLoopState, long, TLocal, TLocal> body, Action<TLocal> localFinally) + { + if (source == null) + { + throw new ArgumentNullException("source"); + } + if (body == null) + { + throw new ArgumentNullException("body"); + } + if (localInit == null) + { + throw new ArgumentNullException("localInit"); + } + if (localFinally == null) + { + throw new ArgumentNullException("localFinally"); + } + + return ForEachWorker<TSource, TLocal>( + source, s_defaultParallelOptions, null, null, null, null, body, localInit, localFinally); + } + + /// <summary> + /// Executes a for each operation on an <see cref="T:System.Collections.IEnumerable{TSource}"/> + /// in which iterations may run in parallel. + /// </summary> + /// <typeparam name="TSource">The type of the data in the source.</typeparam> + /// <typeparam name="TLocal">The type of the thread-local data.</typeparam> + /// <param name="source">An enumerable data source.</param> + /// <param name="parallelOptions">A <see cref="T:System.Threading.Tasks.ParallelOptions">ParallelOptions</see> + /// instance that configures the behavior of this operation.</param> + /// <param name="localInit">The function delegate that returns the initial state of the local data + /// for each thread.</param> + /// <param name="body">The delegate that is invoked once per iteration.</param> + /// <param name="localFinally">The delegate that performs a final action on the local state of each + /// thread.</param> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the <paramref name="source"/> + /// argument is null.</exception> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the + /// <paramref name="parallelOptions"/> argument is null.</exception> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the <paramref name="body"/> + /// argument is null.</exception> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the + /// <paramref name="localInit"/> argument is null.</exception> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the + /// <paramref name="localFinally"/> argument is null.</exception> + /// <exception cref="T:System.OperationCanceledException">The exception that is thrown when the + /// <see cref="T:System.Threading.CancellationToken">CancellationToken</see> in the <paramref name="parallelOptions"/> + /// argument is set</exception> + /// <exception cref="T:System.AggregateException">The exception that is thrown to contain an exception + /// thrown from one of the specified delegates.</exception> + /// <exception cref="T:System.ObjectDisposedException">The exception that is thrown when the + /// the <see cref="T:System.Threading.CancellationTokenSource">CancellationTokenSource</see> associated with the + /// the <see cref="T:System.Threading.CancellationToken">CancellationToken</see> in the + /// <paramref name="parallelOptions"/> has been disposed.</exception> + /// <returns>A <see cref="T:System.Threading.Tasks.ParallelLoopResult">ParallelLoopResult</see> structure + /// that contains information on what portion of the loop completed.</returns> + /// <remarks> + /// <para> + /// The <paramref name="body"/> delegate is invoked once for each element in the <paramref name="source"/> + /// enumerable. It is provided with the following parameters: the current element, + /// a <see cref="System.Threading.Tasks.ParallelLoopState">ParallelLoopState</see> instance that may be + /// used to break out of the loop prematurely, the current element's index (an Int64), and some local + /// state that may be shared amongst iterations that execute on the same thread. + /// </para> + /// <para> + /// The <paramref name="localInit"/> delegate is invoked once for each thread that participates in the loop's + /// execution and returns the initial local state for each of those threads. These initial states are passed to the first + /// <paramref name="body"/> invocations on each thread. Then, every subsequent body invocation returns a possibly + /// modified state value that is passed to the next body invocation. Finally, the last body invocation on each thread returns a state value + /// that is passed to the <paramref name="localFinally"/> delegate. The localFinally delegate is invoked once per thread to perform a final + /// action on each thread's local state. + /// </para> + /// </remarks> + public static ParallelLoopResult ForEach<TSource, TLocal>(IEnumerable<TSource> source, ParallelOptions parallelOptions, Func<TLocal> localInit, + Func<TSource, ParallelLoopState, long, TLocal, TLocal> body, Action<TLocal> localFinally) + { + if (source == null) + { + throw new ArgumentNullException("source"); + } + if (body == null) + { + throw new ArgumentNullException("body"); + } + if (localInit == null) + { + throw new ArgumentNullException("localInit"); + } + if (localFinally == null) + { + throw new ArgumentNullException("localFinally"); + } + if (parallelOptions == null) + { + throw new ArgumentNullException("parallelOptions"); + } + + return ForEachWorker<TSource, TLocal>( + source, parallelOptions, null, null, null, null, body, localInit, localFinally); + } + + + /// <summary> + /// Performs the major work of the parallel foreach loop. It assumes that argument validation has + /// already been performed by the caller. This function's whole purpose in life is to enable as much + /// reuse of common implementation details for the various For overloads we offer. Without it, we'd + /// end up with lots of duplicate code. It handles: (1) simple foreach loops, (2) foreach loops that + /// depend on ParallelState, and (3) foreach loops that access indices, (4) foreach loops with thread + /// local data, and any necessary permutations thereof. + /// + /// </summary> + /// <typeparam name="TSource">The type of the source data.</typeparam> + /// <typeparam name="TLocal">The type of the local data.</typeparam> + /// <param name="source">An enumerable data source.</param> + /// <param name="parallelOptions">ParallelOptions instance to use with this ForEach-loop</param> + /// <param name="body">The simple loop body.</param> + /// <param name="bodyWithState">The loop body for ParallelState overloads.</param> + /// <param name="bodyWithStateAndIndex">The loop body for ParallelState/indexed overloads.</param> + /// <param name="bodyWithStateAndLocal">The loop body for ParallelState/thread local state overloads.</param> + /// <param name="bodyWithEverything">The loop body for ParallelState/indexed/thread local state overloads.</param> + /// <param name="localInit">A selector function that returns new thread local state.</param> + /// <param name="localFinally">A cleanup function to destroy thread local state.</param> + /// <remarks>Only one of the bodyXX arguments may be supplied (i.e. they are exclusive).</remarks> + /// <returns>A <see cref="T:System.Threading.Tasks.ParallelLoopResult"/> structure.</returns> + private static ParallelLoopResult ForEachWorker<TSource, TLocal>( + IEnumerable<TSource> source, + ParallelOptions parallelOptions, + Action<TSource> body, + Action<TSource, ParallelLoopState> bodyWithState, + Action<TSource, ParallelLoopState, long> bodyWithStateAndIndex, + Func<TSource, ParallelLoopState, TLocal, TLocal> bodyWithStateAndLocal, + Func<TSource, ParallelLoopState, long, TLocal, TLocal> bodyWithEverything, + Func<TLocal> localInit, Action<TLocal> localFinally) + { + Contract.Assert(((body == null ? 0 : 1) + (bodyWithState == null ? 0 : 1) + + (bodyWithStateAndIndex == null ? 0 : 1) + (bodyWithStateAndLocal == null ? 0 : 1) + (bodyWithEverything == null ? 0 : 1)) == 1, + "expected exactly one body function to be supplied"); + Contract.Assert((bodyWithStateAndLocal != null) || (bodyWithEverything != null) || (localInit == null && localFinally == null), + "thread local functions should only be supplied for loops w/ thread local bodies"); + + // Before getting started, do a quick peek to see if we have been canceled already + if (parallelOptions.CancellationToken.IsCancellationRequested) + { + throw new OperationCanceledException(parallelOptions.CancellationToken); + } + + // If it's an array, we can use a fast-path that uses ldelems in the IL. + TSource[] sourceAsArray = source as TSource[]; + if (sourceAsArray != null) + { + return ForEachWorker<TSource, TLocal>( + sourceAsArray, parallelOptions, body, bodyWithState, bodyWithStateAndIndex, bodyWithStateAndLocal, + bodyWithEverything, localInit, localFinally); + } + + // If we can index into the list, we can use a faster code-path that doesn't result in + // contention for the single, shared enumerator object. + IList<TSource> sourceAsList = source as IList<TSource>; + if (sourceAsList != null) + { + return ForEachWorker<TSource, TLocal>( + sourceAsList, parallelOptions, body, bodyWithState, bodyWithStateAndIndex, bodyWithStateAndLocal, + bodyWithEverything, localInit, localFinally); + } + + // This is an honest-to-goodness IEnumerable. Wrap it in a Partitioner and defer to our + // ForEach(Partitioner) logic. + return PartitionerForEachWorker<TSource, TLocal>(Partitioner.Create(source), parallelOptions, body, bodyWithState, + bodyWithStateAndIndex, bodyWithStateAndLocal, bodyWithEverything, localInit, localFinally); + + } + + /// <summary> + /// A fast path for the more general ForEachWorker method above. This uses ldelem instructions to + /// access the individual elements of the array, which will be faster. + /// </summary> + /// <typeparam name="TSource">The type of the source data.</typeparam> + /// <typeparam name="TLocal">The type of the local data.</typeparam> + /// <param name="array">An array data source.</param> + /// <param name="parallelOptions">The options to use for execution.</param> + /// <param name="body">The simple loop body.</param> + /// <param name="bodyWithState">The loop body for ParallelState overloads.</param> + /// <param name="bodyWithStateAndIndex">The loop body for indexed/ParallelLoopState overloads.</param> + /// <param name="bodyWithStateAndLocal">The loop body for local/ParallelLoopState overloads.</param> + /// <param name="bodyWithEverything">The loop body for the most generic overload.</param> + /// <param name="localInit">A selector function that returns new thread local state.</param> + /// <param name="localFinally">A cleanup function to destroy thread local state.</param> + /// <returns>A <see cref="T:System.Threading.Tasks.ParallelLoopResult"/> structure.</returns> + private static ParallelLoopResult ForEachWorker<TSource, TLocal>( + TSource[] array, + ParallelOptions parallelOptions, + Action<TSource> body, + Action<TSource, ParallelLoopState> bodyWithState, + Action<TSource, ParallelLoopState, long> bodyWithStateAndIndex, + Func<TSource, ParallelLoopState, TLocal, TLocal> bodyWithStateAndLocal, + Func<TSource, ParallelLoopState, long, TLocal, TLocal> bodyWithEverything, + Func<TLocal> localInit, Action<TLocal> localFinally) + { + Contract.Assert(array != null); + Contract.Assert(parallelOptions != null, "ForEachWorker(array): parallelOptions is null"); + + int from = array.GetLowerBound(0); + int to = array.GetUpperBound(0) + 1; + + if (body != null) + { + return ForWorker<object>( + from, to, parallelOptions, (i) => body(array[i]), null, null, null, null); + } + else if (bodyWithState != null) + { + return ForWorker<object>( + from, to, parallelOptions, null, (i, state) => bodyWithState(array[i], state), null, null, null); + } + else if (bodyWithStateAndIndex != null) + { + return ForWorker<object>( + from, to, parallelOptions, null, (i, state) => bodyWithStateAndIndex(array[i], state, i), null, null, null); + } + else if (bodyWithStateAndLocal != null) + { + return ForWorker<TLocal>( + from, to, parallelOptions, null, null, (i, state, local) => bodyWithStateAndLocal(array[i], state, local), localInit, localFinally); + } + else + { + return ForWorker<TLocal>( + from, to, parallelOptions, null, null, (i, state, local) => bodyWithEverything(array[i], state, i, local), localInit, localFinally); + } + } + + /// <summary> + /// A fast path for the more general ForEachWorker method above. This uses IList<T>'s indexer + /// capabilities to access the individual elements of the list rather than an enumerator. + /// </summary> + /// <typeparam name="TSource">The type of the source data.</typeparam> + /// <typeparam name="TLocal">The type of the local data.</typeparam> + /// <param name="list">A list data source.</param> + /// <param name="parallelOptions">The options to use for execution.</param> + /// <param name="body">The simple loop body.</param> + /// <param name="bodyWithState">The loop body for ParallelState overloads.</param> + /// <param name="bodyWithStateAndIndex">The loop body for indexed/ParallelLoopState overloads.</param> + /// <param name="bodyWithStateAndLocal">The loop body for local/ParallelLoopState overloads.</param> + /// <param name="bodyWithEverything">The loop body for the most generic overload.</param> + /// <param name="localInit">A selector function that returns new thread local state.</param> + /// <param name="localFinally">A cleanup function to destroy thread local state.</param> + /// <returns>A <see cref="T:System.Threading.Tasks.ParallelLoopResult"/> structure.</returns> + private static ParallelLoopResult ForEachWorker<TSource, TLocal>( + IList<TSource> list, + ParallelOptions parallelOptions, + Action<TSource> body, + Action<TSource, ParallelLoopState> bodyWithState, + Action<TSource, ParallelLoopState, long> bodyWithStateAndIndex, + Func<TSource, ParallelLoopState, TLocal, TLocal> bodyWithStateAndLocal, + Func<TSource, ParallelLoopState, long, TLocal, TLocal> bodyWithEverything, + Func<TLocal> localInit, Action<TLocal> localFinally) + { + Contract.Assert(list != null); + Contract.Assert(parallelOptions != null, "ForEachWorker(list): parallelOptions is null"); + + if (body != null) + { + return ForWorker<object>( + 0, list.Count, parallelOptions, (i) => body(list[i]), null, null, null, null); + } + else if (bodyWithState != null) + { + return ForWorker<object>( + 0, list.Count, parallelOptions, null, (i, state) => bodyWithState(list[i], state), null, null, null); + } + else if (bodyWithStateAndIndex != null) + { + return ForWorker<object>( + 0, list.Count, parallelOptions, null, (i, state) => bodyWithStateAndIndex(list[i], state, i), null, null, null); + } + else if (bodyWithStateAndLocal != null) + { + return ForWorker<TLocal>( + 0, list.Count, parallelOptions, null, null, (i, state, local) => bodyWithStateAndLocal(list[i], state, local), localInit, localFinally); + } + else + { + return ForWorker<TLocal>( + 0, list.Count, parallelOptions, null, null, (i, state, local) => bodyWithEverything(list[i], state, i, local), localInit, localFinally); + } + } + + + + /// <summary> + /// Executes a for each operation on a <see cref="T:System.Collections.Concurrent.Partitioner{TSource}"> + /// Partitioner</see> in which iterations may run in parallel. + /// </summary> + /// <typeparam name="TSource">The type of the elements in <paramref name="source"/>.</typeparam> + /// <param name="source">The Partitioner that contains the original data source.</param> + /// <param name="body">The delegate that is invoked once per iteration.</param> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the <paramref name="source"/> + /// argument is null.</exception> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the <paramref name="body"/> + /// argument is null.</exception> + /// <exception cref="T:System.InvalidOperationException">The exception that is thrown when the + /// SupportsDynamicPartitions property in the <paramref name="source"/> Partitioner returns + /// false.</exception> + /// <exception cref="T:System.InvalidOperationException">The exception that is thrown when any + /// methods in the <paramref name="source"/> Partitioner return null.</exception> + /// <exception cref="T:System.InvalidOperationException">The exception that is thrown when the + /// GetPartitions() method in the <paramref name="source"/> Partitioner does not return + /// the correct number of partitions.</exception> + /// <exception cref="T:System.InvalidOperationException">The exception that is thrown when the + /// GetPartitions() method in the <paramref name="source"/> Partitioner returns an IList + /// with at least one null value.</exception> + /// <exception cref="T:System.InvalidOperationException">The exception that is thrown when the + /// GetDynamicPartitions() method in the <paramref name="source"/> Partitioner returns an + /// IEnumerable whose GetEnumerator() method returns null.</exception> + /// <exception cref="T:System.AggregateException">The exception that is thrown to contain an exception + /// thrown from one of the specified delegates.</exception> + /// <returns>A <see cref="T:System.Threading.Tasks.ParallelLoopResult">ParallelLoopResult</see> structure + /// that contains information on what portion of the loop completed.</returns> + /// <remarks> + /// <para> + /// The <see cref="T:System.Collections.Concurrent.Partitioner{TSource}">Partitioner</see> is used to retrieve + /// the elements to be processed, in place of the original data source. If the current element's + /// index is desired, the source must be an <see cref="T:System.Collections.Concurrent.OrderablePartitioner"> + /// OrderablePartitioner</see>. + /// </para> + /// <para> + /// The <paramref name="body"/> delegate is invoked once for each element in the <paramref name="source"/> + /// Partitioner. It is provided with the current element as a parameter. + /// </para> + /// </remarks> + public static ParallelLoopResult ForEach<TSource>( + Partitioner<TSource> source, + Action<TSource> body) + { + if (source == null) + { + throw new ArgumentNullException("source"); + } + if (body == null) + { + throw new ArgumentNullException("body"); + } + + return PartitionerForEachWorker<TSource, object>(source, s_defaultParallelOptions, body, null, null, null, null, null, null); + } + + /// <summary> + /// Executes a for each operation on a <see cref="T:System.Collections.Concurrent.Partitioner{TSource}"> + /// Partitioner</see> in which iterations may run in parallel. + /// </summary> + /// <typeparam name="TSource">The type of the elements in <paramref name="source"/>.</typeparam> + /// <param name="source">The Partitioner that contains the original data source.</param> + /// <param name="body">The delegate that is invoked once per iteration.</param> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the <paramref name="source"/> + /// argument is null.</exception> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the <paramref name="body"/> + /// argument is null.</exception> + /// <exception cref="T:System.InvalidOperationException">The exception that is thrown when the + /// SupportsDynamicPartitions property in the <paramref name="source"/> Partitioner returns + /// false.</exception> + /// <exception cref="T:System.InvalidOperationException">The exception that is thrown when any + /// methods in the <paramref name="source"/> Partitioner return null.</exception> + /// <exception cref="T:System.InvalidOperationException">The exception that is thrown when the + /// GetPartitions() method in the <paramref name="source"/> Partitioner does not return + /// the correct number of partitions.</exception> + /// <exception cref="T:System.InvalidOperationException">The exception that is thrown when the + /// GetPartitions() method in the <paramref name="source"/> Partitioner returns an IList + /// with at least one null value.</exception> + /// <exception cref="T:System.InvalidOperationException">The exception that is thrown when the + /// GetDynamicPartitions() method in the <paramref name="source"/> Partitioner returns an + /// IEnumerable whose GetEnumerator() method returns null.</exception> + /// <exception cref="T:System.AggregateException">The exception that is thrown to contain an exception + /// thrown from one of the specified delegates.</exception> + /// <returns>A <see cref="T:System.Threading.Tasks.ParallelLoopResult">ParallelLoopResult</see> structure + /// that contains information on what portion of the loop completed.</returns> + /// <remarks> + /// <para> + /// The <see cref="T:System.Collections.Concurrent.Partitioner{TSource}">Partitioner</see> is used to retrieve + /// the elements to be processed, in place of the original data source. If the current element's + /// index is desired, the source must be an <see cref="T:System.Collections.Concurrent.OrderablePartitioner"> + /// OrderablePartitioner</see>. + /// </para> + /// <para> + /// The <paramref name="body"/> delegate is invoked once for each element in the <paramref name="source"/> + /// Partitioner. It is provided with the following parameters: the current element, + /// and a <see cref="System.Threading.Tasks.ParallelLoopState">ParallelLoopState</see> instance that may be + /// used to break out of the loop prematurely. + /// </para> + /// </remarks> + public static ParallelLoopResult ForEach<TSource>( + Partitioner<TSource> source, + Action<TSource, ParallelLoopState> body) + { + if (source == null) + { + throw new ArgumentNullException("source"); + } + if (body == null) + { + throw new ArgumentNullException("body"); + } + + return PartitionerForEachWorker<TSource, object>(source, s_defaultParallelOptions, null, body, null, null, null, null, null); + } + + /// <summary> + /// Executes a for each operation on a <see cref="T:System.Collections.Concurrent.OrderablePartitioner{TSource}"> + /// OrderablePartitioner</see> in which iterations may run in parallel. + /// </summary> + /// <typeparam name="TSource">The type of the elements in <paramref name="source"/>.</typeparam> + /// <param name="source">The OrderablePartitioner that contains the original data source.</param> + /// <param name="body">The delegate that is invoked once per iteration.</param> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the <paramref name="source"/> + /// argument is null.</exception> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the <paramref name="body"/> + /// argument is null.</exception> + /// <exception cref="T:System.InvalidOperationException">The exception that is thrown when the + /// SupportsDynamicPartitions property in the <paramref name="source"/> OrderablePartitioner returns + /// false.</exception> + /// <exception cref="T:System.InvalidOperationException">The exception that is thrown when the + /// KeysNormalized property in the <paramref name="source"/> OrderablePartitioner returns + /// false.</exception> + /// <exception cref="T:System.InvalidOperationException">The exception that is thrown when any + /// methods in the <paramref name="source"/> OrderablePartitioner return null.</exception> + /// <exception cref="T:System.InvalidOperationException">The exception that is thrown when the + /// GetPartitions() or GetOrderablePartitions() methods in the <paramref name="source"/> + /// OrderablePartitioner do not return the correct number of partitions.</exception> + /// <exception cref="T:System.InvalidOperationException">The exception that is thrown when the + /// GetPartitions() or GetOrderablePartitions() methods in the <paramref name="source"/> + /// OrderablePartitioner return an IList with at least one null value.</exception> + /// <exception cref="T:System.InvalidOperationException">The exception that is thrown when the + /// GetDynamicPartitions() or GetDynamicOrderablePartitions() methods in the <paramref name="source"/> + /// OrderablePartitioner return an IEnumerable whose GetEnumerator() method returns null.</exception> + /// <exception cref="T:System.AggregateException">The exception that is thrown to contain an exception + /// thrown from one of the specified delegates.</exception> + /// <returns>A <see cref="T:System.Threading.Tasks.ParallelLoopResult">ParallelLoopResult</see> structure + /// that contains information on what portion of the loop completed.</returns> + /// <remarks> + /// <para> + /// The <see cref="T:System.Collections.Concurrent.Partitioner{TSource}">Partitioner</see> is used to retrieve + /// the elements to be processed, in place of the original data source. If the current element's + /// index is desired, the source must be an <see cref="T:System.Collections.Concurrent.OrderablePartitioner"> + /// OrderablePartitioner</see>. + /// </para> + /// <para> + /// The <paramref name="body"/> delegate is invoked once for each element in the <paramref name="source"/> + /// Partitioner. It is provided with the following parameters: the current element, + /// a <see cref="System.Threading.Tasks.ParallelLoopState">ParallelLoopState</see> instance that may be + /// used to break out of the loop prematurely, and the current element's index (an Int64). + /// </para> + /// </remarks> + public static ParallelLoopResult ForEach<TSource>( + OrderablePartitioner<TSource> source, + Action<TSource, ParallelLoopState, long> body) + { + if (source == null) + { + throw new ArgumentNullException("source"); + } + if (body == null) + { + throw new ArgumentNullException("body"); + } + + if (!source.KeysNormalized) + { + throw new InvalidOperationException(Environment.GetResourceString("Parallel_ForEach_OrderedPartitionerKeysNotNormalized")); + } + + return PartitionerForEachWorker<TSource, object>(source, s_defaultParallelOptions, null, null, body, null, null, null, null); + } + + /// <summary> + /// Executes a for each operation on a <see cref="T:System.Collections.Concurrent.Partitioner{TSource}"> + /// Partitioner</see> in which iterations may run in parallel. + /// </summary> + /// <typeparam name="TSource">The type of the elements in <paramref name="source"/>.</typeparam> + /// <typeparam name="TLocal">The type of the thread-local data.</typeparam> + /// <param name="source">The Partitioner that contains the original data source.</param> + /// <param name="localInit">The function delegate that returns the initial state of the local data + /// for each thread.</param> + /// <param name="body">The delegate that is invoked once per iteration.</param> + /// <param name="localFinally">The delegate that performs a final action on the local state of each + /// thread.</param> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the <paramref name="source"/> + /// argument is null.</exception> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the <paramref name="body"/> + /// argument is null.</exception> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the + /// <paramref name="localInit"/> argument is null.</exception> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the + /// <paramref name="localFinally"/> argument is null.</exception> + /// <exception cref="T:System.InvalidOperationException">The exception that is thrown when the + /// SupportsDynamicPartitions property in the <paramref name="source"/> Partitioner returns + /// false.</exception> + /// <exception cref="T:System.InvalidOperationException">The exception that is thrown when any + /// methods in the <paramref name="source"/> Partitioner return null.</exception> + /// <exception cref="T:System.InvalidOperationException">The exception that is thrown when the + /// GetPartitions() method in the <paramref name="source"/> Partitioner does not return + /// the correct number of partitions.</exception> + /// <exception cref="T:System.InvalidOperationException">The exception that is thrown when the + /// GetPartitions() method in the <paramref name="source"/> Partitioner returns an IList + /// with at least one null value.</exception> + /// <exception cref="T:System.InvalidOperationException">The exception that is thrown when the + /// GetDynamicPartitions() method in the <paramref name="source"/> Partitioner returns an + /// IEnumerable whose GetEnumerator() method returns null.</exception> + /// <exception cref="T:System.AggregateException">The exception that is thrown to contain an exception + /// thrown from one of the specified delegates.</exception> + /// <returns>A <see cref="T:System.Threading.Tasks.ParallelLoopResult">ParallelLoopResult</see> structure + /// that contains information on what portion of the loop completed.</returns> + /// <remarks> + /// <para> + /// The <see cref="T:System.Collections.Concurrent.Partitioner{TSource}">Partitioner</see> is used to retrieve + /// the elements to be processed, in place of the original data source. If the current element's + /// index is desired, the source must be an <see cref="T:System.Collections.Concurrent.OrderablePartitioner"> + /// OrderablePartitioner</see>. + /// </para> + /// <para> + /// The <paramref name="body"/> delegate is invoked once for each element in the <paramref name="source"/> + /// Partitioner. It is provided with the following parameters: the current element, + /// a <see cref="System.Threading.Tasks.ParallelLoopState">ParallelLoopState</see> instance that may be + /// used to break out of the loop prematurely, and some local state that may be shared amongst iterations + /// that execute on the same thread. + /// </para> + /// <para> + /// The <paramref name="localInit"/> delegate is invoked once for each thread that participates in the loop's + /// execution and returns the initial local state for each of those threads. These initial states are passed to the first + /// <paramref name="body"/> invocations on each thread. Then, every subsequent body invocation returns a possibly + /// modified state value that is passed to the next body invocation. Finally, the last body invocation on each thread returns a state value + /// that is passed to the <paramref name="localFinally"/> delegate. The localFinally delegate is invoked once per thread to perform a final + /// action on each thread's local state. + /// </para> + /// </remarks> + public static ParallelLoopResult ForEach<TSource, TLocal>( + Partitioner<TSource> source, + Func<TLocal> localInit, + Func<TSource, ParallelLoopState, TLocal, TLocal> body, + Action<TLocal> localFinally) + { + if (source == null) + { + throw new ArgumentNullException("source"); + } + if (body == null) + { + throw new ArgumentNullException("body"); + } + if (localInit == null) + { + throw new ArgumentNullException("localInit"); + } + if (localFinally == null) + { + throw new ArgumentNullException("localFinally"); + } + + return PartitionerForEachWorker<TSource, TLocal>(source, s_defaultParallelOptions, null, null, null, body, null, localInit, localFinally); + } + + /// <summary> + /// Executes a for each operation on a <see cref="T:System.Collections.Concurrent.OrderablePartitioner{TSource}"> + /// OrderablePartitioner</see> in which iterations may run in parallel. + /// </summary> + /// <typeparam name="TSource">The type of the elements in <paramref name="source"/>.</typeparam> + /// <typeparam name="TLocal">The type of the thread-local data.</typeparam> + /// <param name="source">The OrderablePartitioner that contains the original data source.</param> + /// <param name="localInit">The function delegate that returns the initial state of the local data + /// for each thread.</param> + /// <param name="body">The delegate that is invoked once per iteration.</param> + /// <param name="localFinally">The delegate that performs a final action on the local state of each + /// thread.</param> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the <paramref name="source"/> + /// argument is null.</exception> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the <paramref name="body"/> + /// argument is null.</exception> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the + /// <paramref name="localInit"/> argument is null.</exception> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the + /// <paramref name="localFinally"/> argument is null.</exception> + /// <exception cref="T:System.InvalidOperationException">The exception that is thrown when the + /// SupportsDynamicPartitions property in the <paramref name="source"/> OrderablePartitioner returns + /// false.</exception> + /// <exception cref="T:System.InvalidOperationException">The exception that is thrown when the + /// KeysNormalized property in the <paramref name="source"/> OrderablePartitioner returns + /// false.</exception> + /// <exception cref="T:System.InvalidOperationException">The exception that is thrown when any + /// methods in the <paramref name="source"/> OrderablePartitioner return null.</exception> + /// <exception cref="T:System.InvalidOperationException">The exception that is thrown when the + /// GetPartitions() or GetOrderablePartitions() methods in the <paramref name="source"/> + /// OrderablePartitioner do not return the correct number of partitions.</exception> + /// <exception cref="T:System.InvalidOperationException">The exception that is thrown when the + /// GetPartitions() or GetOrderablePartitions() methods in the <paramref name="source"/> + /// OrderablePartitioner return an IList with at least one null value.</exception> + /// <exception cref="T:System.InvalidOperationException">The exception that is thrown when the + /// GetDynamicPartitions() or GetDynamicOrderablePartitions() methods in the <paramref name="source"/> + /// OrderablePartitioner return an IEnumerable whose GetEnumerator() method returns null.</exception> + /// <exception cref="T:System.AggregateException">The exception that is thrown to contain an exception + /// thrown from one of the specified delegates.</exception> + /// <returns>A <see cref="T:System.Threading.Tasks.ParallelLoopResult">ParallelLoopResult</see> structure + /// that contains information on what portion of the loop completed.</returns> + /// <remarks> + /// <para> + /// The <see cref="T:System.Collections.Concurrent.Partitioner{TSource}">Partitioner</see> is used to retrieve + /// the elements to be processed, in place of the original data source. If the current element's + /// index is desired, the source must be an <see cref="T:System.Collections.Concurrent.OrderablePartitioner"> + /// OrderablePartitioner</see>. + /// </para> + /// <para> + /// The <paramref name="body"/> delegate is invoked once for each element in the <paramref name="source"/> + /// Partitioner. It is provided with the following parameters: the current element, + /// a <see cref="System.Threading.Tasks.ParallelLoopState">ParallelLoopState</see> instance that may be + /// used to break out of the loop prematurely, the current element's index (an Int64), and some local + /// state that may be shared amongst iterations that execute on the same thread. + /// </para> + /// <para> + /// The <paramref name="localInit"/> delegate is invoked once for each thread that participates in the loop's + /// execution and returns the initial local state for each of those threads. These initial states are passed to the first + /// <paramref name="body"/> invocations on each thread. Then, every subsequent body invocation returns a possibly + /// modified state value that is passed to the next body invocation. Finally, the last body invocation on each thread returns a state value + /// that is passed to the <paramref name="localFinally"/> delegate. The localFinally delegate is invoked once per thread to perform a final + /// action on each thread's local state. + /// </para> + /// </remarks> + public static ParallelLoopResult ForEach<TSource, TLocal>( + OrderablePartitioner<TSource> source, + Func<TLocal> localInit, + Func<TSource, ParallelLoopState, long, TLocal, TLocal> body, + Action<TLocal> localFinally) + { + if (source == null) + { + throw new ArgumentNullException("source"); + } + if (body == null) + { + throw new ArgumentNullException("body"); + } + if (localInit == null) + { + throw new ArgumentNullException("localInit"); + } + if (localFinally == null) + { + throw new ArgumentNullException("localFinally"); + } + + if (!source.KeysNormalized) + { + throw new InvalidOperationException(Environment.GetResourceString("Parallel_ForEach_OrderedPartitionerKeysNotNormalized")); + } + + return PartitionerForEachWorker<TSource, TLocal>(source, s_defaultParallelOptions, null, null, null, null, body, localInit, localFinally); + } + + /// <summary> + /// Executes a for each operation on a <see cref="T:System.Collections.Concurrent.Partitioner{TSource}"> + /// Partitioner</see> in which iterations may run in parallel. + /// </summary> + /// <typeparam name="TSource">The type of the elements in <paramref name="source"/>.</typeparam> + /// <param name="source">The Partitioner that contains the original data source.</param> + /// <param name="parallelOptions">A <see cref="T:System.Threading.Tasks.ParallelOptions">ParallelOptions</see> + /// instance that configures the behavior of this operation.</param> + /// <param name="body">The delegate that is invoked once per iteration.</param> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the <paramref name="source"/> + /// argument is null.</exception> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the + /// <paramref name="parallelOptions"/> argument is null.</exception> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the <paramref name="body"/> + /// argument is null.</exception> + /// <exception cref="T:System.OperationCanceledException">The exception that is thrown when the + /// <see cref="T:System.Threading.CancellationToken">CancellationToken</see> in the <paramref name="parallelOptions"/> + /// argument is set</exception> + /// <exception cref="T:System.InvalidOperationException">The exception that is thrown when the + /// SupportsDynamicPartitions property in the <paramref name="source"/> Partitioner returns + /// false.</exception> + /// <exception cref="T:System.InvalidOperationException">The exception that is thrown when any + /// methods in the <paramref name="source"/> Partitioner return null.</exception> + /// <exception cref="T:System.InvalidOperationException">The exception that is thrown when the + /// GetPartitions() method in the <paramref name="source"/> Partitioner does not return + /// the correct number of partitions.</exception> + /// <exception cref="T:System.InvalidOperationException">The exception that is thrown when the + /// GetPartitions() method in the <paramref name="source"/> Partitioner returns an IList + /// with at least one null value.</exception> + /// <exception cref="T:System.InvalidOperationException">The exception that is thrown when the + /// GetDynamicPartitions() method in the <paramref name="source"/> Partitioner returns an + /// IEnumerable whose GetEnumerator() method returns null.</exception> + /// <exception cref="T:System.AggregateException">The exception that is thrown to contain an exception + /// thrown from one of the specified delegates.</exception> + /// <exception cref="T:System.ObjectDisposedException">The exception that is thrown when the + /// the <see cref="T:System.Threading.CancellationTokenSource">CancellationTokenSource</see> associated with the + /// the <see cref="T:System.Threading.CancellationToken">CancellationToken</see> in the + /// <paramref name="parallelOptions"/> has been disposed.</exception> + /// <returns>A <see cref="T:System.Threading.Tasks.ParallelLoopResult">ParallelLoopResult</see> structure + /// that contains information on what portion of the loop completed.</returns> + /// <remarks> + /// <para> + /// The <see cref="T:System.Collections.Concurrent.Partitioner{TSource}">Partitioner</see> is used to retrieve + /// the elements to be processed, in place of the original data source. If the current element's + /// index is desired, the source must be an <see cref="T:System.Collections.Concurrent.OrderablePartitioner"> + /// OrderablePartitioner</see>. + /// </para> + /// <para> + /// The <paramref name="body"/> delegate is invoked once for each element in the <paramref name="source"/> + /// Partitioner. It is provided with the current element as a parameter. + /// </para> + /// </remarks> + public static ParallelLoopResult ForEach<TSource>( + Partitioner<TSource> source, + ParallelOptions parallelOptions, + Action<TSource> body) + { + if (source == null) + { + throw new ArgumentNullException("source"); + } + if (body == null) + { + throw new ArgumentNullException("body"); + } + if (parallelOptions == null) + { + throw new ArgumentNullException("parallelOptions"); + } + + return PartitionerForEachWorker<TSource, object>(source, parallelOptions, body, null, null, null, null, null, null); + } + + /// <summary> + /// Executes a for each operation on a <see cref="T:System.Collections.Concurrent.Partitioner{TSource}"> + /// Partitioner</see> in which iterations may run in parallel. + /// </summary> + /// <typeparam name="TSource">The type of the elements in <paramref name="source"/>.</typeparam> + /// <param name="source">The Partitioner that contains the original data source.</param> + /// <param name="parallelOptions">A <see cref="T:System.Threading.Tasks.ParallelOptions">ParallelOptions</see> + /// instance that configures the behavior of this operation.</param> + /// <param name="body">The delegate that is invoked once per iteration.</param> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the <paramref name="source"/> + /// argument is null.</exception> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the + /// <paramref name="parallelOptions"/> argument is null.</exception> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the <paramref name="body"/> + /// argument is null.</exception> + /// <exception cref="T:System.OperationCanceledException">The exception that is thrown when the + /// <see cref="T:System.Threading.CancellationToken">CancellationToken</see> in the <paramref name="parallelOptions"/> + /// argument is set</exception> + /// <exception cref="T:System.InvalidOperationException">The exception that is thrown when the + /// SupportsDynamicPartitions property in the <paramref name="source"/> Partitioner returns + /// false.</exception> + /// <exception cref="T:System.InvalidOperationException">The exception that is thrown when any + /// methods in the <paramref name="source"/> Partitioner return null.</exception> + /// <exception cref="T:System.InvalidOperationException">The exception that is thrown when the + /// GetPartitions() method in the <paramref name="source"/> Partitioner does not return + /// the correct number of partitions.</exception> + /// <exception cref="T:System.InvalidOperationException">The exception that is thrown when the + /// GetPartitions() method in the <paramref name="source"/> Partitioner returns an IList + /// with at least one null value.</exception> + /// <exception cref="T:System.InvalidOperationException">The exception that is thrown when the + /// GetDynamicPartitions() method in the <paramref name="source"/> Partitioner returns an + /// IEnumerable whose GetEnumerator() method returns null.</exception> + /// <exception cref="T:System.AggregateException">The exception that is thrown to contain an exception + /// thrown from one of the specified delegates.</exception> + /// <exception cref="T:System.ObjectDisposedException">The exception that is thrown when the + /// the <see cref="T:System.Threading.CancellationTokenSource">CancellationTokenSource</see> associated with the + /// the <see cref="T:System.Threading.CancellationToken">CancellationToken</see> in the + /// <paramref name="parallelOptions"/> has been disposed.</exception> + /// <returns>A <see cref="T:System.Threading.Tasks.ParallelLoopResult">ParallelLoopResult</see> structure + /// that contains information on what portion of the loop completed.</returns> + /// <remarks> + /// <para> + /// The <see cref="T:System.Collections.Concurrent.Partitioner{TSource}">Partitioner</see> is used to retrieve + /// the elements to be processed, in place of the original data source. If the current element's + /// index is desired, the source must be an <see cref="T:System.Collections.Concurrent.OrderablePartitioner"> + /// OrderablePartitioner</see>. + /// </para> + /// <para> + /// The <paramref name="body"/> delegate is invoked once for each element in the <paramref name="source"/> + /// Partitioner. It is provided with the following parameters: the current element, + /// and a <see cref="System.Threading.Tasks.ParallelLoopState">ParallelLoopState</see> instance that may be + /// used to break out of the loop prematurely. + /// </para> + /// </remarks> + public static ParallelLoopResult ForEach<TSource>( + Partitioner<TSource> source, + ParallelOptions parallelOptions, + Action<TSource, ParallelLoopState> body) + { + if (source == null) + { + throw new ArgumentNullException("source"); + } + if (body == null) + { + throw new ArgumentNullException("body"); + } + if (parallelOptions == null) + { + throw new ArgumentNullException("parallelOptions"); + } + + return PartitionerForEachWorker<TSource, object>(source, parallelOptions, null, body, null, null, null, null, null); + } + + /// <summary> + /// Executes a for each operation on a <see cref="T:System.Collections.Concurrent.OrderablePartitioner{TSource}"> + /// OrderablePartitioner</see> in which iterations may run in parallel. + /// </summary> + /// <typeparam name="TSource">The type of the elements in <paramref name="source"/>.</typeparam> + /// <param name="source">The OrderablePartitioner that contains the original data source.</param> + /// <param name="parallelOptions">A <see cref="T:System.Threading.Tasks.ParallelOptions">ParallelOptions</see> + /// instance that configures the behavior of this operation.</param> + /// <param name="body">The delegate that is invoked once per iteration.</param> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the <paramref name="source"/> + /// argument is null.</exception> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the + /// <paramref name="parallelOptions"/> argument is null.</exception> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the <paramref name="body"/> + /// argument is null.</exception> + /// <exception cref="T:System.OperationCanceledException">The exception that is thrown when the + /// <see cref="T:System.Threading.CancellationToken">CancellationToken</see> in the <paramref name="parallelOptions"/> + /// argument is set</exception> + /// <exception cref="T:System.InvalidOperationException">The exception that is thrown when the + /// SupportsDynamicPartitions property in the <paramref name="source"/> OrderablePartitioner returns + /// false.</exception> + /// <exception cref="T:System.InvalidOperationException">The exception that is thrown when the + /// KeysNormalized property in the <paramref name="source"/> OrderablePartitioner returns + /// false.</exception> + /// <exception cref="T:System.InvalidOperationException">The exception that is thrown when any + /// methods in the <paramref name="source"/> OrderablePartitioner return null.</exception> + /// <exception cref="T:System.InvalidOperationException">The exception that is thrown when the + /// GetPartitions() or GetOrderablePartitions() methods in the <paramref name="source"/> + /// OrderablePartitioner do not return the correct number of partitions.</exception> + /// <exception cref="T:System.InvalidOperationException">The exception that is thrown when the + /// GetPartitions() or GetOrderablePartitions() methods in the <paramref name="source"/> + /// OrderablePartitioner return an IList with at least one null value.</exception> + /// <exception cref="T:System.InvalidOperationException">The exception that is thrown when the + /// GetDynamicPartitions() or GetDynamicOrderablePartitions() methods in the <paramref name="source"/> + /// OrderablePartitioner return an IEnumerable whose GetEnumerator() method returns null.</exception> + /// <exception cref="T:System.AggregateException">The exception that is thrown to contain an exception + /// thrown from one of the specified delegates.</exception> + /// <exception cref="T:System.ObjectDisposedException">The exception that is thrown when the + /// the <see cref="T:System.Threading.CancellationTokenSource">CancellationTokenSource</see> associated with the + /// the <see cref="T:System.Threading.CancellationToken">CancellationToken</see> in the + /// <paramref name="parallelOptions"/> has been disposed.</exception> + /// <returns>A <see cref="T:System.Threading.Tasks.ParallelLoopResult">ParallelLoopResult</see> structure + /// that contains information on what portion of the loop completed.</returns> + /// <remarks> + /// <para> + /// The <see cref="T:System.Collections.Concurrent.Partitioner{TSource}">Partitioner</see> is used to retrieve + /// the elements to be processed, in place of the original data source. If the current element's + /// index is desired, the source must be an <see cref="T:System.Collections.Concurrent.OrderablePartitioner"> + /// OrderablePartitioner</see>. + /// </para> + /// <para> + /// The <paramref name="body"/> delegate is invoked once for each element in the <paramref name="source"/> + /// Partitioner. It is provided with the following parameters: the current element, + /// a <see cref="System.Threading.Tasks.ParallelLoopState">ParallelLoopState</see> instance that may be + /// used to break out of the loop prematurely, and the current element's index (an Int64). + /// </para> + /// </remarks> + public static ParallelLoopResult ForEach<TSource>( + OrderablePartitioner<TSource> source, + ParallelOptions parallelOptions, + Action<TSource, ParallelLoopState, long> body) + { + if (source == null) + { + throw new ArgumentNullException("source"); + } + if (body == null) + { + throw new ArgumentNullException("body"); + } + if (parallelOptions == null) + { + throw new ArgumentNullException("parallelOptions"); + } + + if (!source.KeysNormalized) + { + throw new InvalidOperationException(Environment.GetResourceString("Parallel_ForEach_OrderedPartitionerKeysNotNormalized")); + } + + return PartitionerForEachWorker<TSource, object>(source, parallelOptions, null, null, body, null, null, null, null); + } + + /// <summary> + /// Executes a for each operation on a <see cref="T:System.Collections.Concurrent.Partitioner{TSource}"> + /// Partitioner</see> in which iterations may run in parallel. + /// </summary> + /// <typeparam name="TSource">The type of the elements in <paramref name="source"/>.</typeparam> + /// <typeparam name="TLocal">The type of the thread-local data.</typeparam> + /// <param name="source">The Partitioner that contains the original data source.</param> + /// <param name="parallelOptions">A <see cref="T:System.Threading.Tasks.ParallelOptions">ParallelOptions</see> + /// instance that configures the behavior of this operation.</param> + /// <param name="localInit">The function delegate that returns the initial state of the local data + /// for each thread.</param> + /// <param name="body">The delegate that is invoked once per iteration.</param> + /// <param name="localFinally">The delegate that performs a final action on the local state of each + /// thread.</param> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the <paramref name="source"/> + /// argument is null.</exception> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the + /// <paramref name="parallelOptions"/> argument is null.</exception> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the <paramref name="body"/> + /// argument is null.</exception> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the + /// <paramref name="localInit"/> argument is null.</exception> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the + /// <paramref name="localFinally"/> argument is null.</exception> + /// <exception cref="T:System.OperationCanceledException">The exception that is thrown when the + /// <see cref="T:System.Threading.CancellationToken">CancellationToken</see> in the <paramref name="parallelOptions"/> + /// argument is set</exception> + /// <exception cref="T:System.InvalidOperationException">The exception that is thrown when the + /// SupportsDynamicPartitions property in the <paramref name="source"/> Partitioner returns + /// false.</exception> + /// <exception cref="T:System.InvalidOperationException">The exception that is thrown when any + /// methods in the <paramref name="source"/> Partitioner return null.</exception> + /// <exception cref="T:System.InvalidOperationException">The exception that is thrown when the + /// GetPartitions() method in the <paramref name="source"/> Partitioner does not return + /// the correct number of partitions.</exception> + /// <exception cref="T:System.InvalidOperationException">The exception that is thrown when the + /// GetPartitions() method in the <paramref name="source"/> Partitioner returns an IList + /// with at least one null value.</exception> + /// <exception cref="T:System.InvalidOperationException">The exception that is thrown when the + /// GetDynamicPartitions() method in the <paramref name="source"/> Partitioner returns an + /// IEnumerable whose GetEnumerator() method returns null.</exception> + /// <exception cref="T:System.AggregateException">The exception that is thrown to contain an exception + /// thrown from one of the specified delegates.</exception> + /// <exception cref="T:System.ObjectDisposedException">The exception that is thrown when the + /// the <see cref="T:System.Threading.CancellationTokenSource">CancellationTokenSource</see> associated with the + /// the <see cref="T:System.Threading.CancellationToken">CancellationToken</see> in the + /// <paramref name="parallelOptions"/> has been disposed.</exception> + /// <returns>A <see cref="T:System.Threading.Tasks.ParallelLoopResult">ParallelLoopResult</see> structure + /// that contains information on what portion of the loop completed.</returns> + /// <remarks> + /// <para> + /// The <see cref="T:System.Collections.Concurrent.Partitioner{TSource}">Partitioner</see> is used to retrieve + /// the elements to be processed, in place of the original data source. If the current element's + /// index is desired, the source must be an <see cref="T:System.Collections.Concurrent.OrderablePartitioner"> + /// OrderablePartitioner</see>. + /// </para> + /// <para> + /// The <paramref name="body"/> delegate is invoked once for each element in the <paramref name="source"/> + /// Partitioner. It is provided with the following parameters: the current element, + /// a <see cref="System.Threading.Tasks.ParallelLoopState">ParallelLoopState</see> instance that may be + /// used to break out of the loop prematurely, and some local state that may be shared amongst iterations + /// that execute on the same thread. + /// </para> + /// <para> + /// The <paramref name="localInit"/> delegate is invoked once for each thread that participates in the loop's + /// execution and returns the initial local state for each of those threads. These initial states are passed to the first + /// <paramref name="body"/> invocations on each thread. Then, every subsequent body invocation returns a possibly + /// modified state value that is passed to the next body invocation. Finally, the last body invocation on each thread returns a state value + /// that is passed to the <paramref name="localFinally"/> delegate. The localFinally delegate is invoked once per thread to perform a final + /// action on each thread's local state. + /// </para> + /// </remarks> + public static ParallelLoopResult ForEach<TSource, TLocal>( + Partitioner<TSource> source, + ParallelOptions parallelOptions, + Func<TLocal> localInit, + Func<TSource, ParallelLoopState, TLocal, TLocal> body, + Action<TLocal> localFinally) + { + if (source == null) + { + throw new ArgumentNullException("source"); + } + if (body == null) + { + throw new ArgumentNullException("body"); + } + if (localInit == null) + { + throw new ArgumentNullException("localInit"); + } + if (localFinally == null) + { + throw new ArgumentNullException("localFinally"); + } + if (parallelOptions == null) + { + throw new ArgumentNullException("parallelOptions"); + } + + return PartitionerForEachWorker<TSource, TLocal>(source, parallelOptions, null, null, null, body, null, localInit, localFinally); + } + + /// <summary> + /// Executes a for each operation on a <see cref="T:System.Collections.Concurrent.OrderablePartitioner{TSource}"> + /// OrderablePartitioner</see> in which iterations may run in parallel. + /// </summary> + /// <typeparam name="TSource">The type of the elements in <paramref name="source"/>.</typeparam> + /// <typeparam name="TLocal">The type of the thread-local data.</typeparam> + /// <param name="source">The OrderablePartitioner that contains the original data source.</param> + /// <param name="parallelOptions">A <see cref="T:System.Threading.Tasks.ParallelOptions">ParallelOptions</see> + /// instance that configures the behavior of this operation.</param> + /// <param name="localInit">The function delegate that returns the initial state of the local data + /// for each thread.</param> + /// <param name="body">The delegate that is invoked once per iteration.</param> + /// <param name="localFinally">The delegate that performs a final action on the local state of each + /// thread.</param> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the <paramref name="source"/> + /// argument is null.</exception> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the + /// <paramref name="parallelOptions"/> argument is null.</exception> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the <paramref name="body"/> + /// argument is null.</exception> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the + /// <paramref name="localInit"/> argument is null.</exception> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the + /// <paramref name="localFinally"/> argument is null.</exception> + /// <exception cref="T:System.OperationCanceledException">The exception that is thrown when the + /// <see cref="T:System.Threading.CancellationToken">CancellationToken</see> in the <paramref name="parallelOptions"/> + /// argument is set</exception> + /// <exception cref="T:System.InvalidOperationException">The exception that is thrown when the + /// SupportsDynamicPartitions property in the <paramref name="source"/> OrderablePartitioner returns + /// false.</exception> + /// <exception cref="T:System.InvalidOperationException">The exception that is thrown when the + /// KeysNormalized property in the <paramref name="source"/> OrderablePartitioner returns + /// false.</exception> + /// <exception cref="T:System.InvalidOperationException">The exception that is thrown when any + /// methods in the <paramref name="source"/> OrderablePartitioner return null.</exception> + /// <exception cref="T:System.InvalidOperationException">The exception that is thrown when the + /// GetPartitions() or GetOrderablePartitions() methods in the <paramref name="source"/> + /// OrderablePartitioner do not return the correct number of partitions.</exception> + /// <exception cref="T:System.InvalidOperationException">The exception that is thrown when the + /// GetPartitions() or GetOrderablePartitions() methods in the <paramref name="source"/> + /// OrderablePartitioner return an IList with at least one null value.</exception> + /// <exception cref="T:System.InvalidOperationException">The exception that is thrown when the + /// GetDynamicPartitions() or GetDynamicOrderablePartitions() methods in the <paramref name="source"/> + /// OrderablePartitioner return an IEnumerable whose GetEnumerator() method returns null.</exception> + /// <exception cref="T:System.AggregateException">The exception that is thrown to contain an exception + /// thrown from one of the specified delegates.</exception> + /// <exception cref="T:System.ObjectDisposedException">The exception that is thrown when the + /// the <see cref="T:System.Threading.CancellationTokenSource">CancellationTokenSource</see> associated with the + /// the <see cref="T:System.Threading.CancellationToken">CancellationToken</see> in the + /// <paramref name="parallelOptions"/> has been disposed.</exception> + /// <returns>A <see cref="T:System.Threading.Tasks.ParallelLoopResult">ParallelLoopResult</see> structure + /// that contains information on what portion of the loop completed.</returns> + /// <remarks> + /// <para> + /// The <see cref="T:System.Collections.Concurrent.Partitioner{TSource}">Partitioner</see> is used to retrieve + /// the elements to be processed, in place of the original data source. If the current element's + /// index is desired, the source must be an <see cref="T:System.Collections.Concurrent.OrderablePartitioner"> + /// OrderablePartitioner</see>. + /// </para> + /// <para> + /// The <paramref name="body"/> delegate is invoked once for each element in the <paramref name="source"/> + /// Partitioner. It is provided with the following parameters: the current element, + /// a <see cref="System.Threading.Tasks.ParallelLoopState">ParallelLoopState</see> instance that may be + /// used to break out of the loop prematurely, the current element's index (an Int64), and some local + /// state that may be shared amongst iterations that execute on the same thread. + /// </para> + /// <para> + /// The <paramref name="localInit"/> delegate is invoked once for each thread that participates in the loop's + /// execution and returns the initial local state for each of those threads. These initial states are passed to the first + /// <paramref name="body"/> invocations on each thread. Then, every subsequent body invocation returns a possibly + /// modified state value that is passed to the next body invocation. Finally, the last body invocation on each thread returns a state value + /// that is passed to the <paramref name="localFinally"/> delegate. The localFinally delegate is invoked once per thread to perform a final + /// action on each thread's local state. + /// </para> + /// </remarks> + public static ParallelLoopResult ForEach<TSource, TLocal>( + OrderablePartitioner<TSource> source, + ParallelOptions parallelOptions, + Func<TLocal> localInit, + Func<TSource, ParallelLoopState, long, TLocal, TLocal> body, + Action<TLocal> localFinally) + { + if (source == null) + { + throw new ArgumentNullException("source"); + } + if (body == null) + { + throw new ArgumentNullException("body"); + } + if (localInit == null) + { + throw new ArgumentNullException("localInit"); + } + if (localFinally == null) + { + throw new ArgumentNullException("localFinally"); + } + if (parallelOptions == null) + { + throw new ArgumentNullException("parallelOptions"); + } + + if (!source.KeysNormalized) + { + throw new InvalidOperationException(Environment.GetResourceString("Parallel_ForEach_OrderedPartitionerKeysNotNormalized")); + } + + return PartitionerForEachWorker<TSource, TLocal>(source, parallelOptions, null, null, null, null, body, localInit, localFinally); + } + + // Main worker method for Parallel.ForEach() calls w/ Partitioners. + private static ParallelLoopResult PartitionerForEachWorker<TSource, TLocal>( + Partitioner<TSource> source, // Might be OrderablePartitioner + ParallelOptions parallelOptions, + Action<TSource> simpleBody, + Action<TSource, ParallelLoopState> bodyWithState, + Action<TSource, ParallelLoopState, long> bodyWithStateAndIndex, + Func<TSource, ParallelLoopState, TLocal, TLocal> bodyWithStateAndLocal, + Func<TSource, ParallelLoopState, long, TLocal, TLocal> bodyWithEverything, + Func<TLocal> localInit, + Action<TLocal> localFinally) + { + Contract.Assert(((simpleBody == null ? 0 : 1) + (bodyWithState == null ? 0 : 1) + + (bodyWithStateAndIndex == null ? 0 : 1) + (bodyWithStateAndLocal == null ? 0 : 1) + (bodyWithEverything == null ? 0 : 1)) == 1, + "PartitionForEach: expected exactly one body function to be supplied"); + Contract.Assert((bodyWithStateAndLocal != null) || (bodyWithEverything != null) || (localInit == null && localFinally == null), + "PartitionForEach: thread local functions should only be supplied for loops w/ thread local bodies"); + + OrderablePartitioner<TSource> orderedSource = source as OrderablePartitioner<TSource>; + Contract.Assert((orderedSource != null) || (bodyWithStateAndIndex == null && bodyWithEverything == null), + "PartitionForEach: bodies with indices are only allowable for OrderablePartitioner"); + + if (!source.SupportsDynamicPartitions) + { + throw new InvalidOperationException(Environment.GetResourceString("Parallel_ForEach_PartitionerNotDynamic")); + } + + // Before getting started, do a quick peek to see if we have been canceled already + if (parallelOptions.CancellationToken.IsCancellationRequested) + { + throw new OperationCanceledException(parallelOptions.CancellationToken); + } + + // ETW event for Parallel For begin + int forkJoinContextID = 0; + Task callerTask = null; + if (TplEtwProvider.Log.IsEnabled()) + { + forkJoinContextID = Interlocked.Increment(ref s_forkJoinContextID); + callerTask = Task.InternalCurrent; + TplEtwProvider.Log.ParallelLoopBegin((callerTask != null ? callerTask.m_taskScheduler.Id : TaskScheduler.Current.Id), (callerTask != null ? callerTask.Id : 0), + forkJoinContextID, TplEtwProvider.ForkJoinOperationType.ParallelForEach, + 0, 0); + } + + // For all loops we need a shared flag even though we don't have a body with state, + // because the shared flag contains the exceptional bool, which triggers other workers + // to exit their loops if one worker catches an exception + ParallelLoopStateFlags64 sharedPStateFlags = new ParallelLoopStateFlags64(); + + // Instantiate our result. Specifics will be filled in later. + ParallelLoopResult result = new ParallelLoopResult(); + + // Keep track of any cancellations + OperationCanceledException oce = null; + + CancellationTokenRegistration ctr = new CancellationTokenRegistration(); + + // if cancellation is enabled, we need to register a callback to stop the loop when it gets signaled + if (parallelOptions.CancellationToken.CanBeCanceled) + { + ctr = parallelOptions.CancellationToken.InternalRegisterWithoutEC((o) => + { + // Cause processing to stop + sharedPStateFlags.Cancel(); + // Record our cancellation + oce = new OperationCanceledException(parallelOptions.CancellationToken); + }, null); + } + + // Get our dynamic partitioner -- depends on whether source is castable to OrderablePartitioner + // Also, do some error checking. + IEnumerable<TSource> partitionerSource = null; + IEnumerable<KeyValuePair<long, TSource>> orderablePartitionerSource = null; + if (orderedSource != null) + { + orderablePartitionerSource = orderedSource.GetOrderableDynamicPartitions(); + if (orderablePartitionerSource == null) + { + throw new InvalidOperationException(Environment.GetResourceString("Parallel_ForEach_PartitionerReturnedNull")); + } + } + else + { + partitionerSource = source.GetDynamicPartitions(); + if (partitionerSource == null) + { + throw new InvalidOperationException(Environment.GetResourceString("Parallel_ForEach_PartitionerReturnedNull")); + } + } + + ParallelForReplicatingTask rootTask = null; + + // This is the action that will be run by each replicable task. + Action partitionAction = delegate + { + Task currentWorkerTask = Task.InternalCurrent; + + // ETW event for ParallelForEach Worker Fork + if (TplEtwProvider.Log.IsEnabled()) + { + TplEtwProvider.Log.ParallelFork((currentWorkerTask != null ? currentWorkerTask.m_taskScheduler.Id : TaskScheduler.Current.Id), (currentWorkerTask != null ? currentWorkerTask.Id : 0), + forkJoinContextID); + } + + TLocal localValue = default(TLocal); + bool bLocalValueInitialized = false; // Tracks whether localInit ran without exceptions, so that we can skip localFinally if it wasn't + IDisposable myPartitionToDispose = null; + + try + { + // Create a new state object that references the shared "stopped" and "exceptional" flags. + // If needed, it will contain a new instance of thread-local state by invoking the selector. + ParallelLoopState64 state = null; + + if (bodyWithState != null || bodyWithStateAndIndex != null) + { + state = new ParallelLoopState64(sharedPStateFlags); + } + else if (bodyWithStateAndLocal != null || bodyWithEverything != null) + { + state = new ParallelLoopState64(sharedPStateFlags); + // If a thread-local selector was supplied, invoke it. Otherwise, stick with the default. + if (localInit != null) + { + localValue = localInit(); + bLocalValueInitialized = true; + } + } + + + bool bIsRootTask = (rootTask == currentWorkerTask); + + // initialize a loop timer which will help us decide whether we should exit early + LoopTimer loopTimer = new LoopTimer(rootTask.ActiveChildCount); + + if (orderedSource != null) + { + // Use this path for OrderablePartitioner + + + // first check if there's saved state from a previous replica that we might be replacing. + // the only state to be passed down in such a transition is the enumerator + IEnumerator<KeyValuePair<long, TSource>> myPartition = currentWorkerTask.SavedStateFromPreviousReplica as IEnumerator<KeyValuePair<long, TSource>>; + if (myPartition == null) + { + // apparently we're a brand new replica, get a fresh enumerator from the partitioner + myPartition = orderablePartitionerSource.GetEnumerator(); + if (myPartition == null) + { + throw new InvalidOperationException(Environment.GetResourceString("Parallel_ForEach_NullEnumerator")); + } + } + myPartitionToDispose = myPartition; + + while (myPartition.MoveNext()) + { + KeyValuePair<long, TSource> kvp = myPartition.Current; + long index = kvp.Key; + TSource value = kvp.Value; + + // Update our iteration index + if (state != null) state.CurrentIteration = index; + + if (simpleBody != null) + simpleBody(value); + else if (bodyWithState != null) + bodyWithState(value, state); + else if (bodyWithStateAndIndex != null) + bodyWithStateAndIndex(value, state, index); + else if (bodyWithStateAndLocal != null) + localValue = bodyWithStateAndLocal(value, state, localValue); + else + localValue = bodyWithEverything(value, state, index, localValue); + + if (sharedPStateFlags.ShouldExitLoop(index)) break; + + // Cooperative multitasking workaround for AppDomain fairness. + // Check if allowed loop time is exceeded, if so save current state and return. The self replicating task logic + // will detect this, and queue up a replacement task. Note that we don't do this on the root task. + if (!bIsRootTask && loopTimer.LimitExceeded()) + { + currentWorkerTask.SavedStateForNextReplica = myPartition; + myPartitionToDispose = null; + break; + } + } + + } + else + { + // Use this path for Partitioner that is not OrderablePartitioner + + // first check if there's saved state from a previous replica that we might be replacing. + // the only state to be passed down in such a transition is the enumerator + IEnumerator<TSource> myPartition = currentWorkerTask.SavedStateFromPreviousReplica as IEnumerator<TSource>; + if (myPartition == null) + { + // apparently we're a brand new replica, get a fresh enumerator from the partitioner + myPartition = partitionerSource.GetEnumerator(); + if (myPartition == null) + { + throw new InvalidOperationException(Environment.GetResourceString("Parallel_ForEach_NullEnumerator")); + } + } + myPartitionToDispose = myPartition; + + // I'm not going to try to maintain this + if (state != null) + state.CurrentIteration = 0; + + while (myPartition.MoveNext()) + { + TSource t = myPartition.Current; + + if (simpleBody != null) + simpleBody(t); + else if (bodyWithState != null) + bodyWithState(t, state); + else if (bodyWithStateAndLocal != null) + localValue = bodyWithStateAndLocal(t, state, localValue); + else + Contract.Assert(false, "PartitionerForEach: illegal body type in Partitioner handler"); + + + // Any break, stop or exception causes us to halt + // We don't have the global indexing information to discriminate whether or not + // we are before or after a break point. + if (sharedPStateFlags.LoopStateFlags != ParallelLoopStateFlags.PLS_NONE) + break; + + // Cooperative multitasking workaround for AppDomain fairness. + // Check if allowed loop time is exceeded, if so save current state and return. The self replicating task logic + // will detect this, and queue up a replacement task. Note that we don't do this on the root task. + if (!bIsRootTask && loopTimer.LimitExceeded()) + { + currentWorkerTask.SavedStateForNextReplica = myPartition; + myPartitionToDispose = null; + break; + } + } + } + } + catch + { + // Inform other tasks of the exception, then rethrow + sharedPStateFlags.SetExceptional(); + throw; + } + finally + { + if (localFinally != null && bLocalValueInitialized) + { + localFinally(localValue); + } + + if (myPartitionToDispose != null) + { + myPartitionToDispose.Dispose(); + } + + // ETW event for ParallelFor Worker Join + if (TplEtwProvider.Log.IsEnabled()) + { + TplEtwProvider.Log.ParallelJoin((currentWorkerTask != null ? currentWorkerTask.m_taskScheduler.Id : TaskScheduler.Current.Id), (currentWorkerTask != null ? currentWorkerTask.Id : 0), + forkJoinContextID); + } + } + }; + + try + { + // Create and start the self-replicating task. + // This needs to be in try-block because it can throw in BuggyScheduler.MaxConcurrencyLevel + rootTask = new ParallelForReplicatingTask(parallelOptions, partitionAction, TaskCreationOptions.None, + InternalTaskOptions.SelfReplicating); + + // And process it's completion... + // Moved inside try{} block because faulty scheduler may throw here. + rootTask.RunSynchronously(parallelOptions.EffectiveTaskScheduler); + + rootTask.Wait(); + + // If we made a cancellation registration, we need to clean it up now before observing the OCE + // Otherwise we could be caught in the middle of a callback, and observe PLS_STOPPED, but oce = null + if (parallelOptions.CancellationToken.CanBeCanceled) + { + ctr.Dispose(); + } + + // If we got through that with no exceptions, and we were canceled, then + // throw our cancellation exception + if (oce != null) throw oce; + } + catch (AggregateException aggExp) + { + // if we made a cancellation registration, and rootTask.Wait threw, we need to clean it up here + if (parallelOptions.CancellationToken.CanBeCanceled) + { + ctr.Dispose(); + } + + // see if we can combine it into a single OCE. If not propagate the original exception + ThrowIfReducableToSingleOCE(aggExp.InnerExceptions, parallelOptions.CancellationToken); + throw; + } + catch (TaskSchedulerException) + { + // if we made a cancellation registration, and either we threw an exception constructing rootTask or + // rootTask.RunSynchronously threw, we need to clean it up here. + if (parallelOptions.CancellationToken.CanBeCanceled) + { + ctr.Dispose(); + } + throw; + } + finally + { + int sb_status = sharedPStateFlags.LoopStateFlags; + result.m_completed = (sb_status == ParallelLoopStateFlags.PLS_NONE); + if ((sb_status & ParallelLoopStateFlags.PLS_BROKEN) != 0) + { + result.m_lowestBreakIteration = sharedPStateFlags.LowestBreakIteration; + } + + if ((rootTask != null) && rootTask.IsCompleted) rootTask.Dispose(); + + //dispose the partitioner source if it implements IDisposable + IDisposable d = null; + if (orderablePartitionerSource != null) + { + d = orderablePartitionerSource as IDisposable; + } + else + { + d = partitionerSource as IDisposable; + } + + if (d != null) + { + d.Dispose(); + } + + // ETW event for Parallel For End + if (TplEtwProvider.Log.IsEnabled()) + { + TplEtwProvider.Log.ParallelLoopEnd((callerTask != null ? callerTask.m_taskScheduler.Id : TaskScheduler.Current.Id), (callerTask != null ? callerTask.Id : 0), + forkJoinContextID, 0); + } + } + + return result; + } + + /// <summary> + /// Internal utility function that implements the OCE filtering behavior for all Parallel.* APIs. + /// Throws a single OperationCancelledException object with the token if the Exception collection only contains + /// OperationCancelledExceptions with the given CancellationToken. + /// + /// </summary> + /// <param name="excCollection"> The exception collection to filter</param> + /// <param name="ct"> The CancellationToken expected on all inner exceptions</param> + /// <returns></returns> + internal static void ThrowIfReducableToSingleOCE(IEnumerable<Exception> excCollection, CancellationToken ct) + { + bool bCollectionNotZeroLength = false; + if (ct.IsCancellationRequested) + { + foreach (Exception e in excCollection) + { + bCollectionNotZeroLength = true; + OperationCanceledException oce = e as OperationCanceledException; + if (oce == null || oce.CancellationToken != ct) + { + // mismatch found + return; + } + } + + // all exceptions are OCEs with this token, let's throw it + if (bCollectionNotZeroLength) throw new OperationCanceledException(ct); + } + } + + internal struct LoopTimer + { + public LoopTimer(int nWorkerTaskIndex) + { + // This logic ensures that we have a diversity of timeouts across worker tasks (100, 150, 200, 250, 100, etc) + // Otherwise all worker will try to timeout at precisely the same point, which is bad if the work is just about to finish + int timeOut = s_BaseNotifyPeriodMS + (nWorkerTaskIndex % PlatformHelper.ProcessorCount) * s_NotifyPeriodIncrementMS; + + m_timeLimit = Environment.TickCount + timeOut; + } + + public bool LimitExceeded() + { + Contract.Assert(m_timeLimit != 0, "Probably the default initializer for LoopTimer was used somewhere"); + + // comparing against the next expected time saves an addition operation here + // Also we omit the comparison for wrap around here. The only side effect is one extra early yield every 38 days. + return (Environment.TickCount > m_timeLimit); + } + + const int s_BaseNotifyPeriodMS = 100; + const int s_NotifyPeriodIncrementMS = 50; + + private int m_timeLimit; + } + + } + +} diff --git a/src/mscorlib/src/System/Threading/Tasks/ParallelLoopState.cs b/src/mscorlib/src/System/Threading/Tasks/ParallelLoopState.cs new file mode 100644 index 0000000000..4db3a9d105 --- /dev/null +++ b/src/mscorlib/src/System/Threading/Tasks/ParallelLoopState.cs @@ -0,0 +1,642 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +// =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ +// +// ParallelState.cs +// +// +// A non-generic and generic parallel state class, used by the Parallel helper class +// for parallel loop management. +// +// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + +using System.Diagnostics; +using System.Security.Permissions; +using System.Diagnostics.Contracts; + +// Prevents compiler warnings/errors regarding the use of ref params in Interlocked methods +#pragma warning disable 0420 + +namespace System.Threading.Tasks +{ + + /// <summary> + /// Enables iterations of <see cref="T:System.Threading.Tasks.Parallel"/> loops to interact with + /// other iterations. + /// </summary> + [HostProtection(Synchronization = true, ExternalThreading = true)] + [DebuggerDisplay("ShouldExitCurrentIteration = {ShouldExitCurrentIteration}")] + public class ParallelLoopState + { + // Derived classes will track a ParallelStateFlags32 or ParallelStateFlags64. + // So this is slightly redundant, but it enables us to implement some + // methods in this base class. + private ParallelLoopStateFlags m_flagsBase; + + internal ParallelLoopState(ParallelLoopStateFlags fbase) + { + m_flagsBase = fbase; + } + + /// <summary> + /// Internal/virtual support for ShouldExitCurrentIteration. + /// </summary> + internal virtual bool InternalShouldExitCurrentIteration + { + get + { + Contract.Assert(false); + throw new NotSupportedException( + Environment.GetResourceString("ParallelState_NotSupportedException_UnsupportedMethod")); + } + } + + /// <summary> + /// Gets whether the current iteration of the loop should exit based + /// on requests made by this or other iterations. + /// </summary> + /// <remarks> + /// When an iteration of a loop calls <see cref="Break()"/> or <see cref="Stop()"/>, or + /// when one throws an exception, or when the loop is canceled, the <see cref="Parallel"/> class will proactively + /// attempt to prohibit additional iterations of the loop from starting execution. + /// However, there may be cases where it is unable to prevent additional iterations from starting. + /// It may also be the case that a long-running iteration has already begun execution. In such + /// cases, iterations may explicitly check the <see cref="ShouldExitCurrentIteration"/> property and + /// cease execution if the property returns true. + /// </remarks> + public bool ShouldExitCurrentIteration + { + get + { + return InternalShouldExitCurrentIteration; + } + } + + /// <summary> + /// Gets whether any iteration of the loop has called <see cref="Stop()"/>. + /// </summary> + public bool IsStopped + { + get + { + return ((m_flagsBase.LoopStateFlags & ParallelLoopStateFlags.PLS_STOPPED) != 0); + } + } + + /// <summary> + /// Gets whether any iteration of the loop has thrown an exception that went unhandled by that + /// iteration. + /// </summary> + public bool IsExceptional + { + get + { + return ((m_flagsBase.LoopStateFlags & ParallelLoopStateFlags.PLS_EXCEPTIONAL) != 0); + } + } + + /// <summary> + /// Internal/virtual support for LowestBreakIteration. + /// </summary> + internal virtual long? InternalLowestBreakIteration + { + get + { + Contract.Assert(false); + throw new NotSupportedException( + Environment.GetResourceString("ParallelState_NotSupportedException_UnsupportedMethod")); + } + } + + /// <summary> + /// Gets the lowest iteration of the loop from which <see cref="Break()"/> was called. + /// </summary> + /// <remarks> + /// If no iteration of the loop called <see cref="Break()"/>, this property will return null. + /// </remarks> + public long? LowestBreakIteration + { + get + { + return InternalLowestBreakIteration; + } + } + + /// <summary> + /// Communicates that the <see cref="Parallel"/> loop should cease execution at the system's earliest + /// convenience. + /// </summary> + /// <exception cref="T:System.InvalidOperationException"> + /// The <see cref="Break()"/> method was previously called. <see cref="Break()"/> and <see + /// cref="Stop()"/> may not be used in combination by iterations of the same loop. + /// </exception> + /// <remarks> + /// <para> + /// <see cref="Stop()"/> may be used to communicate to the loop that no other iterations need be run. + /// For long-running iterations that may already be executing, <see cref="Stop()"/> causes <see cref="IsStopped"/> + /// to return true for all other iterations of the loop, such that another iteration may check <see + /// cref="IsStopped"/> and exit early if it's observed to be true. + /// </para> + /// <para> + /// <see cref="Stop()"/> is typically employed in search-based algorithms, where once a result is found, + /// no other iterations need be executed. + /// </para> + /// </remarks> + public void Stop() + { + m_flagsBase.Stop(); + } + + // Internal/virtual support for Break(). + internal virtual void InternalBreak() + { + Contract.Assert(false); + throw new NotSupportedException( + Environment.GetResourceString("ParallelState_NotSupportedException_UnsupportedMethod")); + } + + /// <summary> + /// Communicates that the <see cref="Parallel"/> loop should cease execution at the system's earliest + /// convenience of iterations beyond the current iteration. + /// </summary> + /// <exception cref="T:System.InvalidOperationException"> + /// The <see cref="Stop()"/> method was previously called. <see cref="Break()"/> and <see cref="Stop()"/> + /// may not be used in combination by iterations of the same loop. + /// </exception> + /// <remarks> + /// <para> + /// <see cref="Break()"/> may be used to communicate to the loop that no other iterations after the + /// current iteration need be run. For example, if <see cref="Break()"/> is called from the 100th + /// iteration of a for loop iterating in parallel from 0 to 1000, all iterations less than 100 should + /// still be run, but the iterations from 101 through to 1000 are not necessary. + /// </para> + /// <para> + /// For long-running iterations that may already be executing, <see cref="Break()"/> causes <see + /// cref="LowestBreakIteration"/> + /// to be set to the current iteration's index if the current index is less than the current value of + /// <see cref="LowestBreakIteration"/>. + /// </para> + /// <para> + /// <see cref="Break()"/> is typically employed in search-based algorithms where an ordering is + /// present in the data source. + /// </para> + /// </remarks> + public void Break() + { + InternalBreak(); + } + + // Helper method to avoid repeating Break() logic between ParallelState32 and ParallelState32<TLocal> + internal static void Break(int iteration, ParallelLoopStateFlags32 pflags) + { + int oldValue = ParallelLoopStateFlags.PLS_NONE; + + // Attempt to change state from "not stopped or broken or canceled or exceptional" to "broken". + if (!pflags.AtomicLoopStateUpdate(ParallelLoopStateFlags.PLS_BROKEN, + ParallelLoopStateFlags.PLS_STOPPED | ParallelLoopStateFlags.PLS_EXCEPTIONAL | ParallelLoopStateFlags.PLS_CANCELED, + ref oldValue)) + { + + // If we were already stopped, we have a problem + if ((oldValue & ParallelLoopStateFlags.PLS_STOPPED) != 0) + { + throw new InvalidOperationException( + Environment.GetResourceString("ParallelState_Break_InvalidOperationException_BreakAfterStop")); + } + else + { + // Apparently we previously got cancelled or became exceptional. No action necessary + return; + } + } + + // replace shared LowestBreakIteration with CurrentIteration, but only if CurrentIteration + // is less than LowestBreakIteration. + int oldLBI = pflags.m_lowestBreakIteration; + if (iteration < oldLBI) + { + SpinWait wait = new SpinWait(); + while (Interlocked.CompareExchange( + ref pflags.m_lowestBreakIteration, + iteration, + oldLBI) != oldLBI) + { + wait.SpinOnce(); + oldLBI = pflags.m_lowestBreakIteration; + if (iteration > oldLBI) break; + } + } + + } + + // Helper method to avoid repeating Break() logic between ParallelState64 and ParallelState64<TLocal> + internal static void Break(long iteration, ParallelLoopStateFlags64 pflags) + { + int oldValue = ParallelLoopStateFlags.PLS_NONE; + + // Attempt to change state from "not stopped or broken or canceled or exceptional" to "broken". + if (!pflags.AtomicLoopStateUpdate(ParallelLoopStateFlags.PLS_BROKEN, + ParallelLoopStateFlags.PLS_STOPPED | ParallelLoopStateFlags.PLS_EXCEPTIONAL | ParallelLoopStateFlags.PLS_CANCELED, + ref oldValue)) + { + + // If we were already stopped, we have a problem + if ((oldValue & ParallelLoopStateFlags.PLS_STOPPED) != 0) + { + throw new InvalidOperationException( + Environment.GetResourceString("ParallelState_Break_InvalidOperationException_BreakAfterStop")); + } + else + { + // Apparently we previously got cancelled or became exceptional. No action necessary + return; + } + } + + // replace shared LowestBreakIteration with CurrentIteration, but only if CurrentIteration + // is less than LowestBreakIteration. + long oldLBI = pflags.LowestBreakIteration; + if (iteration < oldLBI) + { + SpinWait wait = new SpinWait(); + while (Interlocked.CompareExchange( + ref pflags.m_lowestBreakIteration, + iteration, + oldLBI) != oldLBI) + { + wait.SpinOnce(); + oldLBI = pflags.LowestBreakIteration; + if (iteration > oldLBI) break; + } + } + + } + } + + internal class ParallelLoopState32 : ParallelLoopState + { + private ParallelLoopStateFlags32 m_sharedParallelStateFlags; + private int m_currentIteration = 0; + + /// <summary> + /// Internal constructor to ensure an instance isn't created by users. + /// </summary> + /// <param name="sharedParallelStateFlags">A flag shared among all threads participating + /// in the execution of a certain loop.</param> + internal ParallelLoopState32(ParallelLoopStateFlags32 sharedParallelStateFlags) + : base(sharedParallelStateFlags) + { + m_sharedParallelStateFlags = sharedParallelStateFlags; + } + + /// <summary> + /// Tracks the current loop iteration for the owning task. + /// This is used to compute whether or not the task should + /// terminate early due to a Break() call. + /// </summary> + internal int CurrentIteration { + get { return m_currentIteration; } + set { m_currentIteration = value; } + } + + /// <summary> + /// Returns true if we should be exiting from the current iteration + /// due to Stop(), Break() or exception. + /// </summary> + internal override bool InternalShouldExitCurrentIteration + { + get { return m_sharedParallelStateFlags.ShouldExitLoop(CurrentIteration); } + } + + /// <summary> + /// Returns the lowest iteration at which Break() has been called, or + /// null if Break() has not yet been called. + /// </summary> + internal override long? InternalLowestBreakIteration + { + get {return m_sharedParallelStateFlags.NullableLowestBreakIteration; } + } + + /// <summary> + /// Communicates that parallel tasks should stop when they reach a specified iteration element. + /// (which is CurrentIteration of the caller). + /// </summary> + /// <exception cref="T:System.InvalidOperationException">Break() called after Stop().</exception> + /// <remarks> + /// This is shared with all other concurrent threads in the system which are participating in the + /// loop's execution. After calling Break(), no additional iterations will be executed on + /// the current thread, and other worker threads will execute once they get beyond the calling iteration. + /// </remarks> + internal override void InternalBreak() + { + ParallelLoopState.Break(CurrentIteration, m_sharedParallelStateFlags); + } + } + + /// <summary> + /// Allows independent iterations of a parallel loop to interact with other iterations. + /// </summary> + internal class ParallelLoopState64 : ParallelLoopState + { + private ParallelLoopStateFlags64 m_sharedParallelStateFlags; + private long m_currentIteration = 0; + + /// <summary> + /// Internal constructor to ensure an instance isn't created by users. + /// </summary> + /// <param name="sharedParallelStateFlags">A flag shared among all threads participating + /// in the execution of a certain loop.</param> + internal ParallelLoopState64(ParallelLoopStateFlags64 sharedParallelStateFlags) + : base(sharedParallelStateFlags) + { + m_sharedParallelStateFlags = sharedParallelStateFlags; + } + + /// <summary> + /// Tracks the current loop iteration for the owning task. + /// This is used to compute whether or not the task should + /// terminate early due to a Break() call. + /// </summary> + internal long CurrentIteration + { + // No interlocks needed, because this value is only accessed in a single thread. + get {return m_currentIteration;} + set {m_currentIteration = value; } + } + + /// <summary> + /// Returns true if we should be exiting from the current iteration + /// due to Stop(), Break() or exception. + /// </summary> + internal override bool InternalShouldExitCurrentIteration + { + get { return m_sharedParallelStateFlags.ShouldExitLoop(CurrentIteration); } + } + + /// <summary> + /// Returns the lowest iteration at which Break() has been called, or + /// null if Break() has not yet been called. + /// </summary> + internal override long? InternalLowestBreakIteration + { + // We don't need to worry about torn read/write here because + // ParallelStateFlags64.LowestBreakIteration property is protected + // by an Interlocked.Read(). + get { return m_sharedParallelStateFlags.NullableLowestBreakIteration; } + } + + /// <summary> + /// Communicates that parallel tasks should stop when they reach a specified iteration element. + /// (which is CurrentIteration of the caller). + /// </summary> + /// <exception cref="T:System.InvalidOperationException">Break() called after Stop().</exception> + /// <remarks> + /// Atomically sets shared StoppedBroken flag to BROKEN, then atomically sets shared + /// LowestBreakIteration to CurrentIteration, but only if CurrentIteration is less than + /// LowestBreakIteration. + /// </remarks> + internal override void InternalBreak() + { + ParallelLoopState.Break(CurrentIteration, m_sharedParallelStateFlags); + } + + } + + /// <summary> + /// State information that is common between ParallelStateFlags class + /// and ParallelStateFlags64 class. + /// </summary> + internal class ParallelLoopStateFlags + { + internal static int PLS_NONE; + internal static int PLS_EXCEPTIONAL = 1; + internal static int PLS_BROKEN = 2; + internal static int PLS_STOPPED = 4; + internal static int PLS_CANCELED = 8; + + private volatile int m_LoopStateFlags = PLS_NONE; + + internal int LoopStateFlags + { + get { return m_LoopStateFlags; } + } + + internal bool AtomicLoopStateUpdate(int newState, int illegalStates) + { + int oldState = 0; + return AtomicLoopStateUpdate(newState, illegalStates, ref oldState); + } + + internal bool AtomicLoopStateUpdate(int newState, int illegalStates, ref int oldState) + { + SpinWait sw = new SpinWait(); + + do + { + oldState = m_LoopStateFlags; + if ((oldState & illegalStates) != 0) return false; + if (Interlocked.CompareExchange(ref m_LoopStateFlags, oldState | newState, oldState) == oldState) + { + return true; + } + sw.SpinOnce(); + } while (true); + + } + + internal void SetExceptional() + { + // we can set the exceptional flag regardless of the state of other bits. + AtomicLoopStateUpdate(PLS_EXCEPTIONAL, PLS_NONE); + } + + internal void Stop() + { + // disallow setting of PLS_STOPPED bit only if PLS_BROKEN was already set + if (!AtomicLoopStateUpdate(PLS_STOPPED, PLS_BROKEN)) + { + throw new InvalidOperationException( + Environment.GetResourceString("ParallelState_Stop_InvalidOperationException_StopAfterBreak")); + } + } + + // Returns true if StoppedBroken is updated to PLS_CANCELED. + internal bool Cancel() + { + // we can set the canceled flag regardless of the state of other bits. + return (AtomicLoopStateUpdate(PLS_CANCELED, PLS_NONE)); + } + } + + /// <summary> + /// An internal class used to share accounting information in 32-bit versions + /// of For()/ForEach() loops. + /// </summary> + internal class ParallelLoopStateFlags32 : ParallelLoopStateFlags + { + // Records the lowest iteration at which a Break() has been called, + // or Int32.MaxValue if no break has been called. Used directly + // by Break(). + internal volatile int m_lowestBreakIteration = Int32.MaxValue; + + // Not strictly necessary, but maintains consistency with ParallelStateFlags64 + internal int LowestBreakIteration + { + get { return m_lowestBreakIteration; } + } + + // Does some processing to convert m_lowestBreakIteration to a long?. + internal long? NullableLowestBreakIteration + { + get + { + if (m_lowestBreakIteration == Int32.MaxValue) return null; + else + { + // protect against torn read of 64-bit value + long rval = m_lowestBreakIteration; + if (IntPtr.Size >= 8) return rval; + else return Interlocked.Read(ref rval); + } + } + } + + + /// <summary> + /// Lets the caller know whether or not to prematurely exit the For/ForEach loop. + /// If this returns true, then exit the loop. Otherwise, keep going. + /// </summary> + /// <param name="CallerIteration">The caller's current iteration point + /// in the loop.</param> + /// <remarks> + /// The loop should exit on any one of the following conditions: + /// (1) Stop() has been called by one or more tasks. + /// (2) An exception has been raised by one or more tasks. + /// (3) Break() has been called by one or more tasks, and + /// CallerIteration exceeds the (lowest) iteration at which + /// Break() was called. + /// (4) The loop was canceled. + /// </remarks> + internal bool ShouldExitLoop(int CallerIteration) + { + int flags = LoopStateFlags; + return (flags != PLS_NONE && ( + ((flags & (PLS_EXCEPTIONAL | PLS_STOPPED | PLS_CANCELED)) != 0) || + (((flags & PLS_BROKEN) != 0) && (CallerIteration > LowestBreakIteration)))); + } + + // This lighter version of ShouldExitLoop will be used when the body type doesn't contain a state. + // Since simpler bodies cannot stop or break, we can safely skip checks for those flags here. + internal bool ShouldExitLoop() + { + int flags = LoopStateFlags; + return ((flags != PLS_NONE) && ((flags & (PLS_EXCEPTIONAL | PLS_CANCELED)) != 0)); + } + } + + /// <summary> + /// An internal class used to share accounting information in 64-bit versions + /// of For()/ForEach() loops. + /// </summary> + internal class ParallelLoopStateFlags64 : ParallelLoopStateFlags + { + // Records the lowest iteration at which a Break() has been called, + // or Int64.MaxValue if no break has been called. Used directly + // by Break(). + internal long m_lowestBreakIteration = Int64.MaxValue; + + // Performs a conditionally interlocked read of m_lowestBreakIteration. + internal long LowestBreakIteration + { + get + { + if (IntPtr.Size >= 8) return m_lowestBreakIteration; + else return Interlocked.Read(ref m_lowestBreakIteration); + } + } + + // Does some processing to convert m_lowestBreakIteration to a long?. + internal long? NullableLowestBreakIteration + { + get + { + if (m_lowestBreakIteration == Int64.MaxValue) return null; + else + { + if (IntPtr.Size >= 8) return m_lowestBreakIteration; + else return Interlocked.Read(ref m_lowestBreakIteration); + } + } + } + + /// <summary> + /// Lets the caller know whether or not to prematurely exit the For/ForEach loop. + /// If this returns true, then exit the loop. Otherwise, keep going. + /// </summary> + /// <param name="CallerIteration">The caller's current iteration point + /// in the loop.</param> + /// <remarks> + /// The loop should exit on any one of the following conditions: + /// (1) Stop() has been called by one or more tasks. + /// (2) An exception has been raised by one or more tasks. + /// (3) Break() has been called by one or more tasks, and + /// CallerIteration exceeds the (lowest) iteration at which + /// Break() was called. + /// (4) The loop has been canceled. + /// </remarks> + internal bool ShouldExitLoop(long CallerIteration) + { + int flags = LoopStateFlags; + return (flags != PLS_NONE && ( + ((flags & (PLS_EXCEPTIONAL | PLS_STOPPED | PLS_CANCELED)) != 0) || + (((flags & PLS_BROKEN) != 0) && (CallerIteration > LowestBreakIteration)))); + } + + // This lighter version of ShouldExitLoop will be used when the body type doesn't contain a state. + // Since simpler bodies cannot stop or break, we can safely skip checks for those flags here. + internal bool ShouldExitLoop() + { + int flags = LoopStateFlags; + return ((flags != PLS_NONE) && ((flags & (PLS_EXCEPTIONAL | PLS_CANCELED)) != 0)); + } + } + + /// <summary> + /// Provides completion status on the execution of a <see cref="Parallel"/> loop. + /// </summary> + /// <remarks> + /// If <see cref="IsCompleted"/> returns true, then the loop ran to completion, such that all iterations + /// of the loop were executed. If <see cref="IsCompleted"/> returns false and <see + /// cref="LowestBreakIteration"/> returns null, a call to <see + /// cref="System.Threading.Tasks.ParallelLoopState.Stop"/> was used to end the loop prematurely. If <see + /// cref="IsCompleted"/> returns false and <see cref="LowestBreakIteration"/> returns a non-null integral + /// value, <see cref="System.Threading.Tasks.ParallelLoopState.Break()"/> was used to end the loop prematurely. + /// </remarks> + public struct ParallelLoopResult + { + internal bool m_completed; + internal long? m_lowestBreakIteration; + + /// <summary> + /// Gets whether the loop ran to completion, such that all iterations of the loop were executed + /// and the loop didn't receive a request to end prematurely. + /// </summary> + public bool IsCompleted { get { return m_completed; } } + + /// <summary> + /// Gets the index of the lowest iteration from which <see + /// cref="System.Threading.Tasks.ParallelLoopState.Break()"/> + /// was called. + /// </summary> + /// <remarks> + /// If <see cref="System.Threading.Tasks.ParallelLoopState.Break()"/> was not employed, this property will + /// return null. + /// </remarks> + public long? LowestBreakIteration { get { return m_lowestBreakIteration; } } + } + +} + +#pragma warning restore 0420 diff --git a/src/mscorlib/src/System/Threading/Tasks/ParallelRangeManager.cs b/src/mscorlib/src/System/Threading/Tasks/ParallelRangeManager.cs new file mode 100644 index 0000000000..c4b66c41a9 --- /dev/null +++ b/src/mscorlib/src/System/Threading/Tasks/ParallelRangeManager.cs @@ -0,0 +1,278 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +// =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ +// +// +// +// Implements the algorithm for distributing loop indices to parallel loop workers +// +// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + +using System; +using System.Threading; +using System.Diagnostics.Contracts; + +#pragma warning disable 0420 + +namespace System.Threading.Tasks +{ + /// <summary> + /// Represents an index range + /// </summary> + internal struct IndexRange + { + // the From and To values for this range. These do not change. + internal long m_nFromInclusive; + internal long m_nToExclusive; + + // The shared index, stored as the offset from nFromInclusive. Using an offset rather than the actual + // value saves us from overflows that can happen due to multiple workers racing to increment this. + // All updates to this field need to be interlocked. + internal volatile Shared<long> m_nSharedCurrentIndexOffset; + + // to be set to 1 by the worker that finishes this range. It's OK to do a non-interlocked write here. + internal int m_bRangeFinished; + } + + + /// <summary> + /// The RangeWorker struct wraps the state needed by a task that services the parallel loop + /// </summary> + internal struct RangeWorker + { + // reference to the IndexRange array allocated by the range manager + internal readonly IndexRange[] m_indexRanges; + + // index of the current index range that this worker is grabbing chunks from + internal int m_nCurrentIndexRange; + + // the step for this loop. Duplicated here for quick access (rather than jumping to rangemanager) + internal long m_nStep; + + // increment value is the current amount that this worker will use + // to increment the shared index of the range it's working on + internal long m_nIncrementValue; + + // the increment value is doubled each time this worker finds work, and is capped at this value + internal readonly long m_nMaxIncrementValue; + + /// <summary> + /// Initializes a RangeWorker struct + /// </summary> + internal RangeWorker(IndexRange[] ranges, int nInitialRange, long nStep) + { + m_indexRanges = ranges; + m_nCurrentIndexRange = nInitialRange; + m_nStep = nStep; + + m_nIncrementValue = nStep; + + m_nMaxIncrementValue = Parallel.DEFAULT_LOOP_STRIDE * nStep; + } + + /// <summary> + /// Implements the core work search algorithm that will be used for this range worker. + /// </summary> + /// + /// Usage pattern is: + /// 1) the thread associated with this rangeworker calls FindNewWork + /// 2) if we return true, the worker uses the nFromInclusiveLocal and nToExclusiveLocal values + /// to execute the sequential loop + /// 3) if we return false it means there is no more work left. It's time to quit. + /// + internal bool FindNewWork(out long nFromInclusiveLocal, out long nToExclusiveLocal) + { + // since we iterate over index ranges circularly, we will use the + // count of visited ranges as our exit condition + int numIndexRangesToVisit = m_indexRanges.Length; + + do + { + // local snap to save array access bounds checks in places where we only read fields + IndexRange currentRange = m_indexRanges[m_nCurrentIndexRange]; + + if (currentRange.m_bRangeFinished == 0) + { + if (m_indexRanges[m_nCurrentIndexRange].m_nSharedCurrentIndexOffset == null) + { + Interlocked.CompareExchange(ref m_indexRanges[m_nCurrentIndexRange].m_nSharedCurrentIndexOffset, new Shared<long>(0), null); + } + + // this access needs to be on the array slot + long nMyOffset = Interlocked.Add(ref m_indexRanges[m_nCurrentIndexRange].m_nSharedCurrentIndexOffset.Value, + m_nIncrementValue) - m_nIncrementValue; + + if (currentRange.m_nToExclusive - currentRange.m_nFromInclusive > nMyOffset) + { + // we found work + + nFromInclusiveLocal = currentRange.m_nFromInclusive + nMyOffset; + nToExclusiveLocal = nFromInclusiveLocal + m_nIncrementValue; + + // Check for going past end of range, or wrapping + if ( (nToExclusiveLocal > currentRange.m_nToExclusive) || (nToExclusiveLocal < currentRange.m_nFromInclusive) ) + { + nToExclusiveLocal = currentRange.m_nToExclusive; + } + + // We will double our unit of increment until it reaches the maximum. + if (m_nIncrementValue < m_nMaxIncrementValue) + { + m_nIncrementValue *= 2; + if (m_nIncrementValue > m_nMaxIncrementValue) + { + m_nIncrementValue = m_nMaxIncrementValue; + } + } + + return true; + } + else + { + // this index range is completed, mark it so that others can skip it quickly + Interlocked.Exchange(ref m_indexRanges[m_nCurrentIndexRange].m_bRangeFinished, 1); + } + } + + // move on to the next index range, in circular order. + m_nCurrentIndexRange = (m_nCurrentIndexRange + 1) % m_indexRanges.Length; + numIndexRangesToVisit--; + + } while (numIndexRangesToVisit > 0); + // we've visited all index ranges possible => there's no work remaining + + nFromInclusiveLocal = 0; + nToExclusiveLocal = 0; + + return false; + } + + + /// <summary> + /// 32 bit integer version of FindNewWork. Assumes the ranges were initialized with 32 bit values. + /// </summary> + internal bool FindNewWork32(out int nFromInclusiveLocal32, out int nToExclusiveLocal32) + { + long nFromInclusiveLocal; + long nToExclusiveLocal; + + bool bRetVal = FindNewWork(out nFromInclusiveLocal, out nToExclusiveLocal); + + Contract.Assert((nFromInclusiveLocal <= Int32.MaxValue) && (nFromInclusiveLocal >= Int32.MinValue) && + (nToExclusiveLocal <= Int32.MaxValue) && (nToExclusiveLocal >= Int32.MinValue)); + + // convert to 32 bit before returning + nFromInclusiveLocal32 = (int)nFromInclusiveLocal; + nToExclusiveLocal32 = (int)nToExclusiveLocal; + + return bRetVal; + } + } + + + /// <summary> + /// Represents the entire loop operation, keeping track of workers and ranges. + /// </summary> + /// + /// The usage pattern is: + /// 1) The Parallel loop entry function (ForWorker) creates an instance of this class + /// 2) Every thread joining to service the parallel loop calls RegisterWorker to grab a + /// RangeWorker struct to wrap the state it will need to find and execute work, + /// and they keep interacting with that struct until the end of the loop + internal class RangeManager + { + internal readonly IndexRange[] m_indexRanges; + + internal int m_nCurrentIndexRangeToAssign; + internal long m_nStep; + + /// <summary> + /// Initializes a RangeManager with the given loop parameters, and the desired number of outer ranges + /// </summary> + internal RangeManager(long nFromInclusive, long nToExclusive, long nStep, int nNumExpectedWorkers) + { + m_nCurrentIndexRangeToAssign = 0; + m_nStep = nStep; + + // Our signed math breaks down w/ nNumExpectedWorkers == 1. So change it to 2. + if (nNumExpectedWorkers == 1) + nNumExpectedWorkers = 2; + + // + // calculate the size of each index range + // + + ulong uSpan = (ulong)(nToExclusive - nFromInclusive); + ulong uRangeSize = uSpan / (ulong) nNumExpectedWorkers; // rough estimate first + + uRangeSize -= uRangeSize % (ulong) nStep; // snap to multiples of nStep + // otherwise index range transitions will derail us from nStep + + if (uRangeSize == 0) + { + uRangeSize = (ulong) nStep; + } + + // + // find the actual number of index ranges we will need + // + Contract.Assert((uSpan / uRangeSize) < Int32.MaxValue); + + int nNumRanges = (int)(uSpan / uRangeSize); + + if (uSpan % uRangeSize != 0) + { + nNumRanges++; + } + + + // Convert to signed so the rest of the logic works. + // Should be fine so long as uRangeSize < Int64.MaxValue, which we guaranteed by setting #workers >= 2. + long nRangeSize = (long)uRangeSize; + + // allocate the array of index ranges + m_indexRanges = new IndexRange[nNumRanges]; + + long nCurrentIndex = nFromInclusive; + for (int i = 0; i < nNumRanges; i++) + { + // the fromInclusive of the new index range is always on nCurrentIndex + m_indexRanges[i].m_nFromInclusive = nCurrentIndex; + m_indexRanges[i].m_nSharedCurrentIndexOffset = null; + m_indexRanges[i].m_bRangeFinished = 0; + + // now increment it to find the toExclusive value for our range + nCurrentIndex += nRangeSize; + + // detect integer overflow or range overage and snap to nToExclusive + if (nCurrentIndex < nCurrentIndex - nRangeSize || + nCurrentIndex > nToExclusive) + { + // this should only happen at the last index + Contract.Assert(i == nNumRanges - 1); + + nCurrentIndex = nToExclusive; + } + + // now that the end point of the new range is calculated, assign it. + m_indexRanges[i].m_nToExclusive = nCurrentIndex; + } + } + + /// <summary> + /// The function that needs to be called by each new worker thread servicing the parallel loop + /// in order to get a RangeWorker struct that wraps the state for finding and executing indices + /// </summary> + internal RangeWorker RegisterNewWorker() + { + Contract.Assert(m_indexRanges != null && m_indexRanges.Length != 0); + + int nInitialRange = (Interlocked.Increment(ref m_nCurrentIndexRangeToAssign) - 1) % m_indexRanges.Length; + + return new RangeWorker(m_indexRanges, nInitialRange, m_nStep); + } + } +} +#pragma warning restore 0420 diff --git a/src/mscorlib/src/System/Threading/Tasks/ProducerConsumerQueues.cs b/src/mscorlib/src/System/Threading/Tasks/ProducerConsumerQueues.cs new file mode 100644 index 0000000000..462ee0a9bd --- /dev/null +++ b/src/mscorlib/src/System/Threading/Tasks/ProducerConsumerQueues.cs @@ -0,0 +1,553 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +// =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ +// +// +// +// Specialized producer/consumer queues. +// +// +// ************<IMPORTANT NOTE>************* +// +// src\ndp\clr\src\bcl\system\threading\tasks\producerConsumerQueue.cs +// src\ndp\fx\src\dataflow\system\threading\tasks\dataflow\internal\producerConsumerQueue.cs +// Keep both of them consistent by changing the other file when you change this one, also avoid: +// 1- To reference interneal types in mscorlib +// 2- To reference any dataflow specific types +// This should be fixed post Dev11 when this class becomes public. +// +// ************</IMPORTANT NOTE>************* +// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + +using System.Collections; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Diagnostics; +using System.Diagnostics.Contracts; +using System.Runtime.InteropServices; + +namespace System.Threading.Tasks +{ + /// <summary>Represents a producer/consumer queue used internally by dataflow blocks.</summary> + /// <typeparam name="T">Specifies the type of data contained in the queue.</typeparam> + internal interface IProducerConsumerQueue<T> : IEnumerable<T> + { + /// <summary>Enqueues an item into the queue.</summary> + /// <param name="item">The item to enqueue.</param> + /// <remarks>This method is meant to be thread-safe subject to the particular nature of the implementation.</remarks> + void Enqueue(T item); + + /// <summary>Attempts to dequeue an item from the queue.</summary> + /// <param name="result">The dequeued item.</param> + /// <returns>true if an item could be dequeued; otherwise, false.</returns> + /// <remarks>This method is meant to be thread-safe subject to the particular nature of the implementation.</remarks> + bool TryDequeue(out T result); + + /// <summary>Gets whether the collection is currently empty.</summary> + /// <remarks>This method may or may not be thread-safe.</remarks> + bool IsEmpty { get; } + + /// <summary>Gets the number of items in the collection.</summary> + /// <remarks>In many implementations, this method will not be thread-safe.</remarks> + int Count { get; } + + /// <summary>A thread-safe way to get the number of items in the collection. May synchronize access by locking the provided synchronization object.</summary> + /// <param name="syncObj">The sync object used to lock</param> + /// <returns>The collection count</returns> + int GetCountSafe(object syncObj); + } + + /// <summary> + /// Provides a producer/consumer queue safe to be used by any number of producers and consumers concurrently. + /// </summary> + /// <typeparam name="T">Specifies the type of data contained in the queue.</typeparam> + [DebuggerDisplay("Count = {Count}")] + internal sealed class MultiProducerMultiConsumerQueue<T> : ConcurrentQueue<T>, IProducerConsumerQueue<T> + { + /// <summary>Enqueues an item into the queue.</summary> + /// <param name="item">The item to enqueue.</param> + void IProducerConsumerQueue<T>.Enqueue(T item) { base.Enqueue(item); } + + /// <summary>Attempts to dequeue an item from the queue.</summary> + /// <param name="result">The dequeued item.</param> + /// <returns>true if an item could be dequeued; otherwise, false.</returns> + bool IProducerConsumerQueue<T>.TryDequeue(out T result) { return base.TryDequeue(out result); } + + /// <summary>Gets whether the collection is currently empty.</summary> + bool IProducerConsumerQueue<T>.IsEmpty { get { return base.IsEmpty; } } + + /// <summary>Gets the number of items in the collection.</summary> + int IProducerConsumerQueue<T>.Count { get { return base.Count; } } + + /// <summary>A thread-safe way to get the number of items in the collection. May synchronize access by locking the provided synchronization object.</summary> + /// <remarks>ConcurrentQueue.Count is thread safe, no need to acquire the lock.</remarks> + int IProducerConsumerQueue<T>.GetCountSafe(object syncObj) { return base.Count; } + } + + /// <summary> + /// Provides a producer/consumer queue safe to be used by only one producer and one consumer concurrently. + /// </summary> + /// <typeparam name="T">Specifies the type of data contained in the queue.</typeparam> + [DebuggerDisplay("Count = {Count}")] + [DebuggerTypeProxy(typeof(SingleProducerSingleConsumerQueue<>.SingleProducerSingleConsumerQueue_DebugView))] + internal sealed class SingleProducerSingleConsumerQueue<T> : IProducerConsumerQueue<T> + { + // Design: + // + // SingleProducerSingleConsumerQueue (SPSCQueue) is a concurrent queue designed to be used + // by one producer thread and one consumer thread. SPSCQueue does not work correctly when used by + // multiple producer threads concurrently or multiple consumer threads concurrently. + // + // SPSCQueue is based on segments that behave like circular buffers. Each circular buffer is represented + // as an array with two indexes: m_first and m_last. m_first is the index of the array slot for the consumer + // to read next, and m_last is the slot for the producer to write next. The circular buffer is empty when + // (m_first == m_last), and full when ((m_last+1) % m_array.Length == m_first). + // + // Since m_first is only ever modified by the consumer thread and m_last by the producer, the two indices can + // be updated without interlocked operations. As long as the queue size fits inside a single circular buffer, + // enqueues and dequeues simply advance the corresponding indices around the circular buffer. If an enqueue finds + // that there is no room in the existing buffer, however, a new circular buffer is allocated that is twice as big + // as the old buffer. From then on, the producer will insert values into the new buffer. The consumer will first + // empty out the old buffer and only then follow the producer into the new (larger) buffer. + // + // As described above, the enqueue operation on the fast path only modifies the m_first field of the current segment. + // However, it also needs to read m_last in order to verify that there is room in the current segment. Similarly, the + // dequeue operation on the fast path only needs to modify m_last, but also needs to read m_first to verify that the + // queue is non-empty. This results in true cache line sharing between the producer and the consumer. + // + // The cache line sharing issue can be mitigating by having a possibly stale copy of m_first that is owned by the producer, + // and a possibly stale copy of m_last that is owned by the consumer. So, the consumer state is described using + // (m_first, m_lastCopy) and the producer state using (m_firstCopy, m_last). The consumer state is separated from + // the producer state by padding, which allows fast-path enqueues and dequeues from hitting shared cache lines. + // m_lastCopy is the consumer's copy of m_last. Whenever the consumer can tell that there is room in the buffer + // simply by observing m_lastCopy, the consumer thread does not need to read m_last and thus encounter a cache miss. Only + // when the buffer appears to be empty will the consumer refresh m_lastCopy from m_last. m_firstCopy is used by the producer + // in the same way to avoid reading m_first on the hot path. + + /// <summary>The initial size to use for segments (in number of elements).</summary> + private const int INIT_SEGMENT_SIZE = 32; // must be a power of 2 + /// <summary>The maximum size to use for segments (in number of elements).</summary> + private const int MAX_SEGMENT_SIZE = 0x1000000; // this could be made as large as Int32.MaxValue / 2 + + /// <summary>The head of the linked list of segments.</summary> + private volatile Segment m_head; + /// <summary>The tail of the linked list of segments.</summary> + private volatile Segment m_tail; + + /// <summary>Initializes the queue.</summary> + internal SingleProducerSingleConsumerQueue() + { + // Validate constants in ctor rather than in an explicit cctor that would cause perf degradation + Contract.Assert(INIT_SEGMENT_SIZE > 0, "Initial segment size must be > 0."); + Contract.Assert((INIT_SEGMENT_SIZE & (INIT_SEGMENT_SIZE - 1)) == 0, "Initial segment size must be a power of 2"); + Contract.Assert(INIT_SEGMENT_SIZE <= MAX_SEGMENT_SIZE, "Initial segment size should be <= maximum."); + Contract.Assert(MAX_SEGMENT_SIZE < Int32.MaxValue / 2, "Max segment size * 2 must be < Int32.MaxValue, or else overflow could occur."); + + // Initialize the queue + m_head = m_tail = new Segment(INIT_SEGMENT_SIZE); + } + + /// <summary>Enqueues an item into the queue.</summary> + /// <param name="item">The item to enqueue.</param> + public void Enqueue(T item) + { + Segment segment = m_tail; + var array = segment.m_array; + int last = segment.m_state.m_last; // local copy to avoid multiple volatile reads + + // Fast path: there's obviously room in the current segment + int tail2 = (last + 1) & (array.Length - 1); + if (tail2 != segment.m_state.m_firstCopy) + { + array[last] = item; + segment.m_state.m_last = tail2; + } + // Slow path: there may not be room in the current segment. + else EnqueueSlow(item, ref segment); + } + + /// <summary>Enqueues an item into the queue.</summary> + /// <param name="item">The item to enqueue.</param> + /// <param name="segment">The segment in which to first attempt to store the item.</param> + private void EnqueueSlow(T item, ref Segment segment) + { + Contract.Requires(segment != null, "Expected a non-null segment."); + + if (segment.m_state.m_firstCopy != segment.m_state.m_first) + { + segment.m_state.m_firstCopy = segment.m_state.m_first; + Enqueue(item); // will only recur once for this enqueue operation + return; + } + + int newSegmentSize = m_tail.m_array.Length << 1; // double size + Contract.Assert(newSegmentSize > 0, "The max size should always be small enough that we don't overflow."); + if (newSegmentSize > MAX_SEGMENT_SIZE) newSegmentSize = MAX_SEGMENT_SIZE; + + var newSegment = new Segment(newSegmentSize); + newSegment.m_array[0] = item; + newSegment.m_state.m_last = 1; + newSegment.m_state.m_lastCopy = 1; + + try { } finally + { + // Finally block to protect against corruption due to a thread abort + // between setting m_next and setting m_tail. + Volatile.Write(ref m_tail.m_next, newSegment); // ensure segment not published until item is fully stored + m_tail = newSegment; + } + } + + /// <summary>Attempts to dequeue an item from the queue.</summary> + /// <param name="result">The dequeued item.</param> + /// <returns>true if an item could be dequeued; otherwise, false.</returns> + public bool TryDequeue(out T result) + { + Segment segment = m_head; + var array = segment.m_array; + int first = segment.m_state.m_first; // local copy to avoid multiple volatile reads + + // Fast path: there's obviously data available in the current segment + if (first != segment.m_state.m_lastCopy) + { + result = array[first]; + array[first] = default(T); // Clear the slot to release the element + segment.m_state.m_first = (first + 1) & (array.Length - 1); + return true; + } + // Slow path: there may not be data available in the current segment + else return TryDequeueSlow(ref segment, ref array, out result); + } + + /// <summary>Attempts to dequeue an item from the queue.</summary> + /// <param name="array">The array from which the item was dequeued.</param> + /// <param name="segment">The segment from which the item was dequeued.</param> + /// <param name="result">The dequeued item.</param> + /// <returns>true if an item could be dequeued; otherwise, false.</returns> + private bool TryDequeueSlow(ref Segment segment, ref T[] array, out T result) + { + Contract.Requires(segment != null, "Expected a non-null segment."); + Contract.Requires(array != null, "Expected a non-null item array."); + + if (segment.m_state.m_last != segment.m_state.m_lastCopy) + { + segment.m_state.m_lastCopy = segment.m_state.m_last; + return TryDequeue(out result); // will only recur once for this dequeue operation + } + + if (segment.m_next != null && segment.m_state.m_first == segment.m_state.m_last) + { + segment = segment.m_next; + array = segment.m_array; + m_head = segment; + } + + var first = segment.m_state.m_first; // local copy to avoid extraneous volatile reads + + if (first == segment.m_state.m_last) + { + result = default(T); + return false; + } + + result = array[first]; + array[first] = default(T); // Clear the slot to release the element + segment.m_state.m_first = (first + 1) & (segment.m_array.Length - 1); + segment.m_state.m_lastCopy = segment.m_state.m_last; // Refresh m_lastCopy to ensure that m_first has not passed m_lastCopy + + return true; + } + + /// <summary>Attempts to peek at an item in the queue.</summary> + /// <param name="result">The peeked item.</param> + /// <returns>true if an item could be peeked; otherwise, false.</returns> + public bool TryPeek(out T result) + { + Segment segment = m_head; + var array = segment.m_array; + int first = segment.m_state.m_first; // local copy to avoid multiple volatile reads + + // Fast path: there's obviously data available in the current segment + if (first != segment.m_state.m_lastCopy) + { + result = array[first]; + return true; + } + // Slow path: there may not be data available in the current segment + else return TryPeekSlow(ref segment, ref array, out result); + } + + /// <summary>Attempts to peek at an item in the queue.</summary> + /// <param name="array">The array from which the item is peeked.</param> + /// <param name="segment">The segment from which the item is peeked.</param> + /// <param name="result">The peeked item.</param> + /// <returns>true if an item could be peeked; otherwise, false.</returns> + private bool TryPeekSlow(ref Segment segment, ref T[] array, out T result) + { + Contract.Requires(segment != null, "Expected a non-null segment."); + Contract.Requires(array != null, "Expected a non-null item array."); + + if (segment.m_state.m_last != segment.m_state.m_lastCopy) + { + segment.m_state.m_lastCopy = segment.m_state.m_last; + return TryPeek(out result); // will only recur once for this peek operation + } + + if (segment.m_next != null && segment.m_state.m_first == segment.m_state.m_last) + { + segment = segment.m_next; + array = segment.m_array; + m_head = segment; + } + + var first = segment.m_state.m_first; // local copy to avoid extraneous volatile reads + + if (first == segment.m_state.m_last) + { + result = default(T); + return false; + } + + result = array[first]; + return true; + } + + /// <summary>Attempts to dequeue an item from the queue.</summary> + /// <param name="predicate">The predicate that must return true for the item to be dequeued. If null, all items implicitly return true.</param> + /// <param name="result">The dequeued item.</param> + /// <returns>true if an item could be dequeued; otherwise, false.</returns> + public bool TryDequeueIf(Predicate<T> predicate, out T result) + { + Segment segment = m_head; + var array = segment.m_array; + int first = segment.m_state.m_first; // local copy to avoid multiple volatile reads + + // Fast path: there's obviously data available in the current segment + if (first != segment.m_state.m_lastCopy) + { + result = array[first]; + if (predicate == null || predicate(result)) + { + array[first] = default(T); // Clear the slot to release the element + segment.m_state.m_first = (first + 1) & (array.Length - 1); + return true; + } + else + { + result = default(T); + return false; + } + } + // Slow path: there may not be data available in the current segment + else return TryDequeueIfSlow(predicate, ref segment, ref array, out result); + } + + /// <summary>Attempts to dequeue an item from the queue.</summary> + /// <param name="predicate">The predicate that must return true for the item to be dequeued. If null, all items implicitly return true.</param> + /// <param name="array">The array from which the item was dequeued.</param> + /// <param name="segment">The segment from which the item was dequeued.</param> + /// <param name="result">The dequeued item.</param> + /// <returns>true if an item could be dequeued; otherwise, false.</returns> + private bool TryDequeueIfSlow(Predicate<T> predicate, ref Segment segment, ref T[] array, out T result) + { + Contract.Requires(segment != null, "Expected a non-null segment."); + Contract.Requires(array != null, "Expected a non-null item array."); + + if (segment.m_state.m_last != segment.m_state.m_lastCopy) + { + segment.m_state.m_lastCopy = segment.m_state.m_last; + return TryDequeueIf(predicate, out result); // will only recur once for this dequeue operation + } + + if (segment.m_next != null && segment.m_state.m_first == segment.m_state.m_last) + { + segment = segment.m_next; + array = segment.m_array; + m_head = segment; + } + + var first = segment.m_state.m_first; // local copy to avoid extraneous volatile reads + + if (first == segment.m_state.m_last) + { + result = default(T); + return false; + } + + result = array[first]; + if (predicate == null || predicate(result)) + { + array[first] = default(T); // Clear the slot to release the element + segment.m_state.m_first = (first + 1) & (segment.m_array.Length - 1); + segment.m_state.m_lastCopy = segment.m_state.m_last; // Refresh m_lastCopy to ensure that m_first has not passed m_lastCopy + return true; + } + else + { + result = default(T); + return false; + } + } + + public void Clear() + { + T ignored; + while (TryDequeue(out ignored)) ; + } + + /// <summary>Gets whether the collection is currently empty.</summary> + /// <remarks>WARNING: This should not be used concurrently without further vetting.</remarks> + public bool IsEmpty + { + // This implementation is optimized for calls from the consumer. + get + { + var head = m_head; + if (head.m_state.m_first != head.m_state.m_lastCopy) return false; // m_first is volatile, so the read of m_lastCopy cannot get reordered + if (head.m_state.m_first != head.m_state.m_last) return false; + return head.m_next == null; + } + } + + /// <summary>Gets an enumerable for the collection.</summary> + /// <remarks>WARNING: This should only be used for debugging purposes. It is not safe to be used concurrently.</remarks> + public IEnumerator<T> GetEnumerator() + { + for (Segment segment = m_head; segment != null; segment = segment.m_next) + { + for (int pt = segment.m_state.m_first; + pt != segment.m_state.m_last; + pt = (pt + 1) & (segment.m_array.Length - 1)) + { + yield return segment.m_array[pt]; + } + } + } + /// <summary>Gets an enumerable for the collection.</summary> + /// <remarks>WARNING: This should only be used for debugging purposes. It is not safe to be used concurrently.</remarks> + IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } + + /// <summary>Gets the number of items in the collection.</summary> + /// <remarks>WARNING: This should only be used for debugging purposes. It is not meant to be used concurrently.</remarks> + public int Count + { + get + { + int count = 0; + for (Segment segment = m_head; segment != null; segment = segment.m_next) + { + int arraySize = segment.m_array.Length; + int first, last; + while (true) // Count is not meant to be used concurrently, but this helps to avoid issues if it is + { + first = segment.m_state.m_first; + last = segment.m_state.m_last; + if (first == segment.m_state.m_first) break; + } + count += (last - first) & (arraySize - 1); + } + return count; + } + } + + /// <summary>A thread-safe way to get the number of items in the collection. May synchronize access by locking the provided synchronization object.</summary> + /// <remarks>The Count is not thread safe, so we need to acquire the lock.</remarks> + int IProducerConsumerQueue<T>.GetCountSafe(object syncObj) + { + Contract.Assert(syncObj != null, "The syncObj parameter is null."); + lock (syncObj) + { + return Count; + } + } + + /// <summary>A segment in the queue containing one or more items.</summary> + [StructLayout(LayoutKind.Sequential)] + private sealed class Segment + { + /// <summary>The next segment in the linked list of segments.</summary> + internal Segment m_next; + /// <summary>The data stored in this segment.</summary> + internal readonly T[] m_array; + /// <summary>Details about the segment.</summary> + internal SegmentState m_state; // separated out to enable StructLayout attribute to take effect + + /// <summary>Initializes the segment.</summary> + /// <param name="size">The size to use for this segment.</param> + internal Segment(int size) + { + Contract.Requires((size & (size - 1)) == 0, "Size must be a power of 2"); + m_array = new T[size]; + } + } + + /// <summary>Stores information about a segment.</summary> + [StructLayout(LayoutKind.Sequential)] // enforce layout so that padding reduces false sharing + private struct SegmentState + { + /// <summary>Padding to reduce false sharing between the segment's array and m_first.</summary> + internal PaddingFor32 m_pad0; + + /// <summary>The index of the current head in the segment.</summary> + internal volatile int m_first; + /// <summary>A copy of the current tail index.</summary> + internal int m_lastCopy; // not volatile as read and written by the producer, except for IsEmpty, and there m_lastCopy is only read after reading the volatile m_first + + /// <summary>Padding to reduce false sharing between the first and last.</summary> + internal PaddingFor32 m_pad1; + + /// <summary>A copy of the current head index.</summary> + internal int m_firstCopy; // not voliatle as only read and written by the consumer thread + /// <summary>The index of the current tail in the segment.</summary> + internal volatile int m_last; + + /// <summary>Padding to reduce false sharing with the last and what's after the segment.</summary> + internal PaddingFor32 m_pad2; + } + + /// <summary>Debugger type proxy for a SingleProducerSingleConsumerQueue of T.</summary> + private sealed class SingleProducerSingleConsumerQueue_DebugView + { + /// <summary>The queue being visualized.</summary> + private readonly SingleProducerSingleConsumerQueue<T> m_queue; + + /// <summary>Initializes the debug view.</summary> + /// <param name="enumerable">The queue being debugged.</param> + public SingleProducerSingleConsumerQueue_DebugView(SingleProducerSingleConsumerQueue<T> queue) + { + Contract.Requires(queue != null, "Expected a non-null queue."); + m_queue = queue; + } + + /// <summary>Gets the contents of the list.</summary> + [DebuggerBrowsable(DebuggerBrowsableState.RootHidden)] + public T[] Items + { + get + { + List<T> list = new List<T>(); + foreach (T item in m_queue) + list.Add(item); + return list.ToArray(); + } + } + } + } + + + /// <summary>A placeholder class for common padding constants and eventually routines.</summary> + static class PaddingHelpers + { + /// <summary>A size greater than or equal to the size of the most common CPU cache lines.</summary> + internal const int CACHE_LINE_SIZE = 128; + } + + /// <summary>Padding structure used to minimize false sharing in SingleProducerSingleConsumerQueue{T}.</summary> + [StructLayout(LayoutKind.Explicit, Size = PaddingHelpers.CACHE_LINE_SIZE - sizeof(Int32))] // Based on common case of 64-byte cache lines + struct PaddingFor32 + { + } + +} diff --git a/src/mscorlib/src/System/Threading/Tasks/TPLETWProvider.cs b/src/mscorlib/src/System/Threading/Tasks/TPLETWProvider.cs new file mode 100644 index 0000000000..5f79f30b35 --- /dev/null +++ b/src/mscorlib/src/System/Threading/Tasks/TPLETWProvider.cs @@ -0,0 +1,763 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +// =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ +// +// +// +// EventSource for TPL. +// +// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + +using System; +using System.Collections.Generic; +using System.Text; +using System.Security; +using System.Security.Permissions; +using System.Runtime.CompilerServices; + +namespace System.Threading.Tasks +{ + using System.Diagnostics.Tracing; + + /// <summary>Provides an event source for tracing TPL information.</summary> + [EventSource( + Name = "System.Threading.Tasks.TplEventSource", + Guid = "2e5dba47-a3d2-4d16-8ee0-6671ffdcd7b5", + LocalizationResources = System.CoreLib.Name)] + internal sealed class TplEtwProvider : EventSource + { + /// Used to determine if tasks should generate Activity IDs for themselves + internal bool TasksSetActivityIds; // This keyword is set + internal bool Debug; + private bool DebugActivityId; + + /// <summary> + /// Get callbacks when the ETW sends us commands` + /// </summary> + protected override void OnEventCommand(EventCommandEventArgs command) + { + // To get the AsyncCausality events, we need to inform the AsyncCausalityTracer + if (command.Command == EventCommand.Enable) + AsyncCausalityTracer.EnableToETW(true); + else if (command.Command == EventCommand.Disable) + AsyncCausalityTracer.EnableToETW(false); + + if (IsEnabled(EventLevel.Informational, Keywords.TasksFlowActivityIds)) + ActivityTracker.Instance.Enable(); + else + TasksSetActivityIds = IsEnabled(EventLevel.Informational, Keywords.TasksSetActivityIds); + + Debug = IsEnabled(EventLevel.Informational, Keywords.Debug); + DebugActivityId = IsEnabled(EventLevel.Informational, Keywords.DebugActivityId); + } + + /// <summary> + /// Defines the singleton instance for the TPL ETW provider. + /// The TPL Event provider GUID is {2e5dba47-a3d2-4d16-8ee0-6671ffdcd7b5}. + /// </summary> + public static TplEtwProvider Log = new TplEtwProvider(); + /// <summary>Prevent external instantiation. All logging should go through the Log instance.</summary> + private TplEtwProvider() { } + + /// <summary>Type of a fork/join operation.</summary> + public enum ForkJoinOperationType + { + /// <summary>Parallel.Invoke.</summary> + ParallelInvoke=1, + /// <summary>Parallel.For.</summary> + ParallelFor=2, + /// <summary>Parallel.ForEach.</summary> + ParallelForEach=3 + } + + /// <summary>Configured behavior of a task wait operation.</summary> + public enum TaskWaitBehavior : int + { + /// <summary>A synchronous wait.</summary> + Synchronous = 1, + /// <summary>An asynchronous await.</summary> + Asynchronous = 2 + } + + /// <summary>ETW tasks that have start/stop events.</summary> + public class Tasks // this name is important for EventSource + { + /// <summary>A parallel loop.</summary> + public const EventTask Loop = (EventTask)1; + /// <summary>A parallel invoke.</summary> + public const EventTask Invoke = (EventTask)2; + /// <summary>Executing a Task.</summary> + public const EventTask TaskExecute = (EventTask)3; + /// <summary>Waiting on a Task.</summary> + public const EventTask TaskWait = (EventTask)4; + /// <summary>A fork/join task within a loop or invoke.</summary> + public const EventTask ForkJoin = (EventTask)5; + /// <summary>A task is scheduled to execute.</summary> + public const EventTask TaskScheduled = (EventTask)6; + /// <summary>An await task continuation is scheduled to execute.</summary> + public const EventTask AwaitTaskContinuationScheduled = (EventTask)7; + + /// <summary>AsyncCausalityFunctionality.</summary> + public const EventTask TraceOperation = (EventTask)8; + public const EventTask TraceSynchronousWork = (EventTask)9; + } + + public class Keywords // thisname is important for EventSource + { + /// <summary> + /// Only the most basic information about the workings of the task library + /// This sets activity IDS and logs when tasks are schedules (or waits begin) + /// But are otherwise silent + /// </summary> + public const EventKeywords TaskTransfer = (EventKeywords) 1; + /// <summary> + /// TaskTranser events plus events when tasks start and stop + /// </summary> + public const EventKeywords Tasks = (EventKeywords) 2; + /// <summary> + /// Events associted with the higher level parallel APIs + /// </summary> + public const EventKeywords Parallel = (EventKeywords) 4; + /// <summary> + /// These are relatively verbose events that effectively just redirect + /// the windows AsyncCausalityTracer to ETW + /// </summary> + public const EventKeywords AsyncCausalityOperation = (EventKeywords) 8; + public const EventKeywords AsyncCausalityRelation = (EventKeywords) 0x10; + public const EventKeywords AsyncCausalitySynchronousWork = (EventKeywords) 0x20; + + /// <summary> + /// Emit the stops as well as the schedule/start events + /// </summary> + public const EventKeywords TaskStops = (EventKeywords) 0x40; + + /// <summary> + /// TasksFlowActivityIds indicate that activity ID flow from one task + /// to any task created by it. + /// </summary> + public const EventKeywords TasksFlowActivityIds = (EventKeywords) 0x80; + + /// <summary> + /// TasksSetActivityIds will cause the task operations to set Activity Ids + /// This option is incompatible with TasksFlowActivityIds flow is ignored + /// if that keyword is set. This option is likley to be removed in the future + /// </summary> + public const EventKeywords TasksSetActivityIds = (EventKeywords) 0x10000; + + /// <summary> + /// Relatively Verbose logging meant for debugging the Task library itself. Will probably be removed in the future + /// </summary> + public const EventKeywords Debug = (EventKeywords) 0x20000; + /// <summary> + /// Relatively Verbose logging meant for debugging the Task library itself. Will probably be removed in the future + /// </summary> + public const EventKeywords DebugActivityId = (EventKeywords) 0x40000; + } + + /// <summary>Enabled for all keywords.</summary> + private const EventKeywords ALL_KEYWORDS = (EventKeywords)(-1); + + //----------------------------------------------------------------------------------- + // + // TPL Event IDs (must be unique) + // + + /// <summary>The beginning of a parallel loop.</summary> + private const int PARALLELLOOPBEGIN_ID = 1; + /// <summary>The ending of a parallel loop.</summary> + private const int PARALLELLOOPEND_ID = 2; + /// <summary>The beginning of a parallel invoke.</summary> + private const int PARALLELINVOKEBEGIN_ID = 3; + /// <summary>The ending of a parallel invoke.</summary> + private const int PARALLELINVOKEEND_ID = 4; + /// <summary>A task entering a fork/join construct.</summary> + private const int PARALLELFORK_ID = 5; + /// <summary>A task leaving a fork/join construct.</summary> + private const int PARALLELJOIN_ID = 6; + + /// <summary>A task is scheduled to a task scheduler.</summary> + private const int TASKSCHEDULED_ID = 7; + /// <summary>A task is about to execute.</summary> + private const int TASKSTARTED_ID = 8; + /// <summary>A task has finished executing.</summary> + private const int TASKCOMPLETED_ID = 9; + /// <summary>A wait on a task is beginning.</summary> + private const int TASKWAITBEGIN_ID = 10; + /// <summary>A wait on a task is ending.</summary> + private const int TASKWAITEND_ID = 11; + /// <summary>A continuation of a task is scheduled.</summary> + private const int AWAITTASKCONTINUATIONSCHEDULED_ID = 12; + /// <summary>A continuation of a taskWaitEnd is complete </summary> + private const int TASKWAITCONTINUATIONCOMPLETE_ID = 13; + /// <summary>A continuation of a taskWaitEnd is complete </summary> + private const int TASKWAITCONTINUATIONSTARTED_ID = 19; + + private const int TRACEOPERATIONSTART_ID = 14; + private const int TRACEOPERATIONSTOP_ID = 15; + private const int TRACEOPERATIONRELATION_ID = 16; + private const int TRACESYNCHRONOUSWORKSTART_ID = 17; + private const int TRACESYNCHRONOUSWORKSTOP_ID = 18; + + + //----------------------------------------------------------------------------------- + // + // Parallel Events + // + + #region ParallelLoopBegin + /// <summary> + /// Denotes the entry point for a Parallel.For or Parallel.ForEach loop + /// </summary> + /// <param name="OriginatingTaskSchedulerID">The scheduler ID.</param> + /// <param name="OriginatingTaskID">The task ID.</param> + /// <param name="ForkJoinContextID">The loop ID.</param> + /// <param name="OperationType">The kind of fork/join operation.</param> + /// <param name="InclusiveFrom">The lower bound of the loop.</param> + /// <param name="ExclusiveTo">The upper bound of the loop.</param> + [SecuritySafeCritical] + [Event(PARALLELLOOPBEGIN_ID, Level = EventLevel.Informational, ActivityOptions=EventActivityOptions.Recursive, + Task = TplEtwProvider.Tasks.Loop, Opcode = EventOpcode.Start)] + public void ParallelLoopBegin( + int OriginatingTaskSchedulerID, int OriginatingTaskID, // PFX_COMMON_EVENT_HEADER + int ForkJoinContextID, ForkJoinOperationType OperationType, // PFX_FORKJOIN_COMMON_EVENT_HEADER + long InclusiveFrom, long ExclusiveTo) + { + if (IsEnabled() && IsEnabled(EventLevel.Informational, Keywords.Parallel)) + { + // There is no explicit WriteEvent() overload matching this event's fields. Therefore calling + // WriteEvent() would hit the "params" overload, which leads to an object allocation every time + // this event is fired. To prevent that problem we will call WriteEventCore(), which works with + // a stack based EventData array populated with the event fields. + unsafe + { + EventData* eventPayload = stackalloc EventData[6]; + + eventPayload[0].Size = sizeof(int); + eventPayload[0].DataPointer = ((IntPtr) (&OriginatingTaskSchedulerID)); + eventPayload[1].Size = sizeof(int); + eventPayload[1].DataPointer = ((IntPtr) (&OriginatingTaskID)); + eventPayload[2].Size = sizeof(int); + eventPayload[2].DataPointer = ((IntPtr) (&ForkJoinContextID)); + eventPayload[3].Size = sizeof(int); + eventPayload[3].DataPointer = ((IntPtr) (&OperationType)); + eventPayload[4].Size = sizeof(long); + eventPayload[4].DataPointer = ((IntPtr) (&InclusiveFrom)); + eventPayload[5].Size = sizeof(long); + eventPayload[5].DataPointer = ((IntPtr) (&ExclusiveTo)); + + WriteEventCore(PARALLELLOOPBEGIN_ID, 6, eventPayload); + } + } + } + #endregion + + #region ParallelLoopEnd + /// <summary> + /// Denotes the end of a Parallel.For or Parallel.ForEach loop. + /// </summary> + /// <param name="OriginatingTaskSchedulerID">The scheduler ID.</param> + /// <param name="OriginatingTaskID">The task ID.</param> + /// <param name="ForkJoinContextID">The loop ID.</param> + /// <param name="TotalIterations">the total number of iterations processed.</param> + [SecuritySafeCritical] + [Event(PARALLELLOOPEND_ID, Level = EventLevel.Informational, Task = TplEtwProvider.Tasks.Loop, Opcode = EventOpcode.Stop)] + public void ParallelLoopEnd( + int OriginatingTaskSchedulerID, int OriginatingTaskID, // PFX_COMMON_EVENT_HEADER + int ForkJoinContextID, long TotalIterations) + { + if (IsEnabled() && IsEnabled(EventLevel.Informational, Keywords.Parallel)) + { + // There is no explicit WriteEvent() overload matching this event's fields. + // Therefore calling WriteEvent() would hit the "params" overload, which leads to an object allocation every time this event is fired. + // To prevent that problem we will call WriteEventCore(), which works with a stack based EventData array populated with the event fields + unsafe + { + EventData* eventPayload = stackalloc EventData[4]; + + eventPayload[0].Size = sizeof(int); + eventPayload[0].DataPointer = ((IntPtr) (&OriginatingTaskSchedulerID)); + eventPayload[1].Size = sizeof(int); + eventPayload[1].DataPointer = ((IntPtr) (&OriginatingTaskID)); + eventPayload[2].Size = sizeof(int); + eventPayload[2].DataPointer = ((IntPtr) (&ForkJoinContextID)); + eventPayload[3].Size = sizeof(long); + eventPayload[3].DataPointer = ((IntPtr) (&TotalIterations)); + + WriteEventCore(PARALLELLOOPEND_ID, 4, eventPayload); + } + } + } + #endregion + + #region ParallelInvokeBegin + /// <summary>Denotes the entry point for a Parallel.Invoke call.</summary> + /// <param name="OriginatingTaskSchedulerID">The scheduler ID.</param> + /// <param name="OriginatingTaskID">The task ID.</param> + /// <param name="ForkJoinContextID">The invoke ID.</param> + /// <param name="OperationType">The kind of fork/join operation.</param> + /// <param name="ActionCount">The number of actions being invoked.</param> + [SecuritySafeCritical] + [Event(PARALLELINVOKEBEGIN_ID, Level = EventLevel.Informational, ActivityOptions=EventActivityOptions.Recursive, + Task = TplEtwProvider.Tasks.Invoke, Opcode = EventOpcode.Start)] + public void ParallelInvokeBegin( + int OriginatingTaskSchedulerID, int OriginatingTaskID, // PFX_COMMON_EVENT_HEADER + int ForkJoinContextID, ForkJoinOperationType OperationType, // PFX_FORKJOIN_COMMON_EVENT_HEADER + int ActionCount) + { + if (IsEnabled() && IsEnabled(EventLevel.Informational, Keywords.Parallel)) + { + // There is no explicit WriteEvent() overload matching this event's fields. + // Therefore calling WriteEvent() would hit the "params" overload, which leads to an object allocation every time this event is fired. + // To prevent that problem we will call WriteEventCore(), which works with a stack based EventData array populated with the event fields + unsafe + { + EventData* eventPayload = stackalloc EventData[5]; + + eventPayload[0].Size = sizeof(int); + eventPayload[0].DataPointer = ((IntPtr) (&OriginatingTaskSchedulerID)); + eventPayload[1].Size = sizeof(int); + eventPayload[1].DataPointer = ((IntPtr) (&OriginatingTaskID)); + eventPayload[2].Size = sizeof(int); + eventPayload[2].DataPointer = ((IntPtr) (&ForkJoinContextID)); + eventPayload[3].Size = sizeof(int); + eventPayload[3].DataPointer = ((IntPtr) (&OperationType)); + eventPayload[4].Size = sizeof(int); + eventPayload[4].DataPointer = ((IntPtr) (&ActionCount)); + + WriteEventCore(PARALLELINVOKEBEGIN_ID, 5, eventPayload); + } + } + } + #endregion + + #region ParallelInvokeEnd + /// <summary> + /// Denotes the exit point for a Parallel.Invoke call. + /// </summary> + /// <param name="OriginatingTaskSchedulerID">The scheduler ID.</param> + /// <param name="OriginatingTaskID">The task ID.</param> + /// <param name="ForkJoinContextID">The invoke ID.</param> + [Event(PARALLELINVOKEEND_ID, Level = EventLevel.Informational, Task = TplEtwProvider.Tasks.Invoke, Opcode = EventOpcode.Stop)] + public void ParallelInvokeEnd( + int OriginatingTaskSchedulerID, int OriginatingTaskID, // PFX_COMMON_EVENT_HEADER + int ForkJoinContextID) + { + if (IsEnabled() && IsEnabled(EventLevel.Informational, Keywords.Parallel)) + { + WriteEvent(PARALLELINVOKEEND_ID, OriginatingTaskSchedulerID, OriginatingTaskID, ForkJoinContextID); + } + } + #endregion + + #region ParallelFork + /// <summary> + /// Denotes the start of an individual task that's part of a fork/join context. + /// Before this event is fired, the start of the new fork/join context will be marked + /// with another event that declares a unique context ID. + /// </summary> + /// <param name="OriginatingTaskSchedulerID">The scheduler ID.</param> + /// <param name="OriginatingTaskID">The task ID.</param> + /// <param name="ForkJoinContextID">The invoke ID.</param> + [Event(PARALLELFORK_ID, Level = EventLevel.Verbose, ActivityOptions=EventActivityOptions.Recursive, + Task = TplEtwProvider.Tasks.ForkJoin, Opcode = EventOpcode.Start)] + public void ParallelFork( + int OriginatingTaskSchedulerID, int OriginatingTaskID, // PFX_COMMON_EVENT_HEADER + int ForkJoinContextID) + { + if (IsEnabled() && IsEnabled(EventLevel.Verbose, Keywords.Parallel)) + { + WriteEvent(PARALLELFORK_ID, OriginatingTaskSchedulerID, OriginatingTaskID, ForkJoinContextID); + } + } + #endregion + + #region ParallelJoin + /// <summary> + /// Denotes the end of an individual task that's part of a fork/join context. + /// This should match a previous ParallelFork event with a matching "OriginatingTaskID" + /// </summary> + /// <param name="OriginatingTaskSchedulerID">The scheduler ID.</param> + /// <param name="OriginatingTaskID">The task ID.</param> + /// <param name="ForkJoinContextID">The invoke ID.</param> + [Event(PARALLELJOIN_ID, Level = EventLevel.Verbose, Task = TplEtwProvider.Tasks.ForkJoin, Opcode = EventOpcode.Stop)] + public void ParallelJoin( + int OriginatingTaskSchedulerID, int OriginatingTaskID, // PFX_COMMON_EVENT_HEADER + int ForkJoinContextID) + { + if (IsEnabled() && IsEnabled(EventLevel.Verbose, Keywords.Parallel)) + { + WriteEvent(PARALLELJOIN_ID, OriginatingTaskSchedulerID, OriginatingTaskID, ForkJoinContextID); + } + } + #endregion + + //----------------------------------------------------------------------------------- + // + // Task Events + // + + // These are all verbose events, so we need to call IsEnabled(EventLevel.Verbose, ALL_KEYWORDS) + // call. However since the IsEnabled(l,k) call is more expensive than IsEnabled(), we only want + // to incur this cost when instrumentation is enabled. So the Task codepaths that call these + // event functions still do the check for IsEnabled() + + #region TaskScheduled + /// <summary> + /// Fired when a task is queued to a TaskScheduler. + /// </summary> + /// <param name="OriginatingTaskSchedulerID">The scheduler ID.</param> + /// <param name="OriginatingTaskID">The task ID.</param> + /// <param name="TaskID">The task ID.</param> + /// <param name="CreatingTaskID">The task ID</param> + /// <param name="TaskCreationOptions">The options used to create the task.</param> + [SecuritySafeCritical] + [Event(TASKSCHEDULED_ID, Task = Tasks.TaskScheduled, Version=1, Opcode = EventOpcode.Send, + Level = EventLevel.Informational, Keywords = Keywords.TaskTransfer|Keywords.Tasks)] + public void TaskScheduled( + int OriginatingTaskSchedulerID, int OriginatingTaskID, // PFX_COMMON_EVENT_HEADER + int TaskID, int CreatingTaskID, int TaskCreationOptions, int appDomain) + { + // IsEnabled() call is an inlined quick check that makes this very fast when provider is off + if (IsEnabled() && IsEnabled(EventLevel.Informational, Keywords.TaskTransfer|Keywords.Tasks)) + { + unsafe + { + EventData* eventPayload = stackalloc EventData[6]; + eventPayload[0].Size = sizeof(int); + eventPayload[0].DataPointer = ((IntPtr) (&OriginatingTaskSchedulerID)); + eventPayload[1].Size = sizeof(int); + eventPayload[1].DataPointer = ((IntPtr) (&OriginatingTaskID)); + eventPayload[2].Size = sizeof(int); + eventPayload[2].DataPointer = ((IntPtr) (&TaskID)); + eventPayload[3].Size = sizeof(int); + eventPayload[3].DataPointer = ((IntPtr) (&CreatingTaskID)); + eventPayload[4].Size = sizeof(int); + eventPayload[4].DataPointer = ((IntPtr) (&TaskCreationOptions)); + eventPayload[5].Size = sizeof(int); + eventPayload[5].DataPointer = ((IntPtr) (&appDomain)); + if (TasksSetActivityIds) + { + Guid childActivityId = CreateGuidForTaskID(TaskID); + WriteEventWithRelatedActivityIdCore(TASKSCHEDULED_ID, &childActivityId, 6, eventPayload); + } + else + WriteEventCore(TASKSCHEDULED_ID, 6, eventPayload); + } + } + } + #endregion + + #region TaskStarted + /// <summary> + /// Fired just before a task actually starts executing. + /// </summary> + /// <param name="OriginatingTaskSchedulerID">The scheduler ID.</param> + /// <param name="OriginatingTaskID">The task ID.</param> + /// <param name="TaskID">The task ID.</param> + [Event(TASKSTARTED_ID, + Level = EventLevel.Informational, Keywords = Keywords.Tasks)] + public void TaskStarted( + int OriginatingTaskSchedulerID, int OriginatingTaskID, // PFX_COMMON_EVENT_HEADER + int TaskID) + { + if (IsEnabled(EventLevel.Informational, Keywords.Tasks)) + WriteEvent(TASKSTARTED_ID, OriginatingTaskSchedulerID, OriginatingTaskID, TaskID); + } + #endregion + + #region TaskCompleted + /// <summary> + /// Fired right after a task finished executing. + /// </summary> + /// <param name="OriginatingTaskSchedulerID">The scheduler ID.</param> + /// <param name="OriginatingTaskID">The task ID.</param> + /// <param name="TaskID">The task ID.</param> + /// <param name="IsExceptional">Whether the task completed due to an error.</param> + [SecuritySafeCritical] + [Event(TASKCOMPLETED_ID, Version=1, + Level = EventLevel.Informational, Keywords = Keywords.TaskStops)] + public void TaskCompleted( + int OriginatingTaskSchedulerID, int OriginatingTaskID, // PFX_COMMON_EVENT_HEADER + int TaskID, bool IsExceptional) + { + if (IsEnabled(EventLevel.Informational, Keywords.Tasks)) + { + unsafe + { + EventData* eventPayload = stackalloc EventData[4]; + Int32 isExceptionalInt = IsExceptional ? 1 : 0; + eventPayload[0].Size = sizeof(int); + eventPayload[0].DataPointer = ((IntPtr) (&OriginatingTaskSchedulerID)); + eventPayload[1].Size = sizeof(int); + eventPayload[1].DataPointer = ((IntPtr) (&OriginatingTaskID)); + eventPayload[2].Size = sizeof(int); + eventPayload[2].DataPointer = ((IntPtr) (&TaskID)); + eventPayload[3].Size = sizeof(int); + eventPayload[3].DataPointer = ((IntPtr) (&isExceptionalInt)); + WriteEventCore(TASKCOMPLETED_ID, 4, eventPayload); + } + } + } + #endregion + + #region TaskWaitBegin + /// <summary> + /// Fired when starting to wait for a taks's completion explicitly or implicitly. + /// </summary> + /// <param name="OriginatingTaskSchedulerID">The scheduler ID.</param> + /// <param name="OriginatingTaskID">The task ID.</param> + /// <param name="TaskID">The task ID.</param> + /// <param name="Behavior">Configured behavior for the wait.</param> + /// <param name="ContinueWithTaskID">If known, if 'TaskID' has a 'continueWith' task, mention give its ID here. + /// 0 means unknown. This allows better visualization of the common sequential chaining case.</param> + /// </summary> + [SecuritySafeCritical] + [Event(TASKWAITBEGIN_ID, Version=3, Task = TplEtwProvider.Tasks.TaskWait, Opcode = EventOpcode.Send, + Level = EventLevel.Informational, Keywords = Keywords.TaskTransfer|Keywords.Tasks)] + public void TaskWaitBegin( + int OriginatingTaskSchedulerID, int OriginatingTaskID, // PFX_COMMON_EVENT_HEADER + int TaskID, TaskWaitBehavior Behavior, int ContinueWithTaskID, int appDomain) + { + if (IsEnabled() && IsEnabled(EventLevel.Informational, Keywords.TaskTransfer|Keywords.Tasks)) + { + unsafe + { + EventData* eventPayload = stackalloc EventData[5]; + eventPayload[0].Size = sizeof(int); + eventPayload[0].DataPointer = ((IntPtr)(&OriginatingTaskSchedulerID)); + eventPayload[1].Size = sizeof(int); + eventPayload[1].DataPointer = ((IntPtr)(&OriginatingTaskID)); + eventPayload[2].Size = sizeof(int); + eventPayload[2].DataPointer = ((IntPtr)(&TaskID)); + eventPayload[3].Size = sizeof(int); + eventPayload[3].DataPointer = ((IntPtr)(&Behavior)); + eventPayload[4].Size = sizeof(int); + eventPayload[4].DataPointer = ((IntPtr)(&ContinueWithTaskID)); + if (TasksSetActivityIds) + { + Guid childActivityId = CreateGuidForTaskID(TaskID); + WriteEventWithRelatedActivityIdCore(TASKWAITBEGIN_ID, &childActivityId, 5, eventPayload); + } + else + WriteEventCore(TASKWAITBEGIN_ID, 5, eventPayload); + } + } + } + #endregion + + /// <summary> + /// Fired when the wait for a tasks completion returns. + /// </summary> + /// <param name="OriginatingTaskSchedulerID">The scheduler ID.</param> + /// <param name="OriginatingTaskID">The task ID.</param> + /// <param name="TaskID">The task ID.</param> + [Event(TASKWAITEND_ID, + Level = EventLevel.Verbose, Keywords = Keywords.Tasks)] + public void TaskWaitEnd( + int OriginatingTaskSchedulerID, int OriginatingTaskID, // PFX_COMMON_EVENT_HEADER + int TaskID) + { + // Log an event if indicated. + if (IsEnabled() && IsEnabled(EventLevel.Verbose, Keywords.Tasks)) + WriteEvent(TASKWAITEND_ID, OriginatingTaskSchedulerID, OriginatingTaskID, TaskID); + } + + /// <summary> + /// Fired when the the work (method) associated with a TaskWaitEnd completes + /// </summary> + /// <param name="OriginatingTaskSchedulerID">The scheduler ID.</param> + /// <param name="OriginatingTaskID">The task ID.</param> + /// <param name="TaskID">The task ID.</param> + [Event(TASKWAITCONTINUATIONCOMPLETE_ID, + Level = EventLevel.Verbose, Keywords = Keywords.TaskStops)] + public void TaskWaitContinuationComplete(int TaskID) + { + // Log an event if indicated. + if (IsEnabled() && IsEnabled(EventLevel.Verbose, Keywords.Tasks)) + WriteEvent(TASKWAITCONTINUATIONCOMPLETE_ID, TaskID); + } + + /// <summary> + /// Fired when the the work (method) associated with a TaskWaitEnd completes + /// </summary> + /// <param name="OriginatingTaskSchedulerID">The scheduler ID.</param> + /// <param name="OriginatingTaskID">The task ID.</param> + /// <param name="TaskID">The task ID.</param> + [Event(TASKWAITCONTINUATIONSTARTED_ID, + Level = EventLevel.Verbose, Keywords = Keywords.TaskStops)] + public void TaskWaitContinuationStarted(int TaskID) + { + // Log an event if indicated. + if (IsEnabled() && IsEnabled(EventLevel.Verbose, Keywords.Tasks)) + WriteEvent(TASKWAITCONTINUATIONSTARTED_ID, TaskID); + } + + /// <summary> + /// Fired when the an asynchronous continuation for a task is scheduled + /// </summary> + /// <param name="OriginatingTaskSchedulerID">The scheduler ID.</param> + /// <param name="OriginatingTaskID">The task ID.</param> + /// <param name="TaskID">The activityId for the continuation.</param> + [SecuritySafeCritical] + [Event(AWAITTASKCONTINUATIONSCHEDULED_ID, Task = Tasks.AwaitTaskContinuationScheduled, Opcode = EventOpcode.Send, + Level = EventLevel.Informational, Keywords = Keywords.TaskTransfer|Keywords.Tasks)] + public void AwaitTaskContinuationScheduled( + int OriginatingTaskSchedulerID, int OriginatingTaskID, // PFX_COMMON_EVENT_HEADER + int ContinuwWithTaskId) + { + if (IsEnabled() && IsEnabled(EventLevel.Informational, Keywords.TaskTransfer|Keywords.Tasks)) + { + unsafe + { + EventData* eventPayload = stackalloc EventData[3]; + eventPayload[0].Size = sizeof(int); + eventPayload[0].DataPointer = ((IntPtr) (&OriginatingTaskSchedulerID)); + eventPayload[1].Size = sizeof(int); + eventPayload[1].DataPointer = ((IntPtr) (&OriginatingTaskID)); + eventPayload[2].Size = sizeof(int); + eventPayload[2].DataPointer = ((IntPtr) (&ContinuwWithTaskId)); + if (TasksSetActivityIds) + { + Guid continuationActivityId = CreateGuidForTaskID(ContinuwWithTaskId); + WriteEventWithRelatedActivityIdCore(AWAITTASKCONTINUATIONSCHEDULED_ID, &continuationActivityId, 3, eventPayload); + } + else + WriteEventCore(AWAITTASKCONTINUATIONSCHEDULED_ID, 3, eventPayload); + } + } + } + + [SecuritySafeCritical] + [Event(TRACEOPERATIONSTART_ID, Version=1, + Level = EventLevel.Informational, Keywords = Keywords.AsyncCausalityOperation)] + public void TraceOperationBegin(int TaskID, string OperationName, long RelatedContext) + { + if (IsEnabled() && IsEnabled(EventLevel.Informational, Keywords.AsyncCausalityOperation)) + { + unsafe + { + fixed(char* operationNamePtr = OperationName) + { + EventData* eventPayload = stackalloc EventData[3]; + eventPayload[0].Size = sizeof(int); + eventPayload[0].DataPointer = ((IntPtr) (&TaskID)); + + eventPayload[1].Size = ((OperationName.Length + 1) * 2); + eventPayload[1].DataPointer = ((IntPtr) operationNamePtr); + + eventPayload[2].Size = sizeof(long); + eventPayload[2].DataPointer = ((IntPtr) (&RelatedContext)); + WriteEventCore(TRACEOPERATIONSTART_ID, 3, eventPayload); + } + } + } + } + + [SecuritySafeCritical] + [Event(TRACEOPERATIONRELATION_ID, Version=1, + Level = EventLevel.Informational, Keywords = Keywords.AsyncCausalityRelation)] + public void TraceOperationRelation(int TaskID, CausalityRelation Relation) + { + if (IsEnabled() && IsEnabled(EventLevel.Informational, Keywords.AsyncCausalityRelation)) + WriteEvent(TRACEOPERATIONRELATION_ID, TaskID,(int) Relation); // optmized overload for this exists + } + + [SecuritySafeCritical] + [Event(TRACEOPERATIONSTOP_ID, Version=1, + Level = EventLevel.Informational, Keywords = Keywords.AsyncCausalityOperation)] + public void TraceOperationEnd(int TaskID, AsyncCausalityStatus Status) + { + if (IsEnabled() && IsEnabled(EventLevel.Informational, Keywords.AsyncCausalityOperation)) + WriteEvent(TRACEOPERATIONSTOP_ID, TaskID,(int) Status); // optmized overload for this exists + } + + [SecuritySafeCritical] + [Event(TRACESYNCHRONOUSWORKSTART_ID, Version=1, + Level = EventLevel.Informational, Keywords = Keywords.AsyncCausalitySynchronousWork)] + public void TraceSynchronousWorkBegin(int TaskID, CausalitySynchronousWork Work) + { + if (IsEnabled() && IsEnabled(EventLevel.Informational, Keywords.AsyncCausalitySynchronousWork)) + WriteEvent(TRACESYNCHRONOUSWORKSTART_ID, TaskID,(int) Work); // optmized overload for this exists + } + + [SecuritySafeCritical] + [Event(TRACESYNCHRONOUSWORKSTOP_ID, Version=1, + Level = EventLevel.Informational, Keywords = Keywords.AsyncCausalitySynchronousWork)] + public void TraceSynchronousWorkEnd(CausalitySynchronousWork Work) + { + if (IsEnabled() && IsEnabled(EventLevel.Informational, Keywords.AsyncCausalitySynchronousWork)) + { + unsafe + { + EventData* eventPayload = stackalloc EventData[1]; + eventPayload[0].Size = sizeof(int); + eventPayload[0].DataPointer = ((IntPtr) (&Work)); + + WriteEventCore(TRACESYNCHRONOUSWORKSTOP_ID, 1, eventPayload); + } + } + } + + [NonEvent, System.Security.SecuritySafeCritical] + unsafe public void RunningContinuation(int TaskID, object Object) { RunningContinuation(TaskID, (long) *((void**) JitHelpers.UnsafeCastToStackPointer(ref Object))); } + [Event(20, Keywords = Keywords.Debug)] + private void RunningContinuation(int TaskID, long Object) + { + if (Debug) + WriteEvent(20, TaskID, Object); + } + + [NonEvent, System.Security.SecuritySafeCritical] + unsafe public void RunningContinuationList(int TaskID, int Index, object Object) { RunningContinuationList(TaskID, Index, (long) *((void**) JitHelpers.UnsafeCastToStackPointer(ref Object))); } + + [Event(21, Keywords = Keywords.Debug)] + public void RunningContinuationList(int TaskID, int Index, long Object) + { + if (Debug) + WriteEvent(21, TaskID, Index, Object); + } + + [Event(22, Keywords = Keywords.Debug)] + public void DebugMessage(string Message) { WriteEvent(22, Message); } + + [Event(23, Keywords = Keywords.Debug)] + public void DebugFacilityMessage(string Facility, string Message) { WriteEvent(23, Facility, Message); } + + [Event(24, Keywords = Keywords.Debug)] + public void DebugFacilityMessage1(string Facility, string Message, string Value1) { WriteEvent(24, Facility, Message, Value1); } + + [Event(25, Keywords = Keywords.DebugActivityId)] + public void SetActivityId(Guid NewId) + { + if (DebugActivityId) + WriteEvent(25, NewId); + } + + [Event(26, Keywords = Keywords.Debug)] + public void NewID(int TaskID) + { + if (Debug) + WriteEvent(26, TaskID); + } + + /// <summary> + /// Activity IDs are GUIDS but task IDS are integers (and are not unique across appdomains + /// This routine creates a process wide unique GUID given a task ID + /// </summary> + internal static Guid CreateGuidForTaskID(int taskID) + { + // The thread pool generated a process wide unique GUID from a task GUID by + // using the taskGuid, the appdomain ID, and 8 bytes of 'randomization' chosen by + // using the last 8 bytes as the provider GUID for this provider. + // These were generated by CreateGuid, and are reasonably random (and thus unlikley to collide + uint pid = EventSource.s_currentPid; + int appDomainID = System.Threading.Thread.GetDomainID(); + return new Guid(taskID, + (short) appDomainID , (short) (appDomainID >> 16), + (byte)pid, (byte)(pid >> 8), (byte)(pid >> 16), (byte)(pid >> 24), + 0xff, 0xdc, 0xd7, 0xb5); + } + } +} diff --git a/src/mscorlib/src/System/Threading/Tasks/Task.cs b/src/mscorlib/src/System/Threading/Tasks/Task.cs new file mode 100644 index 0000000000..36f8401a4d --- /dev/null +++ b/src/mscorlib/src/System/Threading/Tasks/Task.cs @@ -0,0 +1,7344 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +// =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ +// +// +// +// A schedulable unit of work. +// +// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Runtime; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Runtime.ExceptionServices; +using System.Security; +using System.Security.Permissions; +using System.Threading; +using System.Diagnostics; +using System.Diagnostics.Contracts; +using Microsoft.Win32; +using System.Diagnostics.Tracing; + +// Disable the "reference to volatile field not treated as volatile" error. +#pragma warning disable 0420 + +namespace System.Threading.Tasks +{ + + /// <summary> + /// Utility class for allocating structs as heap variables + /// </summary> + internal class Shared<T> + { + internal T Value; + + internal Shared(T value) + { + this.Value = value; + } + + } + + /// <summary> + /// Represents the current stage in the lifecycle of a <see cref="Task"/>. + /// </summary> + public enum TaskStatus + { + /// <summary> + /// The task has been initialized but has not yet been scheduled. + /// </summary> + Created, + /// <summary> + /// The task is waiting to be activated and scheduled internally by the .NET Framework infrastructure. + /// </summary> + WaitingForActivation, + /// <summary> + /// The task has been scheduled for execution but has not yet begun executing. + /// </summary> + WaitingToRun, + /// <summary> + /// The task is running but has not yet completed. + /// </summary> + Running, + // /// <summary> + // /// The task is currently blocked in a wait state. + // /// </summary> + // Blocked, + /// <summary> + /// The task has finished executing and is implicitly waiting for + /// attached child tasks to complete. + /// </summary> + WaitingForChildrenToComplete, + /// <summary> + /// The task completed execution successfully. + /// </summary> + RanToCompletion, + /// <summary> + /// The task acknowledged cancellation by throwing an OperationCanceledException with its own CancellationToken + /// while the token was in signaled state, or the task's CancellationToken was already signaled before the + /// task started executing. + /// </summary> + Canceled, + /// <summary> + /// The task completed due to an unhandled exception. + /// </summary> + Faulted + } + + /// <summary> + /// Represents an asynchronous operation. + /// </summary> + /// <remarks> + /// <para> + /// <see cref="Task"/> instances may be created in a variety of ways. The most common approach is by + /// using the Task type's <see cref="Factory"/> property to retrieve a <see + /// cref="System.Threading.Tasks.TaskFactory"/> instance that can be used to create tasks for several + /// purposes. For example, to create a <see cref="Task"/> that runs an action, the factory's StartNew + /// method may be used: + /// <code> + /// // C# + /// var t = Task.Factory.StartNew(() => DoAction()); + /// + /// ' Visual Basic + /// Dim t = Task.Factory.StartNew(Function() DoAction()) + /// </code> + /// </para> + /// <para> + /// The <see cref="Task"/> class also provides constructors that initialize the Task but that do not + /// schedule it for execution. For performance reasons, TaskFactory's StartNew method should be the + /// preferred mechanism for creating and scheduling computational tasks, but for scenarios where creation + /// and scheduling must be separated, the constructors may be used, and the task's <see cref="Start()"/> + /// method may then be used to schedule the task for execution at a later time. + /// </para> + /// <para> + /// All members of <see cref="Task"/>, except for <see cref="Dispose()"/>, are thread-safe + /// and may be used from multiple threads concurrently. + /// </para> + /// <para> + /// For operations that return values, the <see cref="System.Threading.Tasks.Task{TResult}"/> class + /// should be used. + /// </para> + /// <para> + /// For developers implementing custom debuggers, several internal and private members of Task may be + /// useful (these may change from release to release). The Int32 m_taskId field serves as the backing + /// store for the <see cref="Id"/> property, however accessing this field directly from a debugger may be + /// more efficient than accessing the same value through the property's getter method (the + /// s_taskIdCounter Int32 counter is used to retrieve the next available ID for a Task). Similarly, the + /// Int32 m_stateFlags field stores information about the current lifecycle stage of the Task, + /// information also accessible through the <see cref="Status"/> property. The m_action System.Object + /// field stores a reference to the Task's delegate, and the m_stateObject System.Object field stores the + /// async state passed to the Task by the developer. Finally, for debuggers that parse stack frames, the + /// InternalWait method serves a potential marker for when a Task is entering a wait operation. + /// </para> + /// </remarks> + [HostProtection(Synchronization = true, ExternalThreading = true)] + [DebuggerTypeProxy(typeof(SystemThreadingTasks_TaskDebugView))] + [DebuggerDisplay("Id = {Id}, Status = {Status}, Method = {DebuggerDisplayMethodDescription}")] + public class Task : IThreadPoolWorkItem, IAsyncResult, IDisposable + { + [ThreadStatic] + internal static Task t_currentTask; // The currently executing task. + [ThreadStatic] + private static StackGuard t_stackGuard; // The stack guard object for this thread + + internal static int s_taskIdCounter; //static counter used to generate unique task IDs + private readonly static TaskFactory s_factory = new TaskFactory(); + + private volatile int m_taskId; // this task's unique ID. initialized only if it is ever requested + + internal object m_action; // The body of the task. Might be Action<object>, Action<TState> or Action. Or possibly a Func. + // If m_action is set to null it will indicate that we operate in the + // "externally triggered completion" mode, which is exclusively meant + // for the signalling Task<TResult> (aka. promise). In this mode, + // we don't call InnerInvoke() in response to a Wait(), but simply wait on + // the completion event which will be set when the Future class calls Finish(). + // But the event would now be signalled if Cancel() is called + + + internal object m_stateObject; // A state object that can be optionally supplied, passed to action. + internal TaskScheduler m_taskScheduler; // The task scheduler this task runs under. + + internal volatile int m_stateFlags; + + private Task ParentForDebugger => m_contingentProperties?.m_parent; // Private property used by a debugger to access this Task's parent + private int StateFlagsForDebugger => m_stateFlags; // Private property used by a debugger to access this Task's state flags + + // State constants for m_stateFlags; + // The bits of m_stateFlags are allocated as follows: + // 0x40000000 - TaskBase state flag + // 0x3FFF0000 - Task state flags + // 0x0000FF00 - internal TaskCreationOptions flags + // 0x000000FF - publicly exposed TaskCreationOptions flags + // + // See TaskCreationOptions for bit values associated with TaskCreationOptions + // + private const int OptionsMask = 0xFFFF; // signifies the Options portion of m_stateFlags bin: 0000 0000 0000 0000 1111 1111 1111 1111 + internal const int TASK_STATE_STARTED = 0x10000; //bin: 0000 0000 0000 0001 0000 0000 0000 0000 + internal const int TASK_STATE_DELEGATE_INVOKED = 0x20000; //bin: 0000 0000 0000 0010 0000 0000 0000 0000 + internal const int TASK_STATE_DISPOSED = 0x40000; //bin: 0000 0000 0000 0100 0000 0000 0000 0000 + internal const int TASK_STATE_EXCEPTIONOBSERVEDBYPARENT = 0x80000; //bin: 0000 0000 0000 1000 0000 0000 0000 0000 + internal const int TASK_STATE_CANCELLATIONACKNOWLEDGED = 0x100000; //bin: 0000 0000 0001 0000 0000 0000 0000 0000 + internal const int TASK_STATE_FAULTED = 0x200000; //bin: 0000 0000 0010 0000 0000 0000 0000 0000 + internal const int TASK_STATE_CANCELED = 0x400000; //bin: 0000 0000 0100 0000 0000 0000 0000 0000 + internal const int TASK_STATE_WAITING_ON_CHILDREN = 0x800000; //bin: 0000 0000 1000 0000 0000 0000 0000 0000 + internal const int TASK_STATE_RAN_TO_COMPLETION = 0x1000000; //bin: 0000 0001 0000 0000 0000 0000 0000 0000 + internal const int TASK_STATE_WAITINGFORACTIVATION = 0x2000000; //bin: 0000 0010 0000 0000 0000 0000 0000 0000 + internal const int TASK_STATE_COMPLETION_RESERVED = 0x4000000; //bin: 0000 0100 0000 0000 0000 0000 0000 0000 + internal const int TASK_STATE_THREAD_WAS_ABORTED = 0x8000000; //bin: 0000 1000 0000 0000 0000 0000 0000 0000 + internal const int TASK_STATE_WAIT_COMPLETION_NOTIFICATION = 0x10000000; //bin: 0001 0000 0000 0000 0000 0000 0000 0000 + //This could be moved to InternalTaskOptions enum + internal const int TASK_STATE_EXECUTIONCONTEXT_IS_NULL = 0x20000000; //bin: 0010 0000 0000 0000 0000 0000 0000 0000 + internal const int TASK_STATE_TASKSCHEDULED_WAS_FIRED = 0x40000000; //bin: 0100 0000 0000 0000 0000 0000 0000 0000 + + // A mask for all of the final states a task may be in + private const int TASK_STATE_COMPLETED_MASK = TASK_STATE_CANCELED | TASK_STATE_FAULTED | TASK_STATE_RAN_TO_COMPLETION; + + // Values for ContingentProperties.m_internalCancellationRequested. + private const int CANCELLATION_REQUESTED = 0x1; + + // Can be null, a single continuation, a list of continuations, or s_taskCompletionSentinel, + // in that order. The logic arround this object assumes it will never regress to a previous state. + private volatile object m_continuationObject = null; + + // m_continuationObject is set to this when the task completes. + private static readonly object s_taskCompletionSentinel = new object(); + + // A private flag that would be set (only) by the debugger + // When true the Async Causality logging trace is enabled as well as a dictionary to relate operation ids with Tasks + [FriendAccessAllowed] + internal static bool s_asyncDebuggingEnabled; //false by default + + // This dictonary relates the task id, from an operation id located in the Async Causality log to the actual + // task. This is to be used by the debugger ONLY. Task in this dictionary represent current active tasks. + private static readonly Dictionary<int, Task> s_currentActiveTasks = new Dictionary<int, Task>(); + private static readonly Object s_activeTasksLock = new Object(); + + // These methods are a way to access the dictionary both from this class and for other classes that also + // activate dummy tasks. Specifically the AsyncTaskMethodBuilder and AsyncTaskMethodBuilder<> + [FriendAccessAllowed] + internal static bool AddToActiveTasks(Task task) + { + Contract.Requires(task != null, "Null Task objects can't be added to the ActiveTasks collection"); + lock (s_activeTasksLock) + { + s_currentActiveTasks[task.Id] = task; + } + //always return true to keep signature as bool for backwards compatibility + return true; + } + + [FriendAccessAllowed] + internal static void RemoveFromActiveTasks(int taskId) + { + lock (s_activeTasksLock) + { + s_currentActiveTasks.Remove(taskId); + } + } + + // We moved a number of Task properties into this class. The idea is that in most cases, these properties never + // need to be accessed during the life cycle of a Task, so we don't want to instantiate them every time. Once + // one of these properties needs to be written, we will instantiate a ContingentProperties object and set + // the appropriate property. + internal class ContingentProperties + { + // Additional context + + internal ExecutionContext m_capturedContext; // The execution context to run the task within, if any. Only set from non-concurrent contexts. + + // Completion fields (exceptions and event) + + internal volatile ManualResetEventSlim m_completionEvent; // Lazily created if waiting is required. + internal volatile TaskExceptionHolder m_exceptionsHolder; // Tracks exceptions, if any have occurred + + // Cancellation fields (token, registration, and internally requested) + + internal CancellationToken m_cancellationToken; // Task's cancellation token, if it has one + internal Shared<CancellationTokenRegistration> m_cancellationRegistration; // Task's registration with the cancellation token + internal volatile int m_internalCancellationRequested; // Its own field because multiple threads legally try to set it. + + // Parenting fields + + // # of active children + 1 (for this task itself). + // Used for ensuring all children are done before this task can complete + // The extra count helps prevent the race condition for executing the final state transition + // (i.e. whether the last child or this task itself should call FinishStageTwo()) + internal volatile int m_completionCountdown = 1; + // A list of child tasks that threw an exception (TCEs don't count), + // but haven't yet been waited on by the parent, lazily initialized. + internal volatile List<Task> m_exceptionalChildren; + // A task's parent, or null if parent-less. Only set during Task construction. + internal Task m_parent; + + /// <summary> + /// Sets the internal completion event. + /// </summary> + internal void SetCompleted() + { + var mres = m_completionEvent; + if (mres != null) mres.Set(); + } + + /// <summary> + /// Checks if we registered a CT callback during construction, and deregisters it. + /// This should be called when we know the registration isn't useful anymore. Specifically from Finish() if the task has completed + /// successfully or with an exception. + /// </summary> + internal void DeregisterCancellationCallback() + { + if (m_cancellationRegistration != null) + { + // Harden against ODEs thrown from disposing of the CTR. + // Since the task has already been put into a final state by the time this + // is called, all we can do here is suppress the exception. + try { m_cancellationRegistration.Value.Dispose(); } + catch (ObjectDisposedException) { } + m_cancellationRegistration = null; + } + } + } + + + // This field will only be instantiated to some non-null value if any ContingentProperties need to be set. + // This will be a ContingentProperties instance or a type derived from it + internal ContingentProperties m_contingentProperties; + + // Special internal constructor to create an already-completed task. + // if canceled==true, create a Canceled task, or else create a RanToCompletion task. + // Constructs the task as already completed + internal Task(bool canceled, TaskCreationOptions creationOptions, CancellationToken ct) + { + int optionFlags = (int)creationOptions; + if (canceled) + { + m_stateFlags = TASK_STATE_CANCELED | TASK_STATE_CANCELLATIONACKNOWLEDGED | optionFlags; + m_contingentProperties = new ContingentProperties() // can't have children, so just instantiate directly + { + m_cancellationToken = ct, + m_internalCancellationRequested = CANCELLATION_REQUESTED, + }; + } + else + m_stateFlags = TASK_STATE_RAN_TO_COMPLETION | optionFlags; + } + + /// <summary>Constructor for use with promise-style tasks that aren't configurable.</summary> + internal Task() + { + m_stateFlags = TASK_STATE_WAITINGFORACTIVATION | (int)InternalTaskOptions.PromiseTask; + } + + // Special constructor for use with promise-style tasks. + // Added promiseStyle parameter as an aid to the compiler to distinguish between (state,TCO) and + // (action,TCO). It should always be true. + internal Task(object state, TaskCreationOptions creationOptions, bool promiseStyle) + { + Contract.Assert(promiseStyle, "Promise CTOR: promiseStyle was false"); + + // Check the creationOptions. We allow the AttachedToParent option to be specified for promise tasks. + // Also allow RunContinuationsAsynchronously because this is the constructor called by TCS + if ((creationOptions & ~(TaskCreationOptions.AttachedToParent | TaskCreationOptions.RunContinuationsAsynchronously)) != 0) + { + ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.creationOptions); + } + + // Only set a parent if AttachedToParent is specified. + if ((creationOptions & TaskCreationOptions.AttachedToParent) != 0) + { + Task parent = Task.InternalCurrent; + if (parent != null) + { + EnsureContingentPropertiesInitializedUnsafe().m_parent = parent; + } + } + + TaskConstructorCore(null, state, default(CancellationToken), creationOptions, InternalTaskOptions.PromiseTask, null); + } + + /// <summary> + /// Initializes a new <see cref="Task"/> with the specified action. + /// </summary> + /// <param name="action">The delegate that represents the code to execute in the Task.</param> + /// <exception cref="T:System.ArgumentNullException">The <paramref name="action"/> argument is null.</exception> + [MethodImplAttribute(MethodImplOptions.NoInlining)] // Methods containing StackCrawlMark local var have to be marked non-inlineable + public Task(Action action) + : this(action, null, null, default(CancellationToken), TaskCreationOptions.None, InternalTaskOptions.None, null) + { + StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller; + PossiblyCaptureContext(ref stackMark); + } + + /// <summary> + /// Initializes a new <see cref="Task"/> with the specified action and <see cref="System.Threading.CancellationToken">CancellationToken</see>. + /// </summary> + /// <param name="action">The delegate that represents the code to execute in the Task.</param> + /// <param name="cancellationToken">The <see cref="System.Threading.CancellationToken">CancellationToken</see> + /// that will be assigned to the new Task.</param> + /// <exception cref="T:System.ArgumentNullException">The <paramref name="action"/> argument is null.</exception> + /// <exception cref="T:System.ObjectDisposedException">The provided <see cref="System.Threading.CancellationToken">CancellationToken</see> + /// has already been disposed. + /// </exception> + [MethodImplAttribute(MethodImplOptions.NoInlining)] // Methods containing StackCrawlMark local var have to be marked non-inlineable + public Task(Action action, CancellationToken cancellationToken) + : this(action, null, null, cancellationToken, TaskCreationOptions.None, InternalTaskOptions.None, null) + { + StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller; + PossiblyCaptureContext(ref stackMark); + } + + /// <summary> + /// Initializes a new <see cref="Task"/> with the specified action and creation options. + /// </summary> + /// <param name="action">The delegate that represents the code to execute in the task.</param> + /// <param name="creationOptions"> + /// The <see cref="System.Threading.Tasks.TaskCreationOptions">TaskCreationOptions</see> used to + /// customize the Task's behavior. + /// </param> + /// <exception cref="T:System.ArgumentNullException"> + /// The <paramref name="action"/> argument is null. + /// </exception> + /// <exception cref="T:System.ArgumentOutOfRangeException"> + /// The <paramref name="creationOptions"/> argument specifies an invalid value for <see + /// cref="T:System.Threading.Tasks.TaskCreationOptions"/>. + /// </exception> + [MethodImplAttribute(MethodImplOptions.NoInlining)] // Methods containing StackCrawlMark local var have to be marked non-inlineable + public Task(Action action, TaskCreationOptions creationOptions) + : this(action, null, Task.InternalCurrentIfAttached(creationOptions), default(CancellationToken), creationOptions, InternalTaskOptions.None, null) + { + StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller; + PossiblyCaptureContext(ref stackMark); + } + + /// <summary> + /// Initializes a new <see cref="Task"/> with the specified action and creation options. + /// </summary> + /// <param name="action">The delegate that represents the code to execute in the task.</param> + /// <param name="cancellationToken">The <see cref="CancellationToken"/> that will be assigned to the new task.</param> + /// <param name="creationOptions"> + /// The <see cref="System.Threading.Tasks.TaskCreationOptions">TaskCreationOptions</see> used to + /// customize the Task's behavior. + /// </param> + /// <exception cref="T:System.ArgumentNullException"> + /// The <paramref name="action"/> argument is null. + /// </exception> + /// <exception cref="T:System.ArgumentOutOfRangeException"> + /// The <paramref name="creationOptions"/> argument specifies an invalid value for <see + /// cref="T:System.Threading.Tasks.TaskCreationOptions"/>. + /// </exception> + /// <exception cref="T:System.ObjectDisposedException">The provided <see cref="System.Threading.CancellationToken">CancellationToken</see> + /// has already been disposed. + /// </exception> + [MethodImplAttribute(MethodImplOptions.NoInlining)] // Methods containing StackCrawlMark local var have to be marked non-inlineable + public Task(Action action, CancellationToken cancellationToken, TaskCreationOptions creationOptions) + : this(action, null, Task.InternalCurrentIfAttached(creationOptions), cancellationToken, creationOptions, InternalTaskOptions.None, null) + { + StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller; + PossiblyCaptureContext(ref stackMark); + } + + + /// <summary> + /// Initializes a new <see cref="Task"/> with the specified action and state. + /// </summary> + /// <param name="action">The delegate that represents the code to execute in the task.</param> + /// <param name="state">An object representing data to be used by the action.</param> + /// <exception cref="T:System.ArgumentNullException"> + /// The <paramref name="action"/> argument is null. + /// </exception> + [MethodImplAttribute(MethodImplOptions.NoInlining)] // Methods containing StackCrawlMark local var have to be marked non-inlineable + public Task(Action<object> action, object state) + : this(action, state, null, default(CancellationToken), TaskCreationOptions.None, InternalTaskOptions.None, null) + { + StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller; + PossiblyCaptureContext(ref stackMark); + } + + /// <summary> + /// Initializes a new <see cref="Task"/> with the specified action, state, snd options. + /// </summary> + /// <param name="action">The delegate that represents the code to execute in the task.</param> + /// <param name="state">An object representing data to be used by the action.</param> + /// <param name="cancellationToken">The <see cref="CancellationToken"/> that will be assigned to the new task.</param> + /// <exception cref="T:System.ArgumentNullException"> + /// The <paramref name="action"/> argument is null. + /// </exception> + /// <exception cref="T:System.ObjectDisposedException">The provided <see cref="System.Threading.CancellationToken">CancellationToken</see> + /// has already been disposed. + /// </exception> + [MethodImplAttribute(MethodImplOptions.NoInlining)] // Methods containing StackCrawlMark local var have to be marked non-inlineable + public Task(Action<object> action, object state, CancellationToken cancellationToken) + : this(action, state, null, cancellationToken, TaskCreationOptions.None, InternalTaskOptions.None, null) + { + StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller; + PossiblyCaptureContext(ref stackMark); + } + + /// <summary> + /// Initializes a new <see cref="Task"/> with the specified action, state, snd options. + /// </summary> + /// <param name="action">The delegate that represents the code to execute in the task.</param> + /// <param name="state">An object representing data to be used by the action.</param> + /// <param name="creationOptions"> + /// The <see cref="System.Threading.Tasks.TaskCreationOptions">TaskCreationOptions</see> used to + /// customize the Task's behavior. + /// </param> + /// <exception cref="T:System.ArgumentNullException"> + /// The <paramref name="action"/> argument is null. + /// </exception> + /// <exception cref="T:System.ArgumentOutOfRangeException"> + /// The <paramref name="creationOptions"/> argument specifies an invalid value for <see + /// cref="T:System.Threading.Tasks.TaskCreationOptions"/>. + /// </exception> + [MethodImplAttribute(MethodImplOptions.NoInlining)] // Methods containing StackCrawlMark local var have to be marked non-inlineable + public Task(Action<object> action, object state, TaskCreationOptions creationOptions) + : this(action, state, Task.InternalCurrentIfAttached(creationOptions), default(CancellationToken), creationOptions, InternalTaskOptions.None, null) + { + StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller; + PossiblyCaptureContext(ref stackMark); + } + + /// <summary> + /// Initializes a new <see cref="Task"/> with the specified action, state, snd options. + /// </summary> + /// <param name="action">The delegate that represents the code to execute in the task.</param> + /// <param name="state">An object representing data to be used by the action.</param> + /// <param name="cancellationToken">The <see cref="CancellationToken"/> that will be assigned to the new task.</param> + /// <param name="creationOptions"> + /// The <see cref="System.Threading.Tasks.TaskCreationOptions">TaskCreationOptions</see> used to + /// customize the Task's behavior. + /// </param> + /// <exception cref="T:System.ArgumentNullException"> + /// The <paramref name="action"/> argument is null. + /// </exception> + /// <exception cref="T:System.ArgumentOutOfRangeException"> + /// The <paramref name="creationOptions"/> argument specifies an invalid value for <see + /// cref="T:System.Threading.Tasks.TaskCreationOptions"/>. + /// </exception> + /// <exception cref="T:System.ObjectDisposedException">The provided <see cref="System.Threading.CancellationToken">CancellationToken</see> + /// has already been disposed. + /// </exception> + [MethodImplAttribute(MethodImplOptions.NoInlining)] // Methods containing StackCrawlMark local var have to be marked non-inlineable + public Task(Action<object> action, object state, CancellationToken cancellationToken, TaskCreationOptions creationOptions) + : this(action, state, Task.InternalCurrentIfAttached(creationOptions), cancellationToken, creationOptions, InternalTaskOptions.None, null) + { + StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller; + PossiblyCaptureContext(ref stackMark); + } + + internal Task(Action<object> action, object state, Task parent, CancellationToken cancellationToken, + TaskCreationOptions creationOptions, InternalTaskOptions internalOptions, TaskScheduler scheduler, ref StackCrawlMark stackMark) + : this(action, state, parent, cancellationToken, creationOptions, internalOptions, scheduler) + { + PossiblyCaptureContext(ref stackMark); + } + + /// <summary> + /// An internal constructor used by the factory methods on task and its descendent(s). + /// This variant does not capture the ExecutionContext; it is up to the caller to do that. + /// </summary> + /// <param name="action">An action to execute.</param> + /// <param name="state">Optional state to pass to the action.</param> + /// <param name="parent">Parent of Task.</param> + /// <param name="cancellationToken">A CancellationToken for the task.</param> + /// <param name="scheduler">A task scheduler under which the task will run.</param> + /// <param name="creationOptions">Options to control its execution.</param> + /// <param name="internalOptions">Internal options to control its execution</param> + internal Task(Delegate action, object state, Task parent, CancellationToken cancellationToken, + TaskCreationOptions creationOptions, InternalTaskOptions internalOptions, TaskScheduler scheduler) + { + if (action == null) + { + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.action); + } + Contract.EndContractBlock(); + + // Keep a link to your parent if: (A) You are attached, or (B) you are self-replicating. + if (parent != null && + ((creationOptions & TaskCreationOptions.AttachedToParent) != 0 || + (internalOptions & InternalTaskOptions.SelfReplicating) != 0)) + { + EnsureContingentPropertiesInitializedUnsafe().m_parent = parent; + } + + TaskConstructorCore(action, state, cancellationToken, creationOptions, internalOptions, scheduler); + } + + /// <summary> + /// Common logic used by the following internal ctors: + /// Task() + /// Task(object action, object state, Task parent, TaskCreationOptions options, TaskScheduler taskScheduler) + /// </summary> + /// <param name="action">Action for task to execute.</param> + /// <param name="state">Object to which to pass to action (may be null)</param> + /// <param name="scheduler">Task scheduler on which to run thread (only used by continuation tasks).</param> + /// <param name="cancellationToken">A CancellationToken for the Task.</param> + /// <param name="creationOptions">Options to customize behavior of Task.</param> + /// <param name="internalOptions">Internal options to customize behavior of Task.</param> + internal void TaskConstructorCore(object action, object state, CancellationToken cancellationToken, + TaskCreationOptions creationOptions, InternalTaskOptions internalOptions, TaskScheduler scheduler) + { + m_action = action; + m_stateObject = state; + m_taskScheduler = scheduler; + + // Check for validity of options + if ((creationOptions & + ~(TaskCreationOptions.AttachedToParent | + TaskCreationOptions.LongRunning | + TaskCreationOptions.DenyChildAttach | + TaskCreationOptions.HideScheduler | + TaskCreationOptions.PreferFairness | + TaskCreationOptions.RunContinuationsAsynchronously)) != 0) + { + ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.creationOptions); + } + +#if DEBUG + // Check the validity of internalOptions + int illegalInternalOptions = + (int) (internalOptions & + ~(InternalTaskOptions.SelfReplicating | + InternalTaskOptions.ChildReplica | + InternalTaskOptions.PromiseTask | + InternalTaskOptions.ContinuationTask | + InternalTaskOptions.LazyCancellation | + InternalTaskOptions.QueuedByRuntime)); + Contract.Assert(illegalInternalOptions == 0, "TaskConstructorCore: Illegal internal options"); +#endif + + // Throw exception if the user specifies both LongRunning and SelfReplicating + if (((creationOptions & TaskCreationOptions.LongRunning) != 0) && + ((internalOptions & InternalTaskOptions.SelfReplicating) != 0)) + { + ThrowHelper.ThrowInvalidOperationException(ExceptionResource.Task_ctor_LRandSR); + } + + // Assign options to m_stateAndOptionsFlag. + Contract.Assert(m_stateFlags == 0, "TaskConstructorCore: non-zero m_stateFlags"); + Contract.Assert((((int)creationOptions) | OptionsMask) == OptionsMask, "TaskConstructorCore: options take too many bits"); + var tmpFlags = (int)creationOptions | (int)internalOptions; + if ((m_action == null) || ((internalOptions & InternalTaskOptions.ContinuationTask) != 0)) + { + // For continuation tasks or TaskCompletionSource.Tasks, begin life in the + // WaitingForActivation state rather than the Created state. + tmpFlags |= TASK_STATE_WAITINGFORACTIVATION; + } + m_stateFlags = tmpFlags; // one write to the volatile m_stateFlags instead of two when setting the above options + + // Now is the time to add the new task to the children list + // of the creating task if the options call for it. + // We can safely call the creator task's AddNewChild() method to register it, + // because at this point we are already on its thread of execution. + + Task parent = m_contingentProperties?.m_parent; + if (parent != null + && ((creationOptions & TaskCreationOptions.AttachedToParent) != 0) + && ((parent.CreationOptions & TaskCreationOptions.DenyChildAttach) == 0) + ) + { + parent.AddNewChild(); + } + + // if we have a non-null cancellationToken, allocate the contingent properties to save it + // we need to do this as the very last thing in the construction path, because the CT registration could modify m_stateFlags + if (cancellationToken.CanBeCanceled) + { + Contract.Assert((internalOptions & + (InternalTaskOptions.ChildReplica | InternalTaskOptions.SelfReplicating | InternalTaskOptions.ContinuationTask)) == 0, + "TaskConstructorCore: Did not expect to see cancelable token for replica/replicating or continuation task."); + + AssignCancellationToken(cancellationToken, null, null); + } + } + + /// <summary> + /// Handles everything needed for associating a CancellationToken with a task which is being constructed. + /// This method is meant to be be called either from the TaskConstructorCore or from ContinueWithCore. + /// </summary> + private void AssignCancellationToken(CancellationToken cancellationToken, Task antecedent, TaskContinuation continuation) + { + // There is no need to worry about concurrency issues here because we are in the constructor path of the task -- + // there should not be any race conditions to set m_contingentProperties at this point. + ContingentProperties props = EnsureContingentPropertiesInitializedUnsafe(); + props.m_cancellationToken = cancellationToken; + + try + { + if (AppContextSwitches.ThrowExceptionIfDisposedCancellationTokenSource) + { + cancellationToken.ThrowIfSourceDisposed(); + } + + // If an unstarted task has a valid CancellationToken that gets signalled while the task is still not queued + // we need to proactively cancel it, because it may never execute to transition itself. + // The only way to accomplish this is to register a callback on the CT. + // We exclude Promise tasks from this, because TaskCompletionSource needs to fully control the inner tasks's lifetime (i.e. not allow external cancellations) + if ((((InternalTaskOptions)Options & + (InternalTaskOptions.QueuedByRuntime | InternalTaskOptions.PromiseTask | InternalTaskOptions.LazyCancellation)) == 0)) + { + if (cancellationToken.IsCancellationRequested) + { + // Fast path for an already-canceled cancellationToken + this.InternalCancel(false); + } + else + { + // Regular path for an uncanceled cancellationToken + CancellationTokenRegistration ctr; + if (antecedent == null) + { + // if no antecedent was specified, use this task's reference as the cancellation state object + ctr = cancellationToken.InternalRegisterWithoutEC(s_taskCancelCallback, this); + } + else + { + // If an antecedent was specified, pack this task, its antecedent and the TaskContinuation together as a tuple + // and use it as the cancellation state object. This will be unpacked in the cancellation callback so that + // antecedent.RemoveCancellation(continuation) can be invoked. + ctr = cancellationToken.InternalRegisterWithoutEC(s_taskCancelCallback, + new Tuple<Task, Task, TaskContinuation>(this, antecedent, continuation)); + } + + props.m_cancellationRegistration = new Shared<CancellationTokenRegistration>(ctr); + } + } + } + catch + { + // If we have an exception related to our CancellationToken, then we need to subtract ourselves + // from our parent before throwing it. + Task parent = m_contingentProperties?.m_parent; + if ((parent != null) && + ((Options & TaskCreationOptions.AttachedToParent) != 0) + && ((parent.Options & TaskCreationOptions.DenyChildAttach) == 0)) + { + parent.DisregardChild(); + } + throw; + } + } + + + // Static delegate to be used as a cancellation callback on unstarted tasks that have a valid cancellation token. + // This is necessary to transition them into canceled state if their cancellation token is signalled while they are still not queued + private readonly static Action<Object> s_taskCancelCallback = new Action<Object>(TaskCancelCallback); + private static void TaskCancelCallback(Object o) + { + var targetTask = o as Task; + if (targetTask == null) + { + var tuple = o as Tuple<Task, Task, TaskContinuation>; + if (tuple != null) + { + targetTask = tuple.Item1; + + Task antecedentTask = tuple.Item2; + TaskContinuation continuation = tuple.Item3; + antecedentTask.RemoveContinuation(continuation); + } + } + Contract.Assert(targetTask != null, + "targetTask should have been non-null, with the supplied argument being a task or a tuple containing one"); + targetTask.InternalCancel(false); + } + + // Debugger support + private string DebuggerDisplayMethodDescription + { + get + { + Delegate d = (Delegate)m_action; + return d != null ? d.Method.ToString() : "{null}"; + } + } + + + /// <summary> + /// Captures the ExecutionContext so long as flow isn't suppressed. + /// </summary> + /// <param name="stackMark">A stack crawl mark pointing to the frame of the caller.</param> + + [SecuritySafeCritical] + internal void PossiblyCaptureContext(ref StackCrawlMark stackMark) + { + Contract.Assert(m_contingentProperties == null || m_contingentProperties.m_capturedContext == null, + "Captured an ExecutionContext when one was already captured."); + + // In the legacy .NET 3.5 build, we don't have the optimized overload of Capture() + // available, so we call the parameterless overload. + CapturedContext = ExecutionContext.Capture( + ref stackMark, + ExecutionContext.CaptureOptions.IgnoreSyncCtx | ExecutionContext.CaptureOptions.OptimizeDefaultCase); + } + + // Internal property to process TaskCreationOptions access and mutation. + internal TaskCreationOptions Options + { + get + { + int stateFlags = m_stateFlags; // "cast away" volatility to enable inlining of OptionsMethod + return OptionsMethod(stateFlags); + } + } + + // Similar to Options property, but allows for the use of a cached flags value rather than + // a read of the volatile m_stateFlags field. + internal static TaskCreationOptions OptionsMethod(int flags) + { + Contract.Assert((OptionsMask & 1) == 1, "OptionsMask needs a shift in Options.get"); + return (TaskCreationOptions)(flags & OptionsMask); + } + + // Atomically OR-in newBits to m_stateFlags, while making sure that + // no illegalBits are set. Returns true on success, false on failure. + internal bool AtomicStateUpdate(int newBits, int illegalBits) + { + // This could be implemented in terms of: + // internal bool AtomicStateUpdate(int newBits, int illegalBits, ref int oldFlags); + // but for high-throughput perf, that delegation's cost is noticeable. + + SpinWait sw = new SpinWait(); + do + { + int oldFlags = m_stateFlags; + if ((oldFlags & illegalBits) != 0) return false; + if (Interlocked.CompareExchange(ref m_stateFlags, oldFlags | newBits, oldFlags) == oldFlags) + { + return true; + } + sw.SpinOnce(); + } while (true); + } + + internal bool AtomicStateUpdate(int newBits, int illegalBits, ref int oldFlags) + { + SpinWait sw = new SpinWait(); + do + { + oldFlags = m_stateFlags; + if ((oldFlags & illegalBits) != 0) return false; + if (Interlocked.CompareExchange(ref m_stateFlags, oldFlags | newBits, oldFlags) == oldFlags) + { + return true; + } + sw.SpinOnce(); + } while (true); + } + + /// <summary> + /// Sets or clears the TASK_STATE_WAIT_COMPLETION_NOTIFICATION state bit. + /// The debugger sets this bit to aid it in "stepping out" of an async method body. + /// If enabled is true, this must only be called on a task that has not yet been completed. + /// If enabled is false, this may be called on completed tasks. + /// Either way, it should only be used for promise-style tasks. + /// </summary> + /// <param name="enabled">true to set the bit; false to unset the bit.</param> + internal void SetNotificationForWaitCompletion(bool enabled) + { + Contract.Assert((Options & (TaskCreationOptions)InternalTaskOptions.PromiseTask) != 0, + "Should only be used for promise-style tasks"); // hasn't been vetted on other kinds as there hasn't been a need + + if (enabled) + { + // Atomically set the END_AWAIT_NOTIFICATION bit + bool success = AtomicStateUpdate(TASK_STATE_WAIT_COMPLETION_NOTIFICATION, + TASK_STATE_COMPLETED_MASK | TASK_STATE_COMPLETION_RESERVED); + Contract.Assert(success, "Tried to set enabled on completed Task"); + } + else + { + // Atomically clear the END_AWAIT_NOTIFICATION bit + SpinWait sw = new SpinWait(); + while (true) + { + int oldFlags = m_stateFlags; + int newFlags = oldFlags & (~TASK_STATE_WAIT_COMPLETION_NOTIFICATION); + if (Interlocked.CompareExchange(ref m_stateFlags, newFlags, oldFlags) == oldFlags) break; + sw.SpinOnce(); + } + } + } + + /// <summary> + /// Calls the debugger notification method if the right bit is set and if + /// the task itself allows for the notification to proceed. + /// </summary> + /// <returns>true if the debugger was notified; otherwise, false.</returns> + internal bool NotifyDebuggerOfWaitCompletionIfNecessary() + { + // Notify the debugger if of any of the tasks we've waited on requires notification + if (IsWaitNotificationEnabled && ShouldNotifyDebuggerOfWaitCompletion) + { + NotifyDebuggerOfWaitCompletion(); + return true; + } + return false; + } + + /// <summary>Returns true if any of the supplied tasks require wait notification.</summary> + /// <param name="tasks">The tasks to check.</param> + /// <returns>true if any of the tasks require notification; otherwise, false.</returns> + internal static bool AnyTaskRequiresNotifyDebuggerOfWaitCompletion(Task[] tasks) + { + Contract.Assert(tasks != null, "Expected non-null array of tasks"); + foreach (var task in tasks) + { + if (task != null && + task.IsWaitNotificationEnabled && + task.ShouldNotifyDebuggerOfWaitCompletion) // potential recursion + { + return true; + } + } + return false; + } + + /// <summary>Gets whether either the end await bit is set or (not xor) the task has not completed successfully.</summary> + /// <returns>(DebuggerBitSet || !RanToCompletion)</returns> + internal bool IsWaitNotificationEnabledOrNotRanToCompletion + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + return (m_stateFlags & (Task.TASK_STATE_WAIT_COMPLETION_NOTIFICATION | Task.TASK_STATE_RAN_TO_COMPLETION)) + != Task.TASK_STATE_RAN_TO_COMPLETION; + } + } + + /// <summary> + /// Determines whether we should inform the debugger that we're ending a join with a task. + /// This should only be called if the debugger notification bit is set, as it is has some cost, + /// namely it is a virtual call (however calling it if the bit is not set is not functionally + /// harmful). Derived implementations may choose to only conditionally call down to this base + /// implementation. + /// </summary> + internal virtual bool ShouldNotifyDebuggerOfWaitCompletion // ideally would be familyAndAssembly, but that can't be done in C# + { + get + { + // It's theoretically possible but extremely rare that this assert could fire because the + // bit was unset between the time that it was checked and this method was called. + // It's so remote a chance that it's worth having the assert to protect against misuse. + bool isWaitNotificationEnabled = IsWaitNotificationEnabled; + Contract.Assert(isWaitNotificationEnabled, "Should only be called if the wait completion bit is set."); + return isWaitNotificationEnabled; + } + } + + /// <summary>Gets whether the task's debugger notification for wait completion bit is set.</summary> + /// <returns>true if the bit is set; false if it's not set.</returns> + internal bool IsWaitNotificationEnabled // internal only to enable unit tests; would otherwise be private + { + get { return (m_stateFlags & TASK_STATE_WAIT_COMPLETION_NOTIFICATION) != 0; } + } + + /// <summary>Placeholder method used as a breakpoint target by the debugger. Must not be inlined or optimized.</summary> + /// <remarks>All joins with a task should end up calling this if their debugger notification bit is set.</remarks> + [MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)] + private void NotifyDebuggerOfWaitCompletion() + { + // It's theoretically possible but extremely rare that this assert could fire because the + // bit was unset between the time that it was checked and this method was called. + // It's so remote a chance that it's worth having the assert to protect against misuse. + Contract.Assert(IsWaitNotificationEnabled, "Should only be called if the wait completion bit is set."); + + // Now that we're notifying the debugger, clear the bit. The debugger should do this anyway, + // but this adds a bit of protection in case it fails to, and given that the debugger is involved, + // the overhead here for the interlocked is negligable. We do still rely on the debugger + // to clear bits, as this doesn't recursively clear bits in the case of, for example, WhenAny. + SetNotificationForWaitCompletion(enabled: false); + } + + + // Atomically mark a Task as started while making sure that it is not canceled. + internal bool MarkStarted() + { + return AtomicStateUpdate(TASK_STATE_STARTED, TASK_STATE_CANCELED | TASK_STATE_STARTED); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal bool FireTaskScheduledIfNeeded(TaskScheduler ts) + { + var etwLog = TplEtwProvider.Log; + if (etwLog.IsEnabled() && (m_stateFlags & Task.TASK_STATE_TASKSCHEDULED_WAS_FIRED) == 0) + { + m_stateFlags |= Task.TASK_STATE_TASKSCHEDULED_WAS_FIRED; + + Task currentTask = Task.InternalCurrent; + Task parentTask = m_contingentProperties?.m_parent; + etwLog.TaskScheduled(ts.Id, currentTask == null ? 0 : currentTask.Id, + this.Id, parentTask == null ? 0 : parentTask.Id, (int)this.Options, + System.Threading.Thread.GetDomainID()); + return true; + } + else + return false; + } + + /// <summary> + /// Internal function that will be called by a new child task to add itself to + /// the children list of the parent (this). + /// + /// Since a child task can only be created from the thread executing the action delegate + /// of this task, reentrancy is neither required nor supported. This should not be called from + /// anywhere other than the task construction/initialization codepaths. + /// </summary> + internal void AddNewChild() + { + Contract.Assert(Task.InternalCurrent == this || this.IsSelfReplicatingRoot, "Task.AddNewChild(): Called from an external context"); + + var props = EnsureContingentPropertiesInitialized(); + + if (props.m_completionCountdown == 1 && !IsSelfReplicatingRoot) + { + // A count of 1 indicates so far there was only the parent, and this is the first child task + // Single kid => no fuss about who else is accessing the count. Let's save ourselves 100 cycles + // We exclude self replicating root tasks from this optimization, because further child creation can take place on + // other cores and with bad enough timing this write may not be visible to them. + props.m_completionCountdown++; + } + else + { + // otherwise do it safely + Interlocked.Increment(ref props.m_completionCountdown); + } + } + + // This is called in the case where a new child is added, but then encounters a CancellationToken-related exception. + // We need to subtract that child from m_completionCountdown, or the parent will never complete. + internal void DisregardChild() + { + Contract.Assert(Task.InternalCurrent == this, "Task.DisregardChild(): Called from an external context"); + + var props = EnsureContingentPropertiesInitialized(); + Contract.Assert(props.m_completionCountdown >= 2, "Task.DisregardChild(): Expected parent count to be >= 2"); + Interlocked.Decrement(ref props.m_completionCountdown); + } + + /// <summary> + /// Starts the <see cref="Task"/>, scheduling it for execution to the current <see + /// cref="System.Threading.Tasks.TaskScheduler">TaskScheduler</see>. + /// </summary> + /// <remarks> + /// A task may only be started and run only once. Any attempts to schedule a task a second time + /// will result in an exception. + /// </remarks> + /// <exception cref="InvalidOperationException"> + /// The <see cref="Task"/> is not in a valid state to be started. It may have already been started, + /// executed, or canceled, or it may have been created in a manner that doesn't support direct + /// scheduling. + /// </exception> + public void Start() + { + Start(TaskScheduler.Current); + } + + /// <summary> + /// Starts the <see cref="Task"/>, scheduling it for execution to the specified <see + /// cref="System.Threading.Tasks.TaskScheduler">TaskScheduler</see>. + /// </summary> + /// <remarks> + /// A task may only be started and run only once. Any attempts to schedule a task a second time will + /// result in an exception. + /// </remarks> + /// <param name="scheduler"> + /// The <see cref="System.Threading.Tasks.TaskScheduler">TaskScheduler</see> with which to associate + /// and execute this task. + /// </param> + /// <exception cref="ArgumentNullException"> + /// The <paramref name="scheduler"/> argument is null. + /// </exception> + /// <exception cref="InvalidOperationException"> + /// The <see cref="Task"/> is not in a valid state to be started. It may have already been started, + /// executed, or canceled, or it may have been created in a manner that doesn't support direct + /// scheduling. + /// </exception> + public void Start(TaskScheduler scheduler) + { + // Read the volatile m_stateFlags field once and cache it for subsequent operations + int flags = m_stateFlags; + + // Need to check this before (m_action == null) because completed tasks will + // set m_action to null. We would want to know if this is the reason that m_action == null. + if (IsCompletedMethod(flags)) + { + ThrowHelper.ThrowInvalidOperationException(ExceptionResource.Task_Start_TaskCompleted); + } + + if (scheduler == null) + { + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.scheduler); + } + + var options = OptionsMethod(flags); + if ((options & (TaskCreationOptions)InternalTaskOptions.PromiseTask) != 0) + { + ThrowHelper.ThrowInvalidOperationException(ExceptionResource.Task_Start_Promise); + } + if ((options & (TaskCreationOptions)InternalTaskOptions.ContinuationTask) != 0) + { + ThrowHelper.ThrowInvalidOperationException(ExceptionResource.Task_Start_ContinuationTask); + } + + // Make sure that Task only gets started once. Or else throw an exception. + if (Interlocked.CompareExchange(ref m_taskScheduler, scheduler, null) != null) + { + ThrowHelper.ThrowInvalidOperationException(ExceptionResource.Task_Start_AlreadyStarted); + } + + ScheduleAndStart(true); + } + + /// <summary> + /// Runs the <see cref="Task"/> synchronously on the current <see + /// cref="System.Threading.Tasks.TaskScheduler">TaskScheduler</see>. + /// </summary> + /// <remarks> + /// <para> + /// A task may only be started and run only once. Any attempts to schedule a task a second time will + /// result in an exception. + /// </para> + /// <para> + /// Tasks executed with <see cref="RunSynchronously()"/> will be associated with the current <see + /// cref="System.Threading.Tasks.TaskScheduler">TaskScheduler</see>. + /// </para> + /// <para> + /// If the target scheduler does not support running this Task on the current thread, the Task will + /// be scheduled for execution on the scheduler, and the current thread will block until the + /// Task has completed execution. + /// </para> + /// </remarks> + /// <exception cref="InvalidOperationException"> + /// The <see cref="Task"/> is not in a valid state to be started. It may have already been started, + /// executed, or canceled, or it may have been created in a manner that doesn't support direct + /// scheduling. + /// </exception> + public void RunSynchronously() + { + InternalRunSynchronously(TaskScheduler.Current, waitForCompletion: true); + } + + /// <summary> + /// Runs the <see cref="Task"/> synchronously on the <see + /// cref="System.Threading.Tasks.TaskScheduler">scheduler</see> provided. + /// </summary> + /// <remarks> + /// <para> + /// A task may only be started and run only once. Any attempts to schedule a task a second time will + /// result in an exception. + /// </para> + /// <para> + /// If the target scheduler does not support running this Task on the current thread, the Task will + /// be scheduled for execution on the scheduler, and the current thread will block until the + /// Task has completed execution. + /// </para> + /// </remarks> + /// <exception cref="InvalidOperationException"> + /// The <see cref="Task"/> is not in a valid state to be started. It may have already been started, + /// executed, or canceled, or it may have been created in a manner that doesn't support direct + /// scheduling. + /// </exception> + /// <exception cref="ArgumentNullException">The <paramref name="scheduler"/> parameter + /// is null.</exception> + /// <param name="scheduler">The scheduler on which to attempt to run this task inline.</param> + public void RunSynchronously(TaskScheduler scheduler) + { + if (scheduler == null) + { + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.scheduler); + } + Contract.EndContractBlock(); + + InternalRunSynchronously(scheduler, waitForCompletion: true); + } + + // + // Internal version of RunSynchronously that allows not waiting for completion. + // + [SecuritySafeCritical] // Needed for QueueTask + internal void InternalRunSynchronously(TaskScheduler scheduler, bool waitForCompletion) + { + Contract.Requires(scheduler != null, "Task.InternalRunSynchronously(): null TaskScheduler"); + + // Read the volatile m_stateFlags field once and cache it for subsequent operations + int flags = m_stateFlags; + + // Can't call this method on a continuation task + var options = OptionsMethod(flags); + if ((options & (TaskCreationOptions)InternalTaskOptions.ContinuationTask) != 0) + { + ThrowHelper.ThrowInvalidOperationException(ExceptionResource.Task_RunSynchronously_Continuation); + } + + // Can't call this method on a promise-style task + if ((options & (TaskCreationOptions)InternalTaskOptions.PromiseTask) != 0) + { + ThrowHelper.ThrowInvalidOperationException(ExceptionResource.Task_RunSynchronously_Promise); + } + + // Can't call this method on a task that has already completed + if (IsCompletedMethod(flags)) + { + ThrowHelper.ThrowInvalidOperationException(ExceptionResource.Task_RunSynchronously_TaskCompleted); + } + + // Make sure that Task only gets started once. Or else throw an exception. + if (Interlocked.CompareExchange(ref m_taskScheduler, scheduler, null) != null) + { + ThrowHelper.ThrowInvalidOperationException(ExceptionResource.Task_RunSynchronously_AlreadyStarted); + } + + // execute only if we successfully cancel when concurrent cancel attempts are made. + // otherwise throw an exception, because we've been canceled. + if (MarkStarted()) + { + bool taskQueued = false; + try + { + // We wrap TryRunInline() in a try/catch block and move an excepted task to Faulted here, + // but not in Wait()/WaitAll()/FastWaitAll(). Here, we know for sure that the + // task will not be subsequently scheduled (assuming that the scheduler adheres + // to the guideline that an exception implies that no state change took place), + // so it is safe to catch the exception and move the task to a final state. The + // same cannot be said for Wait()/WaitAll()/FastWaitAll(). + if (!scheduler.TryRunInline(this, false)) + { + scheduler.InternalQueueTask(this); + taskQueued = true; // only mark this after successfully queuing the task. + } + + // A successful TryRunInline doesn't guarantee completion, as there may be unfinished children. + // Also if we queued the task above, the task may not be done yet. + if (waitForCompletion && !IsCompleted) + { + SpinThenBlockingWait(Timeout.Infinite, default(CancellationToken)); + } + } + catch (Exception e) + { + // we 1) either received an unexpected exception originating from a custom scheduler, which needs to be wrapped in a TSE and thrown + // 2) or a a ThreadAbortException, which we need to skip here, because it would already have been handled in Task.Execute + if (!taskQueued && !(e is ThreadAbortException)) + { + // We had a problem with TryRunInline() or QueueTask(). + // Record the exception, marking ourselves as Completed/Faulted. + TaskSchedulerException tse = new TaskSchedulerException(e); + AddException(tse); + Finish(false); + + // Mark ourselves as "handled" to avoid crashing the finalizer thread if the caller neglects to + // call Wait() on this task. + // m_contingentProperties.m_exceptionsHolder *should* already exist after AddException() + Contract.Assert( + (m_contingentProperties != null) && + (m_contingentProperties.m_exceptionsHolder != null) && + (m_contingentProperties.m_exceptionsHolder.ContainsFaultList), + "Task.InternalRunSynchronously(): Expected m_contingentProperties.m_exceptionsHolder to exist " + + "and to have faults recorded."); + m_contingentProperties.m_exceptionsHolder.MarkAsHandled(false); + + // And re-throw. + throw tse; + } + // We had a problem with waiting or this is a thread abort. Just re-throw. + else throw; + } + } + else + { + Contract.Assert((m_stateFlags & TASK_STATE_CANCELED) != 0, "Task.RunSynchronously: expected TASK_STATE_CANCELED to be set"); + // Can't call this method on canceled task. + ThrowHelper.ThrowInvalidOperationException(ExceptionResource.Task_RunSynchronously_TaskCompleted); + } + } + + + //// + //// Helper methods for Factory StartNew methods. + //// + + + // Implicitly converts action to object and handles the meat of the StartNew() logic. + internal static Task InternalStartNew( + Task creatingTask, Delegate action, object state, CancellationToken cancellationToken, TaskScheduler scheduler, + TaskCreationOptions options, InternalTaskOptions internalOptions, ref StackCrawlMark stackMark) + { + // Validate arguments. + if (scheduler == null) + { + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.scheduler); + } + Contract.EndContractBlock(); + + // Create and schedule the task. This throws an InvalidOperationException if already shut down. + // Here we add the InternalTaskOptions.QueuedByRuntime to the internalOptions, so that TaskConstructorCore can skip the cancellation token registration + Task t = new Task(action, state, creatingTask, cancellationToken, options, internalOptions | InternalTaskOptions.QueuedByRuntime, scheduler); + t.PossiblyCaptureContext(ref stackMark); + + t.ScheduleAndStart(false); + return t; + } + + /// <summary> + /// Gets a unique ID for a <see cref="Task">Task</see> or task continuation instance. + /// </summary> + internal static int NewId() + { + int newId = 0; + // We need to repeat if Interlocked.Increment wraps around and returns 0. + // Otherwise next time this task's Id is queried it will get a new value + do + { + newId = Interlocked.Increment(ref s_taskIdCounter); + } + while (newId == 0); + TplEtwProvider.Log.NewID(newId); + return newId; + } + + + ///////////// + // properties + + /// <summary> + /// Gets a unique ID for this <see cref="Task">Task</see> instance. + /// </summary> + /// <remarks> + /// Task IDs are assigned on-demand and do not necessarily represent the order in the which Task + /// instances were created. + /// </remarks> + public int Id + { + get + { + if (m_taskId == 0) + { + int newId = NewId(); + Interlocked.CompareExchange(ref m_taskId, newId, 0); + } + + return m_taskId; + } + } + + /// <summary> + /// Returns the unique ID of the currently executing <see cref="Task">Task</see>. + /// </summary> + public static int? CurrentId + { + get + { + Task currentTask = InternalCurrent; + if (currentTask != null) + return currentTask.Id; + else + return null; + } + } + + /// <summary> + /// Gets the <see cref="Task">Task</see> instance currently executing, or + /// null if none exists. + /// </summary> + internal static Task InternalCurrent + { + get { return t_currentTask; } + } + + /// <summary> + /// Gets the Task instance currently executing if the specified creation options + /// contain AttachedToParent. + /// </summary> + /// <param name="options">The options to check.</param> + /// <returns>The current task if there is one and if AttachToParent is in the options; otherwise, null.</returns> + internal static Task InternalCurrentIfAttached(TaskCreationOptions creationOptions) + { + return (creationOptions & TaskCreationOptions.AttachedToParent) != 0 ? InternalCurrent : null; + } + + /// <summary> + /// Gets the StackGuard object assigned to the current thread. + /// </summary> + internal static StackGuard CurrentStackGuard + { + get + { + StackGuard sg = t_stackGuard; + if (sg == null) + { + t_stackGuard = sg = new StackGuard(); + } + return sg; + } + } + + + /// <summary> + /// Gets the <see cref="T:System.AggregateException">Exception</see> that caused the <see + /// cref="Task">Task</see> to end prematurely. If the <see + /// cref="Task">Task</see> completed successfully or has not yet thrown any + /// exceptions, this will return null. + /// </summary> + /// <remarks> + /// Tasks that throw unhandled exceptions store the resulting exception and propagate it wrapped in a + /// <see cref="System.AggregateException"/> in calls to <see cref="Wait()">Wait</see> + /// or in accesses to the <see cref="Exception"/> property. Any exceptions not observed by the time + /// the Task instance is garbage collected will be propagated on the finalizer thread. + /// </remarks> + public AggregateException Exception + { + get + { + AggregateException e = null; + + // If you're faulted, retrieve the exception(s) + if (IsFaulted) e = GetExceptions(false); + + // Only return an exception in faulted state (skip manufactured exceptions) + // A "benevolent" race condition makes it possible to return null when IsFaulted is + // true (i.e., if IsFaulted is set just after the check to IsFaulted above). + Contract.Assert((e == null) || IsFaulted, "Task.Exception_get(): returning non-null value when not Faulted"); + + return e; + } + } + + /// <summary> + /// Gets the <see cref="T:System.Threading.Tasks.TaskStatus">TaskStatus</see> of this Task. + /// </summary> + public TaskStatus Status + { + get + { + TaskStatus rval; + + // get a cached copy of the state flags. This should help us + // to get a consistent view of the flags if they are changing during the + // execution of this method. + int sf = m_stateFlags; + + if ((sf & TASK_STATE_FAULTED) != 0) rval = TaskStatus.Faulted; + else if ((sf & TASK_STATE_CANCELED) != 0) rval = TaskStatus.Canceled; + else if ((sf & TASK_STATE_RAN_TO_COMPLETION) != 0) rval = TaskStatus.RanToCompletion; + else if ((sf & TASK_STATE_WAITING_ON_CHILDREN) != 0) rval = TaskStatus.WaitingForChildrenToComplete; + else if ((sf & TASK_STATE_DELEGATE_INVOKED) != 0) rval = TaskStatus.Running; + else if ((sf & TASK_STATE_STARTED) != 0) rval = TaskStatus.WaitingToRun; + else if ((sf & TASK_STATE_WAITINGFORACTIVATION) != 0) rval = TaskStatus.WaitingForActivation; + else rval = TaskStatus.Created; + + return rval; + } + } + + /// <summary> + /// Gets whether this <see cref="Task">Task</see> instance has completed + /// execution due to being canceled. + /// </summary> + /// <remarks> + /// A <see cref="Task">Task</see> will complete in Canceled state either if its <see cref="CancellationToken">CancellationToken</see> + /// was marked for cancellation before the task started executing, or if the task acknowledged the cancellation request on + /// its already signaled CancellationToken by throwing an + /// <see cref="System.OperationCanceledException">OperationCanceledException</see> that bears the same + /// <see cref="System.Threading.CancellationToken">CancellationToken</see>. + /// </remarks> + public bool IsCanceled + { + get + { + // Return true if canceled bit is set and faulted bit is not set + return (m_stateFlags & (TASK_STATE_CANCELED | TASK_STATE_FAULTED)) == TASK_STATE_CANCELED; + } + } + + /// <summary> + /// Returns true if this task has a cancellation token and it was signaled. + /// To be used internally in execute entry codepaths. + /// </summary> + internal bool IsCancellationRequested + { + get + { + // check both the internal cancellation request flag and the CancellationToken attached to this task + var props = Volatile.Read(ref m_contingentProperties); + return props != null && + (props.m_internalCancellationRequested == CANCELLATION_REQUESTED || + props.m_cancellationToken.IsCancellationRequested); + } + } + + /// <summary> + /// Ensures that the contingent properties field has been initialized. + /// ASSUMES THAT m_stateFlags IS ALREADY SET! + /// </summary> + /// <returns>The initialized contingent properties object.</returns> + internal ContingentProperties EnsureContingentPropertiesInitialized() + { + return LazyInitializer.EnsureInitialized(ref m_contingentProperties, () => new ContingentProperties()); + } + + /// <summary> + /// Without synchronization, ensures that the contingent properties field has been initialized. + /// ASSUMES THAT m_stateFlags IS ALREADY SET! + /// </summary> + /// <returns>The initialized contingent properties object.</returns> + internal ContingentProperties EnsureContingentPropertiesInitializedUnsafe() + { + return m_contingentProperties ?? (m_contingentProperties = new ContingentProperties()); + } + + /// <summary> + /// This internal property provides access to the CancellationToken that was set on the task + /// when it was constructed. + /// </summary> + internal CancellationToken CancellationToken + { + get + { + var props = Volatile.Read(ref m_contingentProperties); + return (props == null) ? default(CancellationToken) : props.m_cancellationToken; + } + } + + /// <summary> + /// Gets whether this <see cref="Task"/> threw an OperationCanceledException while its CancellationToken was signaled. + /// </summary> + internal bool IsCancellationAcknowledged + { + get { return (m_stateFlags & TASK_STATE_CANCELLATIONACKNOWLEDGED) != 0; } + } + + + /// <summary> + /// Gets whether this <see cref="Task">Task</see> has completed. + /// </summary> + /// <remarks> + /// <see cref="IsCompleted"/> will return true when the Task is in one of the three + /// final states: <see cref="System.Threading.Tasks.TaskStatus.RanToCompletion">RanToCompletion</see>, + /// <see cref="System.Threading.Tasks.TaskStatus.Faulted">Faulted</see>, or + /// <see cref="System.Threading.Tasks.TaskStatus.Canceled">Canceled</see>. + /// </remarks> + public bool IsCompleted + { + get + { + int stateFlags = m_stateFlags; // enable inlining of IsCompletedMethod by "cast"ing away the volatility + return IsCompletedMethod(stateFlags); + } + } + + // Similar to IsCompleted property, but allows for the use of a cached flags value + // rather than reading the volatile m_stateFlags field. + private static bool IsCompletedMethod(int flags) + { + return (flags & TASK_STATE_COMPLETED_MASK) != 0; + } + + // For use in InternalWait -- marginally faster than (Task.Status == TaskStatus.RanToCompletion) + internal bool IsRanToCompletion + { + get { return (m_stateFlags & TASK_STATE_COMPLETED_MASK) == TASK_STATE_RAN_TO_COMPLETION; } + } + + /// <summary> + /// Gets the <see cref="T:System.Threading.Tasks.TaskCreationOptions">TaskCreationOptions</see> used + /// to create this task. + /// </summary> + public TaskCreationOptions CreationOptions + { + get { return Options & (TaskCreationOptions)(~InternalTaskOptions.InternalOptionsMask); } + } + + /// <summary> + /// Gets a <see cref="T:System.Threading.WaitHandle"/> that can be used to wait for the task to + /// complete. + /// </summary> + /// <remarks> + /// Using the wait functionality provided by <see cref="Wait()"/> + /// should be preferred over using <see cref="IAsyncResult.AsyncWaitHandle"/> for similar + /// functionality. + /// </remarks> + /// <exception cref="T:System.ObjectDisposedException"> + /// The <see cref="Task"/> has been disposed. + /// </exception> + WaitHandle IAsyncResult.AsyncWaitHandle + { + // Although a slim event is used internally to avoid kernel resource allocation, this function + // forces allocation of a true WaitHandle when called. + get + { + bool isDisposed = (m_stateFlags & TASK_STATE_DISPOSED) != 0; + if (isDisposed) + { + ThrowHelper.ThrowObjectDisposedException(ExceptionResource.Task_ThrowIfDisposed); + } + return CompletedEvent.WaitHandle; + } + } + + /// <summary> + /// Gets the state object supplied when the <see cref="Task">Task</see> was created, + /// or null if none was supplied. + /// </summary> + public object AsyncState + { + get { return m_stateObject; } + } + + /// <summary> + /// Gets an indication of whether the asynchronous operation completed synchronously. + /// </summary> + /// <value>true if the asynchronous operation completed synchronously; otherwise, false.</value> + bool IAsyncResult.CompletedSynchronously + { + get + { + return false; + } + } + + /// <summary> + /// Provides access to the TaskScheduler responsible for executing this Task. + /// </summary> + internal TaskScheduler ExecutingTaskScheduler + { + get { return m_taskScheduler; } + } + + /// <summary> + /// Provides access to factory methods for creating <see cref="Task"/> and <see cref="Task{TResult}"/> instances. + /// </summary> + /// <remarks> + /// The factory returned from <see cref="Factory"/> is a default instance + /// of <see cref="System.Threading.Tasks.TaskFactory"/>, as would result from using + /// the default constructor on TaskFactory. + /// </remarks> + public static TaskFactory Factory { get { return s_factory; } } + + /// <summary>A task that's already been completed successfully.</summary> + private static Task s_completedTask; + + /// <summary>Gets a task that's already been completed successfully.</summary> + /// <remarks>May not always return the same instance.</remarks> + public static Task CompletedTask + { + get + { + var completedTask = s_completedTask; + if (completedTask == null) + s_completedTask = completedTask = new Task(false, (TaskCreationOptions)InternalTaskOptions.DoNotDispose, default(CancellationToken)); // benign initialization race condition + return completedTask; + } + } + + /// <summary> + /// Provides an event that can be used to wait for completion. + /// Only called by IAsyncResult.AsyncWaitHandle, which means that we really do need to instantiate a completion event. + /// </summary> + internal ManualResetEventSlim CompletedEvent + { + get + { + var contingentProps = EnsureContingentPropertiesInitialized(); + if (contingentProps.m_completionEvent == null) + { + bool wasCompleted = IsCompleted; + ManualResetEventSlim newMre = new ManualResetEventSlim(wasCompleted); + if (Interlocked.CompareExchange(ref contingentProps.m_completionEvent, newMre, null) != null) + { + // Someone else already set the value, so we will just close the event right away. + newMre.Dispose(); + } + else if (!wasCompleted && IsCompleted) + { + // We published the event as unset, but the task has subsequently completed. + // Set the event's state properly so that callers don't deadlock. + newMre.Set(); + } + } + + return contingentProps.m_completionEvent; + } + } + + /// <summary> + /// Determines whether this is the root task of a self replicating group. + /// </summary> + internal bool IsSelfReplicatingRoot + { + get + { + // Return true if self-replicating bit is set and child replica bit is not set + return (Options & (TaskCreationOptions)(InternalTaskOptions.SelfReplicating | InternalTaskOptions.ChildReplica)) + == (TaskCreationOptions)InternalTaskOptions.SelfReplicating; + } + } + + /// <summary> + /// Determines whether the task is a replica itself. + /// </summary> + internal bool IsChildReplica + { + get { return (Options & (TaskCreationOptions)InternalTaskOptions.ChildReplica) != 0; } + } + + internal int ActiveChildCount + { + get + { + var props = Volatile.Read(ref m_contingentProperties); + return props != null ? props.m_completionCountdown - 1 : 0; + } + } + + /// <summary> + /// The property formerly known as IsFaulted. + /// </summary> + internal bool ExceptionRecorded + { + get + { + var props = Volatile.Read(ref m_contingentProperties); + return (props != null) && (props.m_exceptionsHolder != null) && (props.m_exceptionsHolder.ContainsFaultList); + } + } + + /// <summary> + /// Gets whether the <see cref="Task"/> completed due to an unhandled exception. + /// </summary> + /// <remarks> + /// If <see cref="IsFaulted"/> is true, the Task's <see cref="Status"/> will be equal to + /// <see cref="System.Threading.Tasks.TaskStatus.Faulted">TaskStatus.Faulted</see>, and its + /// <see cref="Exception"/> property will be non-null. + /// </remarks> + public bool IsFaulted + { + get + { + // Faulted is "king" -- if that bit is present (regardless of other bits), we are faulted. + return ((m_stateFlags & TASK_STATE_FAULTED) != 0); + } + } + + /// <summary> + /// The captured execution context for the current task to run inside + /// If the TASK_STATE_EXECUTIONCONTEXT_IS_NULL flag is set, this means ExecutionContext.Capture returned null, otherwise + /// If the captured context is the default, nothing is saved, otherwise the m_contingentProperties inflates to save the context + /// </summary> + internal ExecutionContext CapturedContext + { + get + { + if ((m_stateFlags & TASK_STATE_EXECUTIONCONTEXT_IS_NULL) == TASK_STATE_EXECUTIONCONTEXT_IS_NULL) + { + return null; + } + else + { + return m_contingentProperties?.m_capturedContext ?? ExecutionContext.PreAllocatedDefault; + } + } + set + { + // There is no need to atomically set this bit because this set() method is only called during construction, and therefore there should be no contending accesses to m_stateFlags + if (value == null) + { + m_stateFlags |= TASK_STATE_EXECUTIONCONTEXT_IS_NULL; + } + else if (!value.IsPreAllocatedDefault) // not the default context, then inflate the contingent properties and set it + { + EnsureContingentPropertiesInitializedUnsafe().m_capturedContext = value; + } + //else do nothing, this is the default context + } + } + + /// <summary> + /// Static helper function to copy specific ExecutionContext + /// </summary> + /// <param name="capturedContext">The captured context</param> + /// <returns>The copied context, null if the capturedContext is null</returns> + private static ExecutionContext CopyExecutionContext(ExecutionContext capturedContext) + { + if (capturedContext == null) + return null; + if (capturedContext.IsPreAllocatedDefault) + return ExecutionContext.PreAllocatedDefault; + + return capturedContext.CreateCopy(); + } + + +#if DEBUG + /// <summary> + /// Retrieves an identifier for the task. + /// </summary> + internal int InternalId + { + get { return GetHashCode(); } + } +#endif + + ///////////// + // methods + + + /// <summary> + /// Disposes the <see cref="Task"/>, releasing all of its unmanaged resources. + /// </summary> + /// <remarks> + /// Unlike most of the members of <see cref="Task"/>, this method is not thread-safe. + /// Also, <see cref="Dispose()"/> may only be called on a <see cref="Task"/> that is in one of + /// the final states: <see cref="System.Threading.Tasks.TaskStatus.RanToCompletion">RanToCompletion</see>, + /// <see cref="System.Threading.Tasks.TaskStatus.Faulted">Faulted</see>, or + /// <see cref="System.Threading.Tasks.TaskStatus.Canceled">Canceled</see>. + /// </remarks> + /// <exception cref="T:System.InvalidOperationException"> + /// The exception that is thrown if the <see cref="Task"/> is not in + /// one of the final states: <see cref="System.Threading.Tasks.TaskStatus.RanToCompletion">RanToCompletion</see>, + /// <see cref="System.Threading.Tasks.TaskStatus.Faulted">Faulted</see>, or + /// <see cref="System.Threading.Tasks.TaskStatus.Canceled">Canceled</see>. + /// </exception> + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + /// <summary> + /// Disposes the <see cref="Task"/>, releasing all of its unmanaged resources. + /// </summary> + /// <param name="disposing"> + /// A Boolean value that indicates whether this method is being called due to a call to <see + /// cref="Dispose()"/>. + /// </param> + /// <remarks> + /// Unlike most of the members of <see cref="Task"/>, this method is not thread-safe. + /// </remarks> + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + // Dispose is a nop if this task was created with the DoNotDispose internal option. + // This is done before the completed check, because if we're not touching any + // state on the task, it's ok for it to happen before completion. + if ((Options & (TaskCreationOptions)InternalTaskOptions.DoNotDispose) != 0) + { + return; + } + + // Task must be completed to dispose + if (!IsCompleted) + { + ThrowHelper.ThrowInvalidOperationException(ExceptionResource.Task_Dispose_NotCompleted); + } + + // Dispose of the underlying completion event if it exists + var cp = Volatile.Read(ref m_contingentProperties); + if (cp != null) + { + // Make a copy to protect against racing Disposes. + // If we wanted to make this a bit safer, we could use an interlocked here, + // but we state that Dispose is not thread safe. + var ev = cp.m_completionEvent; + if (ev != null) + { + // Null out the completion event in contingent props; we'll use our copy from here on out + cp.m_completionEvent = null; + + // In the unlikely event that our completion event is inflated but not yet signaled, + // go ahead and signal the event. If you dispose of an unsignaled MRES, then any waiters + // will deadlock; an ensuing Set() will not wake them up. In the event of an AppDomainUnload, + // there is no guarantee that anyone else is going to signal the event, and it does no harm to + // call Set() twice on m_completionEvent. + if (!ev.IsSet) ev.Set(); + + // Finally, dispose of the event + ev.Dispose(); + } + } + } + + // We OR the flags to indicate the object has been disposed. The task + // has already completed at this point, and the only conceivable race condition would + // be with the unsetting of the TASK_STATE_WAIT_COMPLETION_NOTIFICATION flag, which + // is extremely unlikely and also benign. (Worst case: we hit a breakpoint + // twice instead of once in the debugger. Weird, but not lethal.) + m_stateFlags |= TASK_STATE_DISPOSED; + } + + ///////////// + // internal helpers + + + /// <summary> + /// Schedules the task for execution. + /// </summary> + /// <param name="needsProtection">If true, TASK_STATE_STARTED bit is turned on in + /// an atomic fashion, making sure that TASK_STATE_CANCELED does not get set + /// underneath us. If false, TASK_STATE_STARTED bit is OR-ed right in. This + /// allows us to streamline things a bit for StartNew(), where competing cancellations + /// are not a problem.</param> + [SecuritySafeCritical] // Needed for QueueTask + internal void ScheduleAndStart(bool needsProtection) + { + Contract.Assert(m_taskScheduler != null, "expected a task scheduler to have been selected"); + Contract.Assert((m_stateFlags & TASK_STATE_STARTED) == 0, "task has already started"); + + // Set the TASK_STATE_STARTED bit + if (needsProtection) + { + if (!MarkStarted()) + { + // A cancel has snuck in before we could get started. Quietly exit. + return; + } + } + else + { + m_stateFlags |= TASK_STATE_STARTED; + } + + if (s_asyncDebuggingEnabled) + { + AddToActiveTasks(this); + } + + if (AsyncCausalityTracer.LoggingOn && (Options & (TaskCreationOptions)InternalTaskOptions.ContinuationTask) == 0) + { + //For all other task than TaskContinuations we want to log. TaskContinuations log in their constructor + AsyncCausalityTracer.TraceOperationCreation(CausalityTraceLevel.Required, this.Id, "Task: "+((Delegate)m_action).Method.Name, 0); + } + + + try + { + // Queue to the indicated scheduler. + m_taskScheduler.InternalQueueTask(this); + } + catch (ThreadAbortException tae) + { + AddException(tae); + FinishThreadAbortedTask(true, false); + } + catch (Exception e) + { + // The scheduler had a problem queueing this task. Record the exception, leaving this task in + // a Faulted state. + TaskSchedulerException tse = new TaskSchedulerException(e); + AddException(tse); + Finish(false); + + // Now we need to mark ourselves as "handled" to avoid crashing the finalizer thread if we are called from StartNew() + // or from the self replicating logic, because in both cases the exception is either propagated outside directly, or added + // to an enclosing parent. However we won't do this for continuation tasks, because in that case we internally eat the exception + // and therefore we need to make sure the user does later observe it explicitly or see it on the finalizer. + + if ((Options & (TaskCreationOptions)InternalTaskOptions.ContinuationTask) == 0) + { + // m_contingentProperties.m_exceptionsHolder *should* already exist after AddException() + Contract.Assert( + (m_contingentProperties != null) && + (m_contingentProperties.m_exceptionsHolder != null) && + (m_contingentProperties.m_exceptionsHolder.ContainsFaultList), + "Task.ScheduleAndStart(): Expected m_contingentProperties.m_exceptionsHolder to exist " + + "and to have faults recorded."); + + m_contingentProperties.m_exceptionsHolder.MarkAsHandled(false); + } + // re-throw the exception wrapped as a TaskSchedulerException. + throw tse; + } + } + + /// <summary> + /// Adds an exception to the list of exceptions this task has thrown. + /// </summary> + /// <param name="exceptionObject">An object representing either an Exception or a collection of Exceptions.</param> + internal void AddException(object exceptionObject) + { + Contract.Requires(exceptionObject != null, "Task.AddException: Expected a non-null exception object"); + AddException(exceptionObject, representsCancellation: false); + } + + /// <summary> + /// Adds an exception to the list of exceptions this task has thrown. + /// </summary> + /// <param name="exceptionObject">An object representing either an Exception or a collection of Exceptions.</param> + /// <param name="representsCancellation">Whether the exceptionObject is an OperationCanceledException representing cancellation.</param> + internal void AddException(object exceptionObject, bool representsCancellation) + { + Contract.Requires(exceptionObject != null, "Task.AddException: Expected a non-null exception object"); + +#if DEBUG + var eoAsException = exceptionObject as Exception; + var eoAsEnumerableException = exceptionObject as IEnumerable<Exception>; + var eoAsEdi = exceptionObject as ExceptionDispatchInfo; + var eoAsEnumerableEdi = exceptionObject as IEnumerable<ExceptionDispatchInfo>; + + Contract.Assert( + eoAsException != null || eoAsEnumerableException != null || eoAsEdi != null || eoAsEnumerableEdi != null, + "Task.AddException: Expected an Exception, ExceptionDispatchInfo, or an IEnumerable<> of one of those"); + + var eoAsOce = exceptionObject as OperationCanceledException; + + Contract.Assert( + !representsCancellation || + eoAsOce != null || + (eoAsEdi != null && eoAsEdi.SourceException is OperationCanceledException), + "representsCancellation should be true only if an OCE was provided."); +#endif + + // + // WARNING: A great deal of care went into ensuring that + // AddException() and GetExceptions() are never called + // simultaneously. See comment at start of GetExceptions(). + // + + // Lazily initialize the holder, ensuring only one thread wins. + var props = EnsureContingentPropertiesInitialized(); + if (props.m_exceptionsHolder == null) + { + TaskExceptionHolder holder = new TaskExceptionHolder(this); + if (Interlocked.CompareExchange(ref props.m_exceptionsHolder, holder, null) != null) + { + // If someone else already set the value, suppress finalization. + holder.MarkAsHandled(false); + } + } + + lock (props) + { + props.m_exceptionsHolder.Add(exceptionObject, representsCancellation); + } + } + + /// <summary> + /// Returns a list of exceptions by aggregating the holder's contents. Or null if + /// no exceptions have been thrown. + /// </summary> + /// <param name="includeTaskCanceledExceptions">Whether to include a TCE if cancelled.</param> + /// <returns>An aggregate exception, or null if no exceptions have been caught.</returns> + private AggregateException GetExceptions(bool includeTaskCanceledExceptions) + { + // + // WARNING: The Task/Task<TResult>/TaskCompletionSource classes + // have all been carefully crafted to insure that GetExceptions() + // is never called while AddException() is being called. There + // are locks taken on m_contingentProperties in several places: + // + // -- Task<TResult>.TrySetException(): The lock allows the + // task to be set to Faulted state, and all exceptions to + // be recorded, in one atomic action. + // + // -- Task.Exception_get(): The lock ensures that Task<TResult>.TrySetException() + // is allowed to complete its operation before Task.Exception_get() + // can access GetExceptions(). + // + // -- Task.ThrowIfExceptional(): The lock insures that Wait() will + // not attempt to call GetExceptions() while Task<TResult>.TrySetException() + // is in the process of calling AddException(). + // + // For "regular" tasks, we effectively keep AddException() and GetException() + // from being called concurrently by the way that the state flows. Until + // a Task is marked Faulted, Task.Exception_get() returns null. And + // a Task is not marked Faulted until it and all of its children have + // completed, which means that all exceptions have been recorded. + // + // It might be a lot easier to follow all of this if we just required + // that all calls to GetExceptions() and AddExceptions() were made + // under a lock on m_contingentProperties. But that would also + // increase our lock occupancy time and the frequency with which we + // would need to take the lock. + // + // If you add a call to GetExceptions() anywhere in the code, + // please continue to maintain the invariant that it can't be + // called when AddException() is being called. + // + + // We'll lazily create a TCE if the task has been canceled. + Exception canceledException = null; + if (includeTaskCanceledExceptions && IsCanceled) + { + // Backcompat: + // Ideally we'd just use the cached OCE from this.GetCancellationExceptionDispatchInfo() + // here. However, that would result in a potentially breaking change from .NET 4, which + // has the code here that throws a new exception instead of the original, and the EDI + // may not contain a TCE, but an OCE or any OCE-derived type, which would mean we'd be + // propagating an exception of a different type. + canceledException = new TaskCanceledException(this); + } + + if (ExceptionRecorded) + { + // There are exceptions; get the aggregate and optionally add the canceled + // exception to the aggregate (if applicable). + Contract.Assert(m_contingentProperties != null); // ExceptionRecorded ==> m_contingentProperties != null + + // No need to lock around this, as other logic prevents the consumption of exceptions + // before they have been completely processed. + return m_contingentProperties.m_exceptionsHolder.CreateExceptionObject(false, canceledException); + } + else if (canceledException != null) + { + // No exceptions, but there was a cancelation. Aggregate and return it. + return new AggregateException(canceledException); + } + + return null; + } + + /// <summary>Gets the exception dispatch infos once the task has faulted.</summary> + internal ReadOnlyCollection<ExceptionDispatchInfo> GetExceptionDispatchInfos() + { + bool exceptionsAvailable = IsFaulted && ExceptionRecorded; + Contract.Assert(exceptionsAvailable, "Must only be used when the task has faulted with exceptions."); + return exceptionsAvailable ? + m_contingentProperties.m_exceptionsHolder.GetExceptionDispatchInfos() : + new ReadOnlyCollection<ExceptionDispatchInfo>(new ExceptionDispatchInfo[0]); + } + + /// <summary>Gets the ExceptionDispatchInfo containing the OperationCanceledException for this task.</summary> + /// <returns>The ExceptionDispatchInfo. May be null if no OCE was stored for the task.</returns> + internal ExceptionDispatchInfo GetCancellationExceptionDispatchInfo() + { + Contract.Assert(IsCanceled, "Must only be used when the task has canceled."); + return Volatile.Read(ref m_contingentProperties)?.m_exceptionsHolder?.GetCancellationExceptionDispatchInfo(); // may be null + } + + /// <summary> + /// Throws an aggregate exception if the task contains exceptions. + /// </summary> + internal void ThrowIfExceptional(bool includeTaskCanceledExceptions) + { + Contract.Requires(IsCompleted, "ThrowIfExceptional(): Expected IsCompleted == true"); + + Exception exception = GetExceptions(includeTaskCanceledExceptions); + if (exception != null) + { + UpdateExceptionObservedStatus(); + throw exception; + } + } + + /// <summary> + /// Checks whether this is an attached task, and whether we are being called by the parent task. + /// And sets the TASK_STATE_EXCEPTIONOBSERVEDBYPARENT status flag based on that. + /// + /// This is meant to be used internally when throwing an exception, and when WaitAll is gathering + /// exceptions for tasks it waited on. If this flag gets set, the implicit wait on children + /// will skip exceptions to prevent duplication. + /// + /// This should only be called when this task has completed with an exception + /// + /// </summary> + internal void UpdateExceptionObservedStatus() + { + Task parent = m_contingentProperties?.m_parent; + if ((parent != null) + && ((Options & TaskCreationOptions.AttachedToParent) != 0) + && ((parent.CreationOptions & TaskCreationOptions.DenyChildAttach) == 0) + && Task.InternalCurrent == parent) + { + m_stateFlags |= TASK_STATE_EXCEPTIONOBSERVEDBYPARENT; + } + } + + /// <summary> + /// Checks whether the TASK_STATE_EXCEPTIONOBSERVEDBYPARENT status flag is set, + /// This will only be used by the implicit wait to prevent double throws + /// + /// </summary> + internal bool IsExceptionObservedByParent + { + get + { + return (m_stateFlags & TASK_STATE_EXCEPTIONOBSERVEDBYPARENT) != 0; + } + } + + /// <summary> + /// Checks whether the body was ever invoked. Used by task scheduler code to verify custom schedulers actually ran the task. + /// </summary> + internal bool IsDelegateInvoked + { + get + { + return (m_stateFlags & TASK_STATE_DELEGATE_INVOKED) != 0; + } + } + + /// <summary> + /// Signals completion of this particular task. + /// + /// The bUserDelegateExecuted parameter indicates whether this Finish() call comes following the + /// full execution of the user delegate. + /// + /// If bUserDelegateExecuted is false, it mean user delegate wasn't invoked at all (either due to + /// a cancellation request, or because this task is a promise style Task). In this case, the steps + /// involving child tasks (i.e. WaitForChildren) will be skipped. + /// + /// </summary> + internal void Finish(bool bUserDelegateExecuted) + { + if (!bUserDelegateExecuted) + { + // delegate didn't execute => no children. We can safely call the remaining finish stages + FinishStageTwo(); + } + else + { + var props = Volatile.Read(ref m_contingentProperties); + + if (props == null || // no contingent properties means no children, so it's safe to complete ourselves + (props.m_completionCountdown == 1 && !IsSelfReplicatingRoot) || + // Count of 1 => either all children finished, or there were none. Safe to complete ourselves + // without paying the price of an Interlocked.Decrement. + // However we need to exclude self replicating root tasks from this optimization, because + // they can have children joining in, or finishing even after the root task delegate is done. + Interlocked.Decrement(ref props.m_completionCountdown) == 0) // Reaching this sub clause means there may be remaining active children, + // and we could be racing with one of them to call FinishStageTwo(). + // So whoever does the final Interlocked.Dec is responsible to finish. + { + FinishStageTwo(); + } + else + { + // Apparently some children still remain. It will be up to the last one to process the completion of this task on their own thread. + // We will now yield the thread back to ThreadPool. Mark our state appropriately before getting out. + + // We have to use an atomic update for this and make sure not to overwrite a final state, + // because at this very moment the last child's thread may be concurrently completing us. + // Otherwise we risk overwriting the TASK_STATE_RAN_TO_COMPLETION, _CANCELED or _FAULTED bit which may have been set by that child task. + // Note that the concurrent update by the last child happening in FinishStageTwo could still wipe out the TASK_STATE_WAITING_ON_CHILDREN flag, + // but it is not critical to maintain, therefore we dont' need to intruduce a full atomic update into FinishStageTwo + + AtomicStateUpdate(TASK_STATE_WAITING_ON_CHILDREN, TASK_STATE_FAULTED | TASK_STATE_CANCELED | TASK_STATE_RAN_TO_COMPLETION); + } + + // Now is the time to prune exceptional children. We'll walk the list and removes the ones whose exceptions we might have observed after they threw. + // we use a local variable for exceptional children here because some other thread may be nulling out m_contingentProperties.m_exceptionalChildren + List<Task> exceptionalChildren = props != null ? props.m_exceptionalChildren : null; + + if (exceptionalChildren != null) + { + lock (exceptionalChildren) + { + exceptionalChildren.RemoveAll(s_IsExceptionObservedByParentPredicate); // RemoveAll has better performance than doing it ourselves + } + } + } + } + + // statically allocated delegate for the removeall expression in Finish() + private readonly static Predicate<Task> s_IsExceptionObservedByParentPredicate = new Predicate<Task>((t) => { return t.IsExceptionObservedByParent; }); + + /// <summary> + /// FinishStageTwo is to be executed as soon as we known there are no more children to complete. + /// It can happen i) either on the thread that originally executed this task (if no children were spawned, or they all completed by the time this task's delegate quit) + /// ii) or on the thread that executed the last child. + /// </summary> + internal void FinishStageTwo() + { + AddExceptionsFromChildren(); + + // At this point, the task is done executing and waiting for its children, + // we can transition our task to a completion state. + int completionState; + if (ExceptionRecorded) + { + completionState = TASK_STATE_FAULTED; + if (AsyncCausalityTracer.LoggingOn) + AsyncCausalityTracer.TraceOperationCompletion(CausalityTraceLevel.Required, this.Id, AsyncCausalityStatus.Error); + + if (Task.s_asyncDebuggingEnabled) + { + RemoveFromActiveTasks(this.Id); + } + } + else if (IsCancellationRequested && IsCancellationAcknowledged) + { + // We transition into the TASK_STATE_CANCELED final state if the task's CT was signalled for cancellation, + // and the user delegate acknowledged the cancellation request by throwing an OCE, + // and the task hasn't otherwise transitioned into faulted state. (TASK_STATE_FAULTED trumps TASK_STATE_CANCELED) + // + // If the task threw an OCE without cancellation being requestsed (while the CT not being in signaled state), + // then we regard it as a regular exception + + completionState = TASK_STATE_CANCELED; + if (AsyncCausalityTracer.LoggingOn) + AsyncCausalityTracer.TraceOperationCompletion(CausalityTraceLevel.Required, this.Id, AsyncCausalityStatus.Canceled); + + if (Task.s_asyncDebuggingEnabled) + { + RemoveFromActiveTasks(this.Id); + } + } + else + { + completionState = TASK_STATE_RAN_TO_COMPLETION; + if (AsyncCausalityTracer.LoggingOn) + AsyncCausalityTracer.TraceOperationCompletion(CausalityTraceLevel.Required, this.Id, AsyncCausalityStatus.Completed); + + if (Task.s_asyncDebuggingEnabled) + { + RemoveFromActiveTasks(this.Id); + } + } + + // Use Interlocked.Exchange() to effect a memory fence, preventing + // any SetCompleted() (or later) instructions from sneak back before it. + Interlocked.Exchange(ref m_stateFlags, m_stateFlags | completionState); + + // Set the completion event if it's been lazy allocated. + // And if we made a cancellation registration, it's now unnecessary. + var cp = Volatile.Read(ref m_contingentProperties); + if (cp != null) + { + cp.SetCompleted(); + cp.DeregisterCancellationCallback(); + } + + // ready to run continuations and notify parent. + FinishStageThree(); + } + + + /// <summary> + /// Final stage of the task completion code path. Notifies the parent (if any) that another of its children are done, and runs continuations. + /// This function is only separated out from FinishStageTwo because these two operations are also needed to be called from CancellationCleanupLogic() + /// </summary> + internal void FinishStageThree() + { + // Release the action so that holding this task object alive doesn't also + // hold alive the body of the task. We do this before notifying a parent, + // so that if notifying the parent completes the parent and causes + // its synchronous continuations to run, the GC can collect the state + // in the interim. And we do it before finishing continuations, because + // continuations hold onto the task, and therefore are keeping it alive. + m_action = null; + + // Notify parent if this was an attached task + Task parent = m_contingentProperties?.m_parent; + if (parent != null + && ((parent.CreationOptions & TaskCreationOptions.DenyChildAttach) == 0) + && (((TaskCreationOptions)(m_stateFlags & OptionsMask)) & TaskCreationOptions.AttachedToParent) != 0) + { + parent.ProcessChildCompletion(this); + } + + // Activate continuations (if any). + FinishContinuations(); + } + + /// <summary> + /// This is called by children of this task when they are completed. + /// </summary> + internal void ProcessChildCompletion(Task childTask) + { + Contract.Requires(childTask != null); + Contract.Requires(childTask.IsCompleted, "ProcessChildCompletion was called for an uncompleted task"); + + Contract.Assert(childTask.m_contingentProperties?.m_parent == this, "ProcessChildCompletion should only be called for a child of this task"); + + var props = Volatile.Read(ref m_contingentProperties); + + // if the child threw and we haven't observed it we need to save it for future reference + if (childTask.IsFaulted && !childTask.IsExceptionObservedByParent) + { + // Lazily initialize the child exception list + if (props.m_exceptionalChildren == null) + { + Interlocked.CompareExchange(ref props.m_exceptionalChildren, new List<Task>(), null); + } + + // In rare situations involving AppDomainUnload, it's possible (though unlikely) for FinishStageTwo() to be called + // multiple times for the same task. In that case, AddExceptionsFromChildren() could be nulling m_exceptionalChildren + // out at the same time that we're processing it, resulting in a NullReferenceException here. We'll protect + // ourselves by caching m_exceptionChildren in a local variable. + List<Task> tmp = props.m_exceptionalChildren; + if (tmp != null) + { + lock (tmp) + { + tmp.Add(childTask); + } + } + + } + + if (Interlocked.Decrement(ref props.m_completionCountdown) == 0) + { + // This call came from the final child to complete, and apparently we have previously given up this task's right to complete itself. + // So we need to invoke the final finish stage. + + FinishStageTwo(); + } + } + + /// <summary> + /// This is to be called just before the task does its final state transition. + /// It traverses the list of exceptional children, and appends their aggregate exceptions into this one's exception list + /// </summary> + internal void AddExceptionsFromChildren() + { + // In rare occurences during AppDomainUnload() processing, it is possible for this method to be called + // simultaneously on the same task from two different contexts. This can result in m_exceptionalChildren + // being nulled out while it is being processed, which could lead to a NullReferenceException. To + // protect ourselves, we'll cache m_exceptionalChildren in a local variable. + var props = Volatile.Read(ref m_contingentProperties); + List<Task> exceptionalChildren = props?.m_exceptionalChildren; + + if (exceptionalChildren != null) + { + // This lock is necessary because even though AddExceptionsFromChildren is last to execute, it may still + // be racing with the code segment at the bottom of Finish() that prunes the exceptional child array. + lock (exceptionalChildren) + { + foreach (Task task in exceptionalChildren) + { + // Ensure any exceptions thrown by children are added to the parent. + // In doing this, we are implicitly marking children as being "handled". + Contract.Assert(task.IsCompleted, "Expected all tasks in list to be completed"); + if (task.IsFaulted && !task.IsExceptionObservedByParent) + { + TaskExceptionHolder exceptionHolder = Volatile.Read(ref task.m_contingentProperties).m_exceptionsHolder; + Contract.Assert(exceptionHolder != null); + + // No locking necessary since child task is finished adding exceptions + // and concurrent CreateExceptionObject() calls do not constitute + // a concurrency hazard. + AddException(exceptionHolder.CreateExceptionObject(false, null)); + } + } + } + + // Reduce memory pressure by getting rid of the array + props.m_exceptionalChildren = null; + } + } + + /// <summary> + /// Special purpose Finish() entry point to be used when the task delegate throws a ThreadAbortedException + /// This makes a note in the state flags so that we avoid any costly synchronous operations in the finish codepath + /// such as inlined continuations + /// </summary> + /// <param name="bTAEAddedToExceptionHolder"> + /// Indicates whether the ThreadAbortException was added to this task's exception holder. + /// This should always be true except for the case of non-root self replicating task copies. + /// </param> + /// <param name="delegateRan">Whether the delegate was executed.</param> + internal void FinishThreadAbortedTask(bool bTAEAddedToExceptionHolder, bool delegateRan) + { + Contract.Assert(!bTAEAddedToExceptionHolder || m_contingentProperties?.m_exceptionsHolder != null, + "FinishThreadAbortedTask() called on a task whose exception holder wasn't initialized"); + + // this will only be false for non-root self replicating task copies, because all of their exceptions go to the root task. + if (bTAEAddedToExceptionHolder) + m_contingentProperties.m_exceptionsHolder.MarkAsHandled(false); + + // If this method has already been called for this task, or if this task has already completed, then + // return before actually calling Finish(). + if (!AtomicStateUpdate(TASK_STATE_THREAD_WAS_ABORTED, + TASK_STATE_THREAD_WAS_ABORTED | TASK_STATE_RAN_TO_COMPLETION | TASK_STATE_FAULTED | TASK_STATE_CANCELED)) + { + return; + } + + Finish(delegateRan); + + } + + + /// <summary> + /// Executes the task. This method will only be called once, and handles bookeeping associated with + /// self-replicating tasks, in addition to performing necessary exception marshaling. + /// </summary> + private void Execute() + { + if (IsSelfReplicatingRoot) + { + ExecuteSelfReplicating(this); + } + else + { + try + { + InnerInvoke(); + } + catch (ThreadAbortException tae) + { + // Don't record the TAE or call FinishThreadAbortedTask for a child replica task -- + // it's already been done downstream. + if (!IsChildReplica) + { + // Record this exception in the task's exception list + HandleException(tae); + + // This is a ThreadAbortException and it will be rethrown from this catch clause, causing us to + // skip the regular Finish codepath. In order not to leave the task unfinished, we now call + // FinishThreadAbortedTask here. + FinishThreadAbortedTask(true, true); + } + } + catch (Exception exn) + { + // Record this exception in the task's exception list + HandleException(exn); + } + } + } + + // Allows (internal) deriving classes to support limited replication. + // (By default, replication is basically unlimited). + internal virtual bool ShouldReplicate() + { + return true; + } + + // Allows (internal) deriving classes to instantiate the task replica as a Task super class of their choice + // (By default, we create a regular Task instance) + internal virtual Task CreateReplicaTask(Action<object> taskReplicaDelegate, Object stateObject, Task parentTask, TaskScheduler taskScheduler, + TaskCreationOptions creationOptionsForReplica, InternalTaskOptions internalOptionsForReplica) + { + return new Task(taskReplicaDelegate, stateObject, parentTask, default(CancellationToken), + creationOptionsForReplica, internalOptionsForReplica, parentTask.ExecutingTaskScheduler); + } + + // Allows internal deriving classes to support replicas that exit prematurely and want to pass on state to the next replica + internal virtual Object SavedStateForNextReplica + { + get { return null; } + + set { /*do nothing*/ } + } + + // Allows internal deriving classes to support replicas that exit prematurely and want to pass on state to the next replica + internal virtual Object SavedStateFromPreviousReplica + { + get { return null; } + + set { /*do nothing*/ } + } + + // Allows internal deriving classes to support replicas that exit prematurely and want to hand over the child replica that they + // had queued, so that the replacement replica can work with that child task instead of queuing up yet another one + internal virtual Task HandedOverChildReplica + { + get { return null; } + + set { /* do nothing*/ } + } + + private static void ExecuteSelfReplicating(Task root) + { + TaskCreationOptions creationOptionsForReplicas = root.CreationOptions | TaskCreationOptions.AttachedToParent; + InternalTaskOptions internalOptionsForReplicas = + InternalTaskOptions.ChildReplica | // child replica flag disables self replication for the replicas themselves. + InternalTaskOptions.SelfReplicating | // we still want to identify this as part of a self replicating group + InternalTaskOptions.QueuedByRuntime; // we queue and cancel these tasks internally, so don't allow CT registration to take place + + + // Important Note: The child replicas we launch from here will be attached the root replica (by virtue of the root.CreateReplicaTask call) + // because we need the root task to receive all their exceptions, and to block until all of them return + + + // This variable is captured in a closure and shared among all replicas. + bool replicasAreQuitting = false; + + // Set up a delegate that will form the body of the root and all recursively created replicas. + Action<object> taskReplicaDelegate = null; + taskReplicaDelegate = delegate + { + Task currentTask = Task.InternalCurrent; + + + // Check if a child task has been handed over by a prematurely quiting replica that we might be a replacement for. + Task childTask = currentTask.HandedOverChildReplica; + + if (childTask == null) + { + // Apparently we are not a replacement task. This means we need to queue up a child task for replication to progress + + // Down-counts a counter in the root task. + if (!root.ShouldReplicate()) return; + + // If any of the replicas have quit, we will do so ourselves. + if (Volatile.Read(ref replicasAreQuitting)) + { + return; + } + + // Propagate a copy of the context from the root task. It may be null if flow was suppressed. + ExecutionContext creatorContext = root.CapturedContext; + + + childTask = root.CreateReplicaTask(taskReplicaDelegate, root.m_stateObject, root, root.ExecutingTaskScheduler, + creationOptionsForReplicas, internalOptionsForReplicas); + + childTask.CapturedContext = CopyExecutionContext(creatorContext); + + childTask.ScheduleAndStart(false); + } + + + + // Finally invoke the meat of the task. + // Note that we are directly calling root.InnerInvoke() even though we are currently be in the action delegate of a child replica + // This is because the actual work was passed down in that delegate, and the action delegate of the child replica simply contains this + // replication control logic. + try + { + // passing in currentTask only so that the parallel debugger can find it + root.InnerInvokeWithArg(currentTask); + } + catch (Exception exn) + { + // Record this exception in the root task's exception list + root.HandleException(exn); + + if (exn is ThreadAbortException) + { + // If this is a ThreadAbortException it will escape this catch clause, causing us to skip the regular Finish codepath + // In order not to leave the task unfinished, we now call FinishThreadAbortedTask here + currentTask.FinishThreadAbortedTask(false, true); + } + } + + Object savedState = currentTask.SavedStateForNextReplica; + + // check for premature exit + if (savedState != null) + { + // the replica decided to exit early + // we need to queue up a replacement, attach the saved state, and yield the thread right away + + Task replacementReplica = root.CreateReplicaTask(taskReplicaDelegate, root.m_stateObject, root, root.ExecutingTaskScheduler, + creationOptionsForReplicas, internalOptionsForReplicas); + + // Propagate a copy of the context from the root task to the replacement task + ExecutionContext creatorContext = root.CapturedContext; + replacementReplica.CapturedContext = CopyExecutionContext(creatorContext); + + replacementReplica.HandedOverChildReplica = childTask; + replacementReplica.SavedStateFromPreviousReplica = savedState; + + replacementReplica.ScheduleAndStart(false); + } + else + { + // The replica finished normally, which means it can't find more work to grab. + // Time to mark replicas quitting + + replicasAreQuitting = true; + + // InternalCancel() could conceivably throw in the underlying scheduler's TryDequeue() method. + // If it does, then make sure that we record it. + try + { + childTask.InternalCancel(true); + } + catch (Exception e) + { + // Apparently TryDequeue threw an exception. Before propagating that exception, InternalCancel should have + // attempted an atomic state transition and a call to CancellationCleanupLogic() on this task. So we know + // the task was properly cleaned up if it was possible. + // + // Now all we need to do is to Record the exception in the root task. + + root.HandleException(e); + } + + // No specific action needed if the child could not be canceled + // because we attached it to the root task, which should therefore be receiving any exceptions from the child, + // and root.wait will not return before this child finishes anyway. + + } + }; + + // + // Now we execute as the root task + // + taskReplicaDelegate(null); + } + + /// <summary> + /// IThreadPoolWorkItem override, which is the entry function for this task when the TP scheduler decides to run it. + /// + /// </summary> + [SecurityCritical] + void IThreadPoolWorkItem.ExecuteWorkItem() + { + ExecuteEntry(false); + } + + /// <summary> + /// The ThreadPool calls this if a ThreadAbortException is thrown while trying to execute this workitem. This may occur + /// before Task would otherwise be able to observe it. + /// </summary> + [SecurityCritical] + void IThreadPoolWorkItem.MarkAborted(ThreadAbortException tae) + { + // If the task has marked itself as Completed, then it either a) already observed this exception (so we shouldn't handle it here) + // or b) completed before the exception ocurred (in which case it shouldn't count against this Task). + if (!IsCompleted) + { + HandleException(tae); + FinishThreadAbortedTask(true, false); + } + } + + /// <summary> + /// Outermost entry function to execute this task. Handles all aspects of executing a task on the caller thread. + /// Currently this is called by IThreadPoolWorkItem.ExecuteWorkItem(), and TaskManager.TryExecuteInline. + /// + /// </summary> + /// <param name="bPreventDoubleExecution"> Performs atomic updates to prevent double execution. Should only be set to true + /// in codepaths servicing user provided TaskSchedulers. The ConcRT or ThreadPool schedulers don't need this. </param> + [SecuritySafeCritical] + internal bool ExecuteEntry(bool bPreventDoubleExecution) + { + if (bPreventDoubleExecution || ((Options & (TaskCreationOptions)InternalTaskOptions.SelfReplicating) != 0)) + { + int previousState = 0; + + // Do atomic state transition from queued to invoked. If we observe a task that's already invoked, + // we will return false so that TaskScheduler.ExecuteTask can throw an exception back to the custom scheduler. + // However we don't want this exception to be throw if the task was already canceled, because it's a + // legitimate scenario for custom schedulers to dequeue a task and mark it as canceled (example: throttling scheduler) + if (!AtomicStateUpdate(TASK_STATE_DELEGATE_INVOKED, + TASK_STATE_DELEGATE_INVOKED | TASK_STATE_COMPLETED_MASK, + ref previousState) && (previousState & TASK_STATE_CANCELED) == 0) + { + // This task has already been invoked. Don't invoke it again. + return false; + } + } + else + { + // Remember that we started running the task delegate. + m_stateFlags |= TASK_STATE_DELEGATE_INVOKED; + } + + if (!IsCancellationRequested && !IsCanceled) + { + ExecuteWithThreadLocal(ref t_currentTask); + } + else if (!IsCanceled) + { + int prevState = Interlocked.Exchange(ref m_stateFlags, m_stateFlags | TASK_STATE_CANCELED); + if ((prevState & TASK_STATE_CANCELED) == 0) + { + CancellationCleanupLogic(); + } + } + + return true; + } + + // A trick so we can refer to the TLS slot with a byref. + [SecurityCritical] + private void ExecuteWithThreadLocal(ref Task currentTaskSlot) + { + // Remember the current task so we can restore it after running, and then + Task previousTask = currentTaskSlot; + + // ETW event for Task Started + var etwLog = TplEtwProvider.Log; + Guid savedActivityID = new Guid(); + bool etwIsEnabled = etwLog.IsEnabled(); + if (etwIsEnabled) + { + if (etwLog.TasksSetActivityIds) + EventSource.SetCurrentThreadActivityId(TplEtwProvider.CreateGuidForTaskID(this.Id), out savedActivityID); + // previousTask holds the actual "current task" we want to report in the event + if (previousTask != null) + etwLog.TaskStarted(previousTask.m_taskScheduler.Id, previousTask.Id, this.Id); + else + etwLog.TaskStarted(TaskScheduler.Current.Id, 0, this.Id); + } + + if (AsyncCausalityTracer.LoggingOn) + AsyncCausalityTracer.TraceSynchronousWorkStart(CausalityTraceLevel.Required, this.Id, CausalitySynchronousWork.Execution); + + + try + { + // place the current task into TLS. + currentTaskSlot = this; + + ExecutionContext ec = CapturedContext; + if (ec == null) + { + // No context, just run the task directly. + Execute(); + } + else + { + if (IsSelfReplicatingRoot || IsChildReplica) + { + CapturedContext = CopyExecutionContext(ec); + } + + // Run the task. We need a simple shim that converts the + // object back into a Task object, so that we can Execute it. + + // Lazily initialize the callback delegate; benign race condition + var callback = s_ecCallback; + if (callback == null) s_ecCallback = callback = new ContextCallback(ExecutionContextCallback); + ExecutionContext.Run(ec, callback, this, true); + } + + if (AsyncCausalityTracer.LoggingOn) + AsyncCausalityTracer.TraceSynchronousWorkCompletion(CausalityTraceLevel.Required, CausalitySynchronousWork.Execution); + + Finish(true); + } + finally + { + currentTaskSlot = previousTask; + + // ETW event for Task Completed + if (etwIsEnabled) + { + // previousTask holds the actual "current task" we want to report in the event + if (previousTask != null) + etwLog.TaskCompleted(previousTask.m_taskScheduler.Id, previousTask.Id, this.Id, IsFaulted); + else + etwLog.TaskCompleted(TaskScheduler.Current.Id, 0, this.Id, IsFaulted); + + if (etwLog.TasksSetActivityIds) + EventSource.SetCurrentThreadActivityId(savedActivityID); + } + } + } + + // Cached callback delegate that's lazily initialized due to ContextCallback being SecurityCritical + [SecurityCritical] + private static ContextCallback s_ecCallback; + + [SecurityCritical] + private static void ExecutionContextCallback(object obj) + { + Task task = obj as Task; + Contract.Assert(task != null, "expected a task object"); + task.Execute(); + } + + + /// <summary> + /// The actual code which invokes the body of the task. This can be overriden in derived types. + /// </summary> + internal virtual void InnerInvoke() + { + // Invoke the delegate + Contract.Assert(m_action != null, "Null action in InnerInvoke()"); + var action = m_action as Action; + if (action != null) + { + action(); + return; + } + var actionWithState = m_action as Action<object>; + if (actionWithState != null) + { + actionWithState(m_stateObject); + return; + } + Contract.Assert(false, "Invalid m_action in Task"); + } + + /// <summary> + /// Alternate InnerInvoke prototype to be called from ExecuteSelfReplicating() so that + /// the Parallel Debugger can discover the actual task being invoked. + /// Details: Here, InnerInvoke is actually being called on the rootTask object while we are actually executing the + /// childTask. And the debugger needs to discover the childTask, so we pass that down as an argument. + /// The NoOptimization and NoInlining flags ensure that the childTask pointer is retained, and that this + /// function appears on the callstack. + /// </summary> + /// <param name="childTask"></param> + [MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)] + internal void InnerInvokeWithArg(Task childTask) + { + InnerInvoke(); + } + + /// <summary> + /// Performs whatever handling is necessary for an unhandled exception. Normally + /// this just entails adding the exception to the holder object. + /// </summary> + /// <param name="unhandledException">The exception that went unhandled.</param> + private void HandleException(Exception unhandledException) + { + Contract.Requires(unhandledException != null); + + OperationCanceledException exceptionAsOce = unhandledException as OperationCanceledException; + if (exceptionAsOce != null && IsCancellationRequested && + m_contingentProperties.m_cancellationToken == exceptionAsOce.CancellationToken) + { + // All conditions are satisfied for us to go into canceled state in Finish(). + // Mark the acknowledgement. The exception is also stored to enable it to be + // the exception propagated from an await. + + SetCancellationAcknowledged(); + AddException(exceptionAsOce, representsCancellation: true); + } + else + { + // Other exceptions, including any OCE from the task that doesn't match the tasks' own CT, + // or that gets thrown without the CT being set will be treated as an ordinary exception + // and added to the aggregate. + + AddException(unhandledException); + } + } + + #region Await Support + /// <summary>Gets an awaiter used to await this <see cref="System.Threading.Tasks.Task"/>.</summary> + /// <returns>An awaiter instance.</returns> + /// <remarks>This method is intended for compiler user rather than use directly in code.</remarks> + public TaskAwaiter GetAwaiter() + { + return new TaskAwaiter(this); + } + + /// <summary>Configures an awaiter used to await this <see cref="System.Threading.Tasks.Task"/>.</summary> + /// <param name="continueOnCapturedContext"> + /// true to attempt to marshal the continuation back to the original context captured; otherwise, false. + /// </param> + /// <returns>An object used to await this task.</returns> + public ConfiguredTaskAwaitable ConfigureAwait(bool continueOnCapturedContext) + { + return new ConfiguredTaskAwaitable(this, continueOnCapturedContext); + } + + /// <summary> + /// Sets a continuation onto the <see cref="System.Threading.Tasks.Task"/>. + /// The continuation is scheduled to run in the current synchronization context is one exists, + /// otherwise in the current task scheduler. + /// </summary> + /// <param name="continuationAction">The action to invoke when the <see cref="System.Threading.Tasks.Task"/> has completed.</param> + /// <param name="continueOnCapturedContext"> + /// true to attempt to marshal the continuation back to the original context captured; otherwise, false. + /// </param> + /// <param name="flowExecutionContext">Whether to flow ExecutionContext across the await.</param> + /// <param name="stackMark">A stack crawl mark tied to execution context.</param> + /// <exception cref="System.InvalidOperationException">The awaiter was not properly initialized.</exception> + [SecurityCritical] + internal void SetContinuationForAwait( + Action continuationAction, bool continueOnCapturedContext, bool flowExecutionContext, ref StackCrawlMark stackMark) + { + Contract.Requires(continuationAction != null); + + // Create the best AwaitTaskContinuation object given the request. + // If this remains null by the end of the function, we can use the + // continuationAction directly without wrapping it. + TaskContinuation tc = null; + + // If the user wants the continuation to run on the current "context" if there is one... + if (continueOnCapturedContext) + { + // First try getting the current synchronization context. + // If the current context is really just the base SynchronizationContext type, + // which is intended to be equivalent to not having a current SynchronizationContext at all, + // then ignore it. This helps with performance by avoiding unnecessary posts and queueing + // of work items, but more so it ensures that if code happens to publish the default context + // as current, it won't prevent usage of a current task scheduler if there is one. + var syncCtx = SynchronizationContext.CurrentNoFlow; + if (syncCtx != null && syncCtx.GetType() != typeof(SynchronizationContext)) + { + tc = new SynchronizationContextAwaitTaskContinuation(syncCtx, continuationAction, flowExecutionContext, ref stackMark); + } + else + { + // If there was no SynchronizationContext, then try for the current scheduler. + // We only care about it if it's not the default. + var scheduler = TaskScheduler.InternalCurrent; + if (scheduler != null && scheduler != TaskScheduler.Default) + { + tc = new TaskSchedulerAwaitTaskContinuation(scheduler, continuationAction, flowExecutionContext, ref stackMark); + } + } + } + + if (tc == null && flowExecutionContext) + { + // We're targeting the default scheduler, so we can use the faster path + // that assumes the default, and thus we don't need to store it. If we're flowing + // ExecutionContext, we need to capture it and wrap it in an AwaitTaskContinuation. + // Otherwise, we're targeting the default scheduler and we don't need to flow ExecutionContext, so + // we don't actually need a continuation object. We can just store/queue the action itself. + tc = new AwaitTaskContinuation(continuationAction, flowExecutionContext: true, stackMark: ref stackMark); + } + + // Now register the continuation, and if we couldn't register it because the task is already completing, + // process the continuation directly (in which case make sure we schedule the continuation + // rather than inlining it, the latter of which could result in a rare but possible stack overflow). + if (tc != null) + { + if (!AddTaskContinuation(tc, addBeforeOthers: false)) + tc.Run(this, bCanInlineContinuationTask: false); + } + else + { + Contract.Assert(!flowExecutionContext, "We already determined we're not required to flow context."); + if (!AddTaskContinuation(continuationAction, addBeforeOthers: false)) + AwaitTaskContinuation.UnsafeScheduleAction(continuationAction, this); + } + } + + /// <summary>Creates an awaitable that asynchronously yields back to the current context when awaited.</summary> + /// <returns> + /// A context that, when awaited, will asynchronously transition back into the current context at the + /// time of the await. If the current SynchronizationContext is non-null, that is treated as the current context. + /// Otherwise, TaskScheduler.Current is treated as the current context. + /// </returns> + public static YieldAwaitable Yield() + { + return new YieldAwaitable(); + } + #endregion + + /// <summary> + /// Waits for the <see cref="Task"/> to complete execution. + /// </summary> + /// <exception cref="T:System.AggregateException"> + /// The <see cref="Task"/> was canceled -or- an exception was thrown during + /// the execution of the <see cref="Task"/>. + /// </exception> + public void Wait() + { +#if DEBUG + bool waitResult = +#endif + Wait(Timeout.Infinite, default(CancellationToken)); + +#if DEBUG + Contract.Assert(waitResult, "expected wait to succeed"); +#endif + } + + /// <summary> + /// Waits for the <see cref="Task"/> to complete execution. + /// </summary> + /// <param name="timeout"> + /// A <see cref="System.TimeSpan"/> that represents the number of milliseconds to wait, or a <see + /// cref="System.TimeSpan"/> that represents -1 milliseconds to wait indefinitely. + /// </param> + /// <returns> + /// true if the <see cref="Task"/> completed execution within the allotted time; otherwise, false. + /// </returns> + /// <exception cref="T:System.AggregateException"> + /// The <see cref="Task"/> was canceled -or- an exception was thrown during the execution of the <see + /// cref="Task"/>. + /// </exception> + /// <exception cref="T:System.ArgumentOutOfRangeException"> + /// <paramref name="timeout"/> is a negative number other than -1 milliseconds, which represents an + /// infinite time-out -or- timeout is greater than + /// <see cref="System.Int32.MaxValue"/>. + /// </exception> + public bool Wait(TimeSpan timeout) + { + long totalMilliseconds = (long)timeout.TotalMilliseconds; + if (totalMilliseconds < -1 || totalMilliseconds > Int32.MaxValue) + { + ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.timeout); + } + + return Wait((int)totalMilliseconds, default(CancellationToken)); + } + + + /// <summary> + /// Waits for the <see cref="Task"/> to complete execution. + /// </summary> + /// <param name="cancellationToken"> + /// A <see cref="CancellationToken"/> to observe while waiting for the task to complete. + /// </param> + /// <exception cref="T:System.OperationCanceledException"> + /// The <paramref name="cancellationToken"/> was canceled. + /// </exception> + /// <exception cref="T:System.AggregateException"> + /// The <see cref="Task"/> was canceled -or- an exception was thrown during the execution of the <see + /// cref="Task"/>. + /// </exception> + public void Wait(CancellationToken cancellationToken) + { + Wait(Timeout.Infinite, cancellationToken); + } + + + /// <summary> + /// Waits for the <see cref="Task"/> to complete execution. + /// </summary> + /// <param name="millisecondsTimeout"> + /// The number of milliseconds to wait, or <see cref="System.Threading.Timeout.Infinite"/> (-1) to + /// wait indefinitely.</param> + /// <returns>true if the <see cref="Task"/> completed execution within the allotted time; otherwise, + /// false. + /// </returns> + /// <exception cref="T:System.ArgumentOutOfRangeException"> + /// <paramref name="millisecondsTimeout"/> is a negative number other than -1, which represents an + /// infinite time-out. + /// </exception> + /// <exception cref="T:System.AggregateException"> + /// The <see cref="Task"/> was canceled -or- an exception was thrown during the execution of the <see + /// cref="Task"/>. + /// </exception> + public bool Wait(int millisecondsTimeout) + { + return Wait(millisecondsTimeout, default(CancellationToken)); + } + + + /// <summary> + /// Waits for the <see cref="Task"/> to complete execution. + /// </summary> + /// <param name="millisecondsTimeout"> + /// The number of milliseconds to wait, or <see cref="System.Threading.Timeout.Infinite"/> (-1) to + /// wait indefinitely. + /// </param> + /// <param name="cancellationToken"> + /// A <see cref="CancellationToken"/> to observe while waiting for the task to complete. + /// </param> + /// <returns> + /// true if the <see cref="Task"/> completed execution within the allotted time; otherwise, false. + /// </returns> + /// <exception cref="T:System.AggregateException"> + /// The <see cref="Task"/> was canceled -or- an exception was thrown during the execution of the <see + /// cref="Task"/>. + /// </exception> + /// <exception cref="T:System.ArgumentOutOfRangeException"> + /// <paramref name="millisecondsTimeout"/> is a negative number other than -1, which represents an + /// infinite time-out. + /// </exception> + /// <exception cref="T:System.OperationCanceledException"> + /// The <paramref name="cancellationToken"/> was canceled. + /// </exception> + public bool Wait(int millisecondsTimeout, CancellationToken cancellationToken) + { + if (millisecondsTimeout < -1) + { + ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.millisecondsTimeout); + } + Contract.EndContractBlock(); + + // Return immediately if we know that we've completed "clean" -- no exceptions, no cancellations + // and if no notification to the debugger is required + if (!IsWaitNotificationEnabledOrNotRanToCompletion) // (!DebuggerBitSet && RanToCompletion) + return true; + + // Wait, and then return if we're still not done. + if (!InternalWait(millisecondsTimeout, cancellationToken)) + return false; + + if (IsWaitNotificationEnabledOrNotRanToCompletion) // avoid a few unnecessary volatile reads if we completed successfully + { + // Notify the debugger of the wait completion if it's requested such a notification + NotifyDebuggerOfWaitCompletionIfNecessary(); + + // If cancellation was requested and the task was canceled, throw an + // OperationCanceledException. This is prioritized ahead of the ThrowIfExceptional + // call to bring more determinism to cases where the same token is used to + // cancel the Wait and to cancel the Task. Otherwise, there's a race condition between + // whether the Wait or the Task observes the cancellation request first, + // and different exceptions result from the different cases. + if (IsCanceled) cancellationToken.ThrowIfCancellationRequested(); + + // If an exception occurred, or the task was cancelled, throw an exception. + ThrowIfExceptional(true); + } + + Contract.Assert((m_stateFlags & TASK_STATE_FAULTED) == 0, "Task.Wait() completing when in Faulted state."); + + return true; + } + + // Convenience method that wraps any scheduler exception in a TaskSchedulerException + // and rethrows it. + private bool WrappedTryRunInline() + { + if (m_taskScheduler == null) + return false; + + try + { + return m_taskScheduler.TryRunInline(this, true); + } + catch (Exception e) + { + // we 1) either received an unexpected exception originating from a custom scheduler, which needs to be wrapped in a TSE and thrown + // 2) or a a ThreadAbortException, which we need to skip here, because it would already have been handled in Task.Execute + if (!(e is ThreadAbortException)) + { + TaskSchedulerException tse = new TaskSchedulerException(e); + throw tse; + } + else + { + throw; + } + } + } + + /// <summary> + /// The core wait function, which is only accesible internally. It's meant to be used in places in TPL code where + /// the current context is known or cached. + /// </summary> + [MethodImpl(MethodImplOptions.NoOptimization)] // this is needed for the parallel debugger + internal bool InternalWait(int millisecondsTimeout, CancellationToken cancellationToken) + { + // ETW event for Task Wait Begin + var etwLog = TplEtwProvider.Log; + bool etwIsEnabled = etwLog.IsEnabled(); + if (etwIsEnabled) + { + Task currentTask = Task.InternalCurrent; + etwLog.TaskWaitBegin( + (currentTask != null ? currentTask.m_taskScheduler.Id : TaskScheduler.Default.Id), (currentTask != null ? currentTask.Id : 0), + this.Id, TplEtwProvider.TaskWaitBehavior.Synchronous, 0, System.Threading.Thread.GetDomainID()); + } + + bool returnValue = IsCompleted; + + // If the event hasn't already been set, we will wait. + if (!returnValue) + { + // Alert a listening debugger that we can't make forward progress unless it slips threads. + // We call NOCTD for two reasons: + // 1. If the task runs on another thread, then we'll be blocked here indefinitely. + // 2. If the task runs inline but takes some time to complete, it will suffer ThreadAbort with possible state corruption, + // and it is best to prevent this unless the user explicitly asks to view the value with thread-slipping enabled. + Debugger.NotifyOfCrossThreadDependency(); + + // We will attempt inline execution only if an infinite wait was requested + // Inline execution doesn't make sense for finite timeouts and if a cancellation token was specified + // because we don't know how long the task delegate will take. + if (millisecondsTimeout == Timeout.Infinite && !cancellationToken.CanBeCanceled && + WrappedTryRunInline() && IsCompleted) // TryRunInline doesn't guarantee completion, as there may be unfinished children. + { + returnValue = true; + } + else + { + returnValue = SpinThenBlockingWait(millisecondsTimeout, cancellationToken); + } + } + + Contract.Assert(IsCompleted || millisecondsTimeout != Timeout.Infinite); + + // ETW event for Task Wait End + if (etwIsEnabled) + { + Task currentTask = Task.InternalCurrent; + if (currentTask != null) + { + etwLog.TaskWaitEnd(currentTask.m_taskScheduler.Id, currentTask.Id, this.Id); + } + else + { + etwLog.TaskWaitEnd(TaskScheduler.Default.Id, 0, this.Id); + } + // logically the continuation is empty so we immediately fire + etwLog.TaskWaitContinuationComplete(this.Id); + } + + return returnValue; + } + + // An MRES that gets set when Invoke is called. This replaces old logic that looked like this: + // ManualResetEventSlim mres = new ManualResetEventSlim(false, 0); + // Action<Task> completionAction = delegate {mres.Set();} + // AddCompletionAction(completionAction); + // with this: + // SetOnInvokeMres mres = new SetOnInvokeMres(); + // AddCompletionAction(mres, addBeforeOthers: true); + // which saves a couple of allocations. + // + // Used in SpinThenBlockingWait (below), but could be seen as a general purpose mechanism. + private sealed class SetOnInvokeMres : ManualResetEventSlim, ITaskCompletionAction + { + internal SetOnInvokeMres() : base(false, 0) { } + public void Invoke(Task completingTask) { Set(); } + public bool InvokeMayRunArbitraryCode { get { return false; } } + } + + /// <summary> + /// Waits for the task to complete, for a timeout to occur, or for cancellation to be requested. + /// The method first spins and then falls back to blocking on a new event. + /// </summary> + /// <param name="millisecondsTimeout">The timeout.</param> + /// <param name="cancellationToken">The token.</param> + /// <returns>true if the task is completed; otherwise, false.</returns> + private bool SpinThenBlockingWait(int millisecondsTimeout, CancellationToken cancellationToken) + { + bool infiniteWait = millisecondsTimeout == Timeout.Infinite; + uint startTimeTicks = infiniteWait ? 0 : (uint)Environment.TickCount; + bool returnValue = SpinWait(millisecondsTimeout); + if (!returnValue) + { + var mres = new SetOnInvokeMres(); + try + { + AddCompletionAction(mres, addBeforeOthers: true); + if (infiniteWait) + { + returnValue = mres.Wait(Timeout.Infinite, cancellationToken); + } + else + { + uint elapsedTimeTicks = ((uint)Environment.TickCount) - startTimeTicks; + if (elapsedTimeTicks < millisecondsTimeout) + { + returnValue = mres.Wait((int)(millisecondsTimeout - elapsedTimeTicks), cancellationToken); + } + } + } + finally + { + if (!IsCompleted) RemoveContinuation(mres); + // Don't Dispose of the MRES, because the continuation off of this task may + // still be running. This is ok, however, as we never access the MRES' WaitHandle, + // and thus no finalizable resources are actually allocated. + } + } + return returnValue; + } + + /// <summary> + /// Spins briefly while checking IsCompleted + /// </summary> + /// <param name="millisecondsTimeout">The timeout.</param> + /// <returns>true if the task is completed; otherwise, false.</returns> + /// <exception cref="System.OperationCanceledException">The wait was canceled.</exception> + private bool SpinWait(int millisecondsTimeout) + { + if (IsCompleted) return true; + + if (millisecondsTimeout == 0) + { + // For 0-timeouts, we just return immediately. + return false; + } + + //This code is pretty similar to the custom spinning in MRES except there is no yieling after we exceed the spin count + int spinCount = PlatformHelper.IsSingleProcessor ? 1 : System.Threading.SpinWait.YIELD_THRESHOLD; //spin only once if we are running on a single CPU + for (int i = 0; i < spinCount; i++) + { + if (IsCompleted) + { + return true; + } + + if (i == spinCount / 2) + { + Thread.Yield(); + } + else + { + Thread.SpinWait(PlatformHelper.ProcessorCount * (4 << i)); + } + + } + + return IsCompleted; + } + + /// <summary> + /// Cancels the <see cref="Task"/>. + /// </summary> + /// <param name="bCancelNonExecutingOnly"> + /// Indicates whether we should only cancel non-invoked tasks. + /// For the default scheduler this option will only be serviced through TryDequeue. + /// For custom schedulers we also attempt an atomic state transition. + /// </param> + /// <returns>true if the task was successfully canceled; otherwise, false.</returns> + [SecuritySafeCritical] + internal bool InternalCancel(bool bCancelNonExecutingOnly) + { + Contract.Requires((Options & (TaskCreationOptions)InternalTaskOptions.PromiseTask) == 0, "Task.InternalCancel() did not expect promise-style task"); + + bool bPopSucceeded = false; + bool mustCleanup = false; + + TaskSchedulerException tse = null; + + // If started, and running in a task context, we can try to pop the chore. + if ((m_stateFlags & TASK_STATE_STARTED) != 0) + { + TaskScheduler ts = m_taskScheduler; + + try + { + bPopSucceeded = (ts != null) && ts.TryDequeue(this); + } + catch (Exception e) + { + // TryDequeue threw. We don't know whether the task was properly dequeued or not. So we must let the rest of + // the cancellation logic run its course (record the request, attempt atomic state transition and do cleanup where appropriate) + // Here we will only record a TaskSchedulerException, which will later be thrown at function exit. + + if (!(e is ThreadAbortException)) + { + tse = new TaskSchedulerException(e); + } + } + + bool bRequiresAtomicStartTransition = (ts != null && ts.RequiresAtomicStartTransition) || ((Options & (TaskCreationOptions)InternalTaskOptions.SelfReplicating) != 0); + + if (!bPopSucceeded && bCancelNonExecutingOnly && bRequiresAtomicStartTransition) + { + // The caller requested cancellation of non-invoked tasks only, and TryDequeue was one way of doing it... + // Since that seems to have failed, we should now try an atomic state transition (from non-invoked state to canceled) + // An atomic transition here is only safe if we know we're on a custom task scheduler, which also forces a CAS on ExecuteEntry + + // Even though this task can't have any children, we should be ready for handling any continuations that + // may be attached to it (although currently + // So we need to remeber whether we actually did the flip, so we can do clean up (finish continuations etc) + mustCleanup = AtomicStateUpdate(TASK_STATE_CANCELED, TASK_STATE_DELEGATE_INVOKED | TASK_STATE_CANCELED); + + + // PS: This is slightly different from the regular cancellation codepath + // since we record the cancellation request *after* doing the state transition. + // However that shouldn't matter too much because the task was never invoked, thus can't have children + } + + } + + if (!bCancelNonExecutingOnly || bPopSucceeded || mustCleanup) + { + // Record the cancellation request. + RecordInternalCancellationRequest(); + + // Determine whether we need to clean up + // This will be the case + // 1) if we were able to pop, and we are able to update task state to TASK_STATE_CANCELED + // 2) if the task seems to be yet unstarted, and we can transition to + // TASK_STATE_CANCELED before anyone else can transition into _STARTED or _CANCELED or + // _RAN_TO_COMPLETION or _FAULTED + // Note that we do not check for TASK_STATE_COMPLETION_RESERVED. That only applies to promise-style + // tasks, and a promise-style task should not enter into this codepath. + if (bPopSucceeded) + { + // hitting this would mean something wrong with the AtomicStateUpdate above + Contract.Assert(!mustCleanup, "Possibly an invalid state transition call was made in InternalCancel()"); + + // Include TASK_STATE_DELEGATE_INVOKED in "illegal" bits to protect against the situation where + // TS.TryDequeue() returns true but the task is still left on the queue. + mustCleanup = AtomicStateUpdate(TASK_STATE_CANCELED, TASK_STATE_CANCELED | TASK_STATE_DELEGATE_INVOKED); + } + else if (!mustCleanup && (m_stateFlags & TASK_STATE_STARTED) == 0) + { + mustCleanup = AtomicStateUpdate(TASK_STATE_CANCELED, + TASK_STATE_CANCELED | TASK_STATE_STARTED | TASK_STATE_RAN_TO_COMPLETION | + TASK_STATE_FAULTED | TASK_STATE_DELEGATE_INVOKED); + } + + // do the cleanup (i.e. set completion event and finish continuations) + if (mustCleanup) + { + CancellationCleanupLogic(); + } + } + + if (tse != null) + throw tse; + else + return (mustCleanup); + } + + // Breaks out logic for recording a cancellation request + internal void RecordInternalCancellationRequest() + { + // Record the cancellation request. + EnsureContingentPropertiesInitialized().m_internalCancellationRequested = CANCELLATION_REQUESTED; + } + + // Breaks out logic for recording a cancellation request + // This overload should only be used for promise tasks where no cancellation token + // was supplied when the task was created. + internal void RecordInternalCancellationRequest(CancellationToken tokenToRecord) + { + RecordInternalCancellationRequest(); + + Contract.Assert((Options & (TaskCreationOptions)InternalTaskOptions.PromiseTask) != 0, "Task.RecordInternalCancellationRequest(CancellationToken) only valid for promise-style task"); + Contract.Assert(m_contingentProperties.m_cancellationToken == default(CancellationToken)); + + // Store the supplied cancellation token as this task's token. + // Waiting on this task will then result in an OperationCanceledException containing this token. + if (tokenToRecord != default(CancellationToken)) + { + m_contingentProperties.m_cancellationToken = tokenToRecord; + } + } + + // Breaks out logic for recording a cancellation request + // This overload should only be used for promise tasks where no cancellation token + // was supplied when the task was created. + internal void RecordInternalCancellationRequest(CancellationToken tokenToRecord, object cancellationException) + { + RecordInternalCancellationRequest(tokenToRecord); + + // Store the supplied cancellation exception + if (cancellationException != null) + { +#if DEBUG + var oce = cancellationException as OperationCanceledException; + if (oce == null) + { + var edi = cancellationException as ExceptionDispatchInfo; + Contract.Assert(edi != null, "Expected either an OCE or an EDI"); + oce = edi.SourceException as OperationCanceledException; + Contract.Assert(oce != null, "Expected EDI to contain an OCE"); + } + Contract.Assert(oce.CancellationToken == tokenToRecord, + "Expected OCE's token to match the provided token."); +#endif + AddException(cancellationException, representsCancellation: true); + } + } + + // ASSUMES THAT A SUCCESSFUL CANCELLATION HAS JUST OCCURRED ON THIS TASK!!! + // And this method should be called at most once per task. + internal void CancellationCleanupLogic() + { + Contract.Assert((m_stateFlags & (TASK_STATE_CANCELED | TASK_STATE_COMPLETION_RESERVED)) != 0, "Task.CancellationCleanupLogic(): Task not canceled or reserved."); + // I'd like to do this, but there is a small window for a race condition. If someone calls Wait() between InternalCancel() and + // here, that will set m_completionEvent, leading to a meaningless/harmless assertion. + //Contract.Assert((m_completionEvent == null) || !m_completionEvent.IsSet, "Task.CancellationCleanupLogic(): Completion event already set."); + + // This may have been set already, but we need to make sure. + Interlocked.Exchange(ref m_stateFlags, m_stateFlags | TASK_STATE_CANCELED); + + // Fire completion event if it has been lazily initialized + var cp = Volatile.Read(ref m_contingentProperties); + if (cp != null) + { + cp.SetCompleted(); + cp.DeregisterCancellationCallback(); + } + + if (AsyncCausalityTracer.LoggingOn) + AsyncCausalityTracer.TraceOperationCompletion(CausalityTraceLevel.Required, this.Id, AsyncCausalityStatus.Canceled); + + if (Task.s_asyncDebuggingEnabled) + { + RemoveFromActiveTasks(this.Id); + } + + // Notify parents, fire continuations, other cleanup. + FinishStageThree(); + } + + + /// <summary> + /// Sets the task's cancellation acknowledged flag. + /// </summary> + private void SetCancellationAcknowledged() + { + Contract.Assert(this == Task.InternalCurrent, "SetCancellationAcknowledged() should only be called while this is still the current task"); + Contract.Assert(IsCancellationRequested, "SetCancellationAcknowledged() should not be called if the task's CT wasn't signaled"); + + m_stateFlags |= TASK_STATE_CANCELLATIONACKNOWLEDGED; + } + + + // + // Continuation passing functionality (aka ContinueWith) + // + + + + + /// <summary> + /// Runs all of the continuations, as appropriate. + /// </summary> + [SecuritySafeCritical] // for AwaitTaskContinuation.RunOrScheduleAction + internal void FinishContinuations() + { + // Atomically store the fact that this task is completing. From this point on, the adding of continuations will + // result in the continuations being run/launched directly rather than being added to the continuation list. + object continuationObject = Interlocked.Exchange(ref m_continuationObject, s_taskCompletionSentinel); + TplEtwProvider etw = TplEtwProvider.Log; + bool tplEtwProviderLoggingEnabled = etw.IsEnabled(); + if (tplEtwProviderLoggingEnabled) + { + etw.RunningContinuation(Id, continuationObject); + } + + // If continuationObject == null, then we don't have any continuations to process + if (continuationObject != null) + { + + if (AsyncCausalityTracer.LoggingOn) + AsyncCausalityTracer.TraceSynchronousWorkStart(CausalityTraceLevel.Required, this.Id, CausalitySynchronousWork.CompletionNotification); + + // Skip synchronous execution of continuations if this task's thread was aborted + bool bCanInlineContinuations = !(((m_stateFlags & TASK_STATE_THREAD_WAS_ABORTED) != 0) || + (Thread.CurrentThread.ThreadState == ThreadState.AbortRequested) || + ((m_stateFlags & (int)TaskCreationOptions.RunContinuationsAsynchronously) != 0)); + + // Handle the single-Action case + Action singleAction = continuationObject as Action; + if (singleAction != null) + { + AwaitTaskContinuation.RunOrScheduleAction(singleAction, bCanInlineContinuations, ref t_currentTask); + LogFinishCompletionNotification(); + return; + } + + // Handle the single-ITaskCompletionAction case + ITaskCompletionAction singleTaskCompletionAction = continuationObject as ITaskCompletionAction; + if (singleTaskCompletionAction != null) + { + if (bCanInlineContinuations || !singleTaskCompletionAction.InvokeMayRunArbitraryCode) + { + singleTaskCompletionAction.Invoke(this); + } + else + { + ThreadPool.UnsafeQueueCustomWorkItem(new CompletionActionInvoker(singleTaskCompletionAction, this), forceGlobal: false); + } + LogFinishCompletionNotification(); + return; + } + + // Handle the single-TaskContinuation case + TaskContinuation singleTaskContinuation = continuationObject as TaskContinuation; + if (singleTaskContinuation != null) + { + singleTaskContinuation.Run(this, bCanInlineContinuations); + LogFinishCompletionNotification(); + return; + } + + // Not a single; attempt to cast as list + List<object> continuations = continuationObject as List<object>; + + if (continuations == null) + { + LogFinishCompletionNotification(); + return; // Not a single or a list; just return + } + + // + // Begin processing of continuation list + // + + // Wait for any concurrent adds or removes to be retired + lock (continuations) { } + int continuationCount = continuations.Count; + + // Fire the asynchronous continuations first ... + for (int i = 0; i < continuationCount; i++) + { + // Synchronous continuation tasks will have the ExecuteSynchronously option, + // and we're looking for asynchronous tasks... + var tc = continuations[i] as StandardTaskContinuation; + if (tc != null && (tc.m_options & TaskContinuationOptions.ExecuteSynchronously) == 0) + { + if (tplEtwProviderLoggingEnabled) + { + etw.RunningContinuationList(Id, i, tc); + } + continuations[i] = null; // so that we can skip this later + tc.Run(this, bCanInlineContinuations); + } + } + + // ... and then fire the synchronous continuations (if there are any). + // This includes ITaskCompletionAction, AwaitTaskContinuations, and + // Action delegates, which are all by default implicitly synchronous. + for (int i = 0; i < continuationCount; i++) + { + object currentContinuation = continuations[i]; + if (currentContinuation == null) continue; + continuations[i] = null; // to enable free'ing up memory earlier + if (tplEtwProviderLoggingEnabled) + { + etw.RunningContinuationList(Id, i, currentContinuation); + } + + // If the continuation is an Action delegate, it came from an await continuation, + // and we should use AwaitTaskContinuation to run it. + Action ad = currentContinuation as Action; + if (ad != null) + { + AwaitTaskContinuation.RunOrScheduleAction(ad, bCanInlineContinuations, ref t_currentTask); + } + else + { + // If it's a TaskContinuation object of some kind, invoke it. + TaskContinuation tc = currentContinuation as TaskContinuation; + if (tc != null) + { + // We know that this is a synchronous continuation because the + // asynchronous ones have been weeded out + tc.Run(this, bCanInlineContinuations); + } + // Otherwise, it must be an ITaskCompletionAction, so invoke it. + else + { + Contract.Assert(currentContinuation is ITaskCompletionAction, "Expected continuation element to be Action, TaskContinuation, or ITaskContinuationAction"); + var action = (ITaskCompletionAction)currentContinuation; + + if (bCanInlineContinuations || !action.InvokeMayRunArbitraryCode) + { + action.Invoke(this); + } + else + { + ThreadPool.UnsafeQueueCustomWorkItem(new CompletionActionInvoker(action, this), forceGlobal: false); + } + } + } + } + + LogFinishCompletionNotification(); + } + } + + private void LogFinishCompletionNotification() + { + if (AsyncCausalityTracer.LoggingOn) + AsyncCausalityTracer.TraceSynchronousWorkCompletion(CausalityTraceLevel.Required, CausalitySynchronousWork.CompletionNotification); + } + + #region Continuation methods + + #region Action<Task> continuation + /// <summary> + /// Creates a continuation that executes when the target <see cref="Task"/> completes. + /// </summary> + /// <param name="continuationAction"> + /// An action to run when the <see cref="Task"/> completes. When run, the delegate will be + /// passed the completed task as an argument. + /// </param> + /// <returns>A new continuation <see cref="Task"/>.</returns> + /// <remarks> + /// The returned <see cref="Task"/> will not be scheduled for execution until the current task has + /// completed, whether it completes due to running to completion successfully, faulting due to an + /// unhandled exception, or exiting out early due to being canceled. + /// </remarks> + /// <exception cref="T:System.ArgumentNullException"> + /// The <paramref name="continuationAction"/> argument is null. + /// </exception> + [MethodImplAttribute(MethodImplOptions.NoInlining)] // Methods containing StackCrawlMark local var have to be marked non-inlineable + public Task ContinueWith(Action<Task> continuationAction) + { + StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller; + return ContinueWith(continuationAction, TaskScheduler.Current, default(CancellationToken), TaskContinuationOptions.None, ref stackMark); + } + + /// <summary> + /// Creates a continuation that executes when the target <see cref="Task"/> completes. + /// </summary> + /// <param name="continuationAction"> + /// An action to run when the <see cref="Task"/> completes. When run, the delegate will be + /// passed the completed task as an argument. + /// </param> + /// <param name="cancellationToken"> The <see cref="CancellationToken"/> that will be assigned to the new continuation task.</param> + /// <returns>A new continuation <see cref="Task"/>.</returns> + /// <remarks> + /// The returned <see cref="Task"/> will not be scheduled for execution until the current task has + /// completed, whether it completes due to running to completion successfully, faulting due to an + /// unhandled exception, or exiting out early due to being canceled. + /// </remarks> + /// <exception cref="T:System.ArgumentNullException"> + /// The <paramref name="continuationAction"/> argument is null. + /// </exception> + /// <exception cref="T:System.ObjectDisposedException">The provided <see cref="System.Threading.CancellationToken">CancellationToken</see> + /// has already been disposed. + /// </exception> + [MethodImplAttribute(MethodImplOptions.NoInlining)] // Methods containing StackCrawlMark local var have to be marked non-inlineable + public Task ContinueWith(Action<Task> continuationAction, CancellationToken cancellationToken) + { + StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller; + return ContinueWith(continuationAction, TaskScheduler.Current, cancellationToken, TaskContinuationOptions.None, ref stackMark); + } + + /// <summary> + /// Creates a continuation that executes when the target <see cref="Task"/> completes. + /// </summary> + /// <param name="continuationAction"> + /// An action to run when the <see cref="Task"/> completes. When run, the delegate will be + /// passed the completed task as an argument. + /// </param> + /// <param name="scheduler"> + /// The <see cref="TaskScheduler"/> to associate with the continuation task and to use for its execution. + /// </param> + /// <returns>A new continuation <see cref="Task"/>.</returns> + /// <remarks> + /// The returned <see cref="Task"/> will not be scheduled for execution until the current task has + /// completed, whether it completes due to running to completion successfully, faulting due to an + /// unhandled exception, or exiting out early due to being canceled. + /// </remarks> + /// <exception cref="T:System.ArgumentNullException"> + /// The <paramref name="continuationAction"/> argument is null. + /// </exception> + /// <exception cref="T:System.ArgumentNullException"> + /// The <paramref name="scheduler"/> argument is null. + /// </exception> + [MethodImplAttribute(MethodImplOptions.NoInlining)] // Methods containing StackCrawlMark local var have to be marked non-inlineable + public Task ContinueWith(Action<Task> continuationAction, TaskScheduler scheduler) + { + StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller; + return ContinueWith(continuationAction, scheduler, default(CancellationToken), TaskContinuationOptions.None, ref stackMark); + } + + /// <summary> + /// Creates a continuation that executes when the target <see cref="Task"/> completes. + /// </summary> + /// <param name="continuationAction"> + /// An action to run when the <see cref="Task"/> completes. When run, the delegate will be + /// passed the completed task as an argument. + /// </param> + /// <param name="continuationOptions"> + /// Options for when the continuation is scheduled and how it behaves. This includes criteria, such + /// as <see + /// cref="System.Threading.Tasks.TaskContinuationOptions.OnlyOnCanceled">OnlyOnCanceled</see>, as + /// well as execution options, such as <see + /// cref="System.Threading.Tasks.TaskContinuationOptions.ExecuteSynchronously">ExecuteSynchronously</see>. + /// </param> + /// <returns>A new continuation <see cref="Task"/>.</returns> + /// <remarks> + /// The returned <see cref="Task"/> will not be scheduled for execution until the current task has + /// completed. If the continuation criteria specified through the <paramref + /// name="continuationOptions"/> parameter are not met, the continuation task will be canceled + /// instead of scheduled. + /// </remarks> + /// <exception cref="T:System.ArgumentNullException"> + /// The <paramref name="continuationAction"/> argument is null. + /// </exception> + /// <exception cref="T:System.ArgumentOutOfRangeException"> + /// The <paramref name="continuationOptions"/> argument specifies an invalid value for <see + /// cref="T:System.Threading.Tasks.TaskContinuationOptions">TaskContinuationOptions</see>. + /// </exception> + [MethodImplAttribute(MethodImplOptions.NoInlining)] // Methods containing StackCrawlMark local var have to be marked non-inlineable + public Task ContinueWith(Action<Task> continuationAction, TaskContinuationOptions continuationOptions) + { + StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller; + return ContinueWith(continuationAction, TaskScheduler.Current, default(CancellationToken), continuationOptions, ref stackMark); + } + + /// <summary> + /// Creates a continuation that executes when the target <see cref="Task"/> completes. + /// </summary> + /// <param name="continuationAction"> + /// An action to run when the <see cref="Task"/> completes. When run, the delegate will be + /// passed the completed task as an argument. + /// </param> + /// <param name="continuationOptions"> + /// Options for when the continuation is scheduled and how it behaves. This includes criteria, such + /// as <see + /// cref="System.Threading.Tasks.TaskContinuationOptions.OnlyOnCanceled">OnlyOnCanceled</see>, as + /// well as execution options, such as <see + /// cref="System.Threading.Tasks.TaskContinuationOptions.ExecuteSynchronously">ExecuteSynchronously</see>. + /// </param> + /// <param name="cancellationToken">The <see cref="CancellationToken"/> that will be assigned to the new continuation task.</param> + /// <param name="scheduler"> + /// The <see cref="TaskScheduler"/> to associate with the continuation task and to use for its + /// execution. + /// </param> + /// <returns>A new continuation <see cref="Task"/>.</returns> + /// <remarks> + /// The returned <see cref="Task"/> will not be scheduled for execution until the current task has + /// completed. If the criteria specified through the <paramref name="continuationOptions"/> parameter + /// are not met, the continuation task will be canceled instead of scheduled. + /// </remarks> + /// <exception cref="T:System.ArgumentNullException"> + /// The <paramref name="continuationAction"/> argument is null. + /// </exception> + /// <exception cref="T:System.ArgumentOutOfRangeException"> + /// The <paramref name="continuationOptions"/> argument specifies an invalid value for <see + /// cref="T:System.Threading.Tasks.TaskContinuationOptions">TaskContinuationOptions</see>. + /// </exception> + /// <exception cref="T:System.ArgumentNullException"> + /// The <paramref name="scheduler"/> argument is null. + /// </exception> + /// <exception cref="T:System.ObjectDisposedException">The provided <see cref="System.Threading.CancellationToken">CancellationToken</see> + /// has already been disposed. + /// </exception> + [MethodImplAttribute(MethodImplOptions.NoInlining)] // Methods containing StackCrawlMark local var have to be marked non-inlineable + public Task ContinueWith(Action<Task> continuationAction, CancellationToken cancellationToken, + TaskContinuationOptions continuationOptions, TaskScheduler scheduler) + { + StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller; + return ContinueWith(continuationAction, scheduler, cancellationToken, continuationOptions, ref stackMark); + } + + // Same as the above overload, just with a stack mark parameter. + private Task ContinueWith(Action<Task> continuationAction, TaskScheduler scheduler, + CancellationToken cancellationToken, TaskContinuationOptions continuationOptions, ref StackCrawlMark stackMark) + { + // Throw on continuation with null action + if (continuationAction == null) + { + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.continuationAction); + } + + // Throw on continuation with null TaskScheduler + if (scheduler == null) + { + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.scheduler); + } + Contract.EndContractBlock(); + + TaskCreationOptions creationOptions; + InternalTaskOptions internalOptions; + CreationOptionsFromContinuationOptions(continuationOptions, out creationOptions, out internalOptions); + + Task continuationTask = new ContinuationTaskFromTask( + this, continuationAction, null, + creationOptions, internalOptions, + ref stackMark + ); + + // Register the continuation. If synchronous execution is requested, this may + // actually invoke the continuation before returning. + ContinueWithCore(continuationTask, scheduler, cancellationToken, continuationOptions); + + return continuationTask; + } + #endregion + + #region Action<Task, Object> continuation + + /// <summary> + /// Creates a continuation that executes when the target <see cref="Task"/> completes. + /// </summary> + /// <param name="continuationAction"> + /// An action to run when the <see cref="Task"/> completes. When run, the delegate will be + /// passed the completed task as and the caller-supplied state object as arguments. + /// </param> + /// <param name="state">An object representing data to be used by the continuation action.</param> + /// <returns>A new continuation <see cref="Task"/>.</returns> + /// <remarks> + /// The returned <see cref="Task"/> will not be scheduled for execution until the current task has + /// completed, whether it completes due to running to completion successfully, faulting due to an + /// unhandled exception, or exiting out early due to being canceled. + /// </remarks> + /// <exception cref="T:System.ArgumentNullException"> + /// The <paramref name="continuationAction"/> argument is null. + /// </exception> + [MethodImplAttribute(MethodImplOptions.NoInlining)] // Methods containing StackCrawlMark local var have to be marked non-inlineable + public Task ContinueWith(Action<Task, Object> continuationAction, Object state) + { + StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller; + return ContinueWith(continuationAction, state, TaskScheduler.Current, default(CancellationToken), TaskContinuationOptions.None, ref stackMark); + } + + /// <summary> + /// Creates a continuation that executes when the target <see cref="Task"/> completes. + /// </summary> + /// <param name="continuationAction"> + /// An action to run when the <see cref="Task"/> completes. When run, the delegate will be + /// passed the completed task and the caller-supplied state object as arguments. + /// </param> + /// <param name="state">An object representing data to be used by the continuation action.</param> + /// <param name="cancellationToken"> The <see cref="CancellationToken"/> that will be assigned to the new continuation task.</param> + /// <returns>A new continuation <see cref="Task"/>.</returns> + /// <remarks> + /// The returned <see cref="Task"/> will not be scheduled for execution until the current task has + /// completed, whether it completes due to running to completion successfully, faulting due to an + /// unhandled exception, or exiting out early due to being canceled. + /// </remarks> + /// <exception cref="T:System.ArgumentNullException"> + /// The <paramref name="continuationAction"/> argument is null. + /// </exception> + /// <exception cref="T:System.ObjectDisposedException">The provided <see cref="System.Threading.CancellationToken">CancellationToken</see> + /// has already been disposed. + /// </exception> + [MethodImplAttribute(MethodImplOptions.NoInlining)] // Methods containing StackCrawlMark local var have to be marked non-inlineable + public Task ContinueWith(Action<Task, Object> continuationAction, Object state, CancellationToken cancellationToken) + { + StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller; + return ContinueWith(continuationAction, state, TaskScheduler.Current, cancellationToken, TaskContinuationOptions.None, ref stackMark); + } + + /// <summary> + /// Creates a continuation that executes when the target <see cref="Task"/> completes. + /// </summary> + /// <param name="continuationAction"> + /// An action to run when the <see cref="Task"/> completes. When run, the delegate will be + /// passed the completed task and the caller-supplied state object as arguments. + /// </param> + /// <param name="state">An object representing data to be used by the continuation action.</param> + /// <param name="scheduler"> + /// The <see cref="TaskScheduler"/> to associate with the continuation task and to use for its execution. + /// </param> + /// <returns>A new continuation <see cref="Task"/>.</returns> + /// <remarks> + /// The returned <see cref="Task"/> will not be scheduled for execution until the current task has + /// completed, whether it completes due to running to completion successfully, faulting due to an + /// unhandled exception, or exiting out early due to being canceled. + /// </remarks> + /// <exception cref="T:System.ArgumentNullException"> + /// The <paramref name="continuationAction"/> argument is null. + /// </exception> + /// <exception cref="T:System.ArgumentNullException"> + /// The <paramref name="scheduler"/> argument is null. + /// </exception> + [MethodImplAttribute(MethodImplOptions.NoInlining)] // Methods containing StackCrawlMark local var have to be marked non-inlineable + public Task ContinueWith(Action<Task, Object> continuationAction, Object state, TaskScheduler scheduler) + { + StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller; + return ContinueWith(continuationAction, state, scheduler, default(CancellationToken), TaskContinuationOptions.None, ref stackMark); + } + + /// <summary> + /// Creates a continuation that executes when the target <see cref="Task"/> completes. + /// </summary> + /// <param name="continuationAction"> + /// An action to run when the <see cref="Task"/> completes. When run, the delegate will be + /// passed the completed task and the caller-supplied state object as arguments. + /// </param> + /// <param name="state">An object representing data to be used by the continuation action.</param> + /// <param name="continuationOptions"> + /// Options for when the continuation is scheduled and how it behaves. This includes criteria, such + /// as <see + /// cref="System.Threading.Tasks.TaskContinuationOptions.OnlyOnCanceled">OnlyOnCanceled</see>, as + /// well as execution options, such as <see + /// cref="System.Threading.Tasks.TaskContinuationOptions.ExecuteSynchronously">ExecuteSynchronously</see>. + /// </param> + /// <returns>A new continuation <see cref="Task"/>.</returns> + /// <remarks> + /// The returned <see cref="Task"/> will not be scheduled for execution until the current task has + /// completed. If the continuation criteria specified through the <paramref + /// name="continuationOptions"/> parameter are not met, the continuation task will be canceled + /// instead of scheduled. + /// </remarks> + /// <exception cref="T:System.ArgumentNullException"> + /// The <paramref name="continuationAction"/> argument is null. + /// </exception> + /// <exception cref="T:System.ArgumentOutOfRangeException"> + /// The <paramref name="continuationOptions"/> argument specifies an invalid value for <see + /// cref="T:System.Threading.Tasks.TaskContinuationOptions">TaskContinuationOptions</see>. + /// </exception> + [MethodImplAttribute(MethodImplOptions.NoInlining)] // Methods containing StackCrawlMark local var have to be marked non-inlineable + public Task ContinueWith(Action<Task, Object> continuationAction, Object state, TaskContinuationOptions continuationOptions) + { + StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller; + return ContinueWith(continuationAction, state, TaskScheduler.Current, default(CancellationToken), continuationOptions, ref stackMark); + } + + /// <summary> + /// Creates a continuation that executes when the target <see cref="Task"/> completes. + /// </summary> + /// <param name="continuationAction"> + /// An action to run when the <see cref="Task"/> completes. When run, the delegate will be + /// passed the completed task and the caller-supplied state object as arguments. + /// </param> + /// <param name="state">An object representing data to be used by the continuation action.</param> + /// <param name="continuationOptions"> + /// Options for when the continuation is scheduled and how it behaves. This includes criteria, such + /// as <see + /// cref="System.Threading.Tasks.TaskContinuationOptions.OnlyOnCanceled">OnlyOnCanceled</see>, as + /// well as execution options, such as <see + /// cref="System.Threading.Tasks.TaskContinuationOptions.ExecuteSynchronously">ExecuteSynchronously</see>. + /// </param> + /// <param name="cancellationToken">The <see cref="CancellationToken"/> that will be assigned to the new continuation task.</param> + /// <param name="scheduler"> + /// The <see cref="TaskScheduler"/> to associate with the continuation task and to use for its + /// execution. + /// </param> + /// <returns>A new continuation <see cref="Task"/>.</returns> + /// <remarks> + /// The returned <see cref="Task"/> will not be scheduled for execution until the current task has + /// completed. If the criteria specified through the <paramref name="continuationOptions"/> parameter + /// are not met, the continuation task will be canceled instead of scheduled. + /// </remarks> + /// <exception cref="T:System.ArgumentNullException"> + /// The <paramref name="continuationAction"/> argument is null. + /// </exception> + /// <exception cref="T:System.ArgumentOutOfRangeException"> + /// The <paramref name="continuationOptions"/> argument specifies an invalid value for <see + /// cref="T:System.Threading.Tasks.TaskContinuationOptions">TaskContinuationOptions</see>. + /// </exception> + /// <exception cref="T:System.ArgumentNullException"> + /// The <paramref name="scheduler"/> argument is null. + /// </exception> + /// <exception cref="T:System.ObjectDisposedException">The provided <see cref="System.Threading.CancellationToken">CancellationToken</see> + /// has already been disposed. + /// </exception> + [MethodImplAttribute(MethodImplOptions.NoInlining)] // Methods containing StackCrawlMark local var have to be marked non-inlineable + public Task ContinueWith(Action<Task, Object> continuationAction, Object state, CancellationToken cancellationToken, + TaskContinuationOptions continuationOptions, TaskScheduler scheduler) + { + StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller; + return ContinueWith(continuationAction, state, scheduler, cancellationToken, continuationOptions, ref stackMark); + } + + // Same as the above overload, just with a stack mark parameter. + private Task ContinueWith(Action<Task, Object> continuationAction, Object state, TaskScheduler scheduler, + CancellationToken cancellationToken, TaskContinuationOptions continuationOptions, ref StackCrawlMark stackMark) + { + // Throw on continuation with null action + if (continuationAction == null) + { + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.continuationAction); + } + + // Throw on continuation with null TaskScheduler + if (scheduler == null) + { + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.scheduler); + } + Contract.EndContractBlock(); + + TaskCreationOptions creationOptions; + InternalTaskOptions internalOptions; + CreationOptionsFromContinuationOptions(continuationOptions, out creationOptions, out internalOptions); + + Task continuationTask = new ContinuationTaskFromTask( + this, continuationAction, state, + creationOptions, internalOptions, + ref stackMark + ); + + // Register the continuation. If synchronous execution is requested, this may + // actually invoke the continuation before returning. + ContinueWithCore(continuationTask, scheduler, cancellationToken, continuationOptions); + + return continuationTask; + } + + #endregion + + #region Func<Task, TResult> continuation + + /// <summary> + /// Creates a continuation that executes when the target <see cref="Task"/> completes. + /// </summary> + /// <typeparam name="TResult"> + /// The type of the result produced by the continuation. + /// </typeparam> + /// <param name="continuationFunction"> + /// A function to run when the <see cref="Task"/> completes. When run, the delegate will be + /// passed the completed task as an argument. + /// </param> + /// <returns>A new continuation <see cref="Task{TResult}"/>.</returns> + /// <remarks> + /// The returned <see cref="Task{TResult}"/> will not be scheduled for execution until the current task has + /// completed, whether it completes due to running to completion successfully, faulting due to an + /// unhandled exception, or exiting out early due to being canceled. + /// </remarks> + /// <exception cref="T:System.ArgumentNullException"> + /// The <paramref name="continuationFunction"/> argument is null. + /// </exception> + [MethodImplAttribute(MethodImplOptions.NoInlining)] // Methods containing StackCrawlMark local var have to be marked non-inlineable + public Task<TResult> ContinueWith<TResult>(Func<Task, TResult> continuationFunction) + { + StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller; + return ContinueWith<TResult>(continuationFunction, TaskScheduler.Current, default(CancellationToken), + TaskContinuationOptions.None, ref stackMark); + } + + + /// <summary> + /// Creates a continuation that executes when the target <see cref="Task"/> completes. + /// </summary> + /// <typeparam name="TResult"> + /// The type of the result produced by the continuation. + /// </typeparam> + /// <param name="continuationFunction"> + /// A function to run when the <see cref="Task"/> completes. When run, the delegate will be + /// passed the completed task as an argument. + /// </param> + /// <param name="cancellationToken">The <see cref="CancellationToken"/> that will be assigned to the new continuation task.</param> + /// <returns>A new continuation <see cref="Task{TResult}"/>.</returns> + /// <remarks> + /// The returned <see cref="Task{TResult}"/> will not be scheduled for execution until the current task has + /// completed, whether it completes due to running to completion successfully, faulting due to an + /// unhandled exception, or exiting out early due to being canceled. + /// </remarks> + /// <exception cref="T:System.ArgumentNullException"> + /// The <paramref name="continuationFunction"/> argument is null. + /// </exception> + /// <exception cref="T:System.ObjectDisposedException">The provided <see cref="System.Threading.CancellationToken">CancellationToken</see> + /// has already been disposed. + /// </exception> + [MethodImplAttribute(MethodImplOptions.NoInlining)] // Methods containing StackCrawlMark local var have to be marked non-inlineable + public Task<TResult> ContinueWith<TResult>(Func<Task, TResult> continuationFunction, CancellationToken cancellationToken) + { + StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller; + return ContinueWith<TResult>(continuationFunction, TaskScheduler.Current, cancellationToken, TaskContinuationOptions.None, ref stackMark); + } + + /// <summary> + /// Creates a continuation that executes when the target <see cref="Task"/> completes. + /// </summary> + /// <typeparam name="TResult"> + /// The type of the result produced by the continuation. + /// </typeparam> + /// <param name="continuationFunction"> + /// A function to run when the <see cref="Task"/> completes. When run, the delegate will be + /// passed the completed task as an argument. + /// </param> + /// <param name="scheduler"> + /// The <see cref="TaskScheduler"/> to associate with the continuation task and to use for its execution. + /// </param> + /// <returns>A new continuation <see cref="Task{TResult}"/>.</returns> + /// <remarks> + /// The returned <see cref="Task{TResult}"/> will not be scheduled for execution until the current task has + /// completed, whether it completes due to running to completion successfully, faulting due to an + /// unhandled exception, or exiting out early due to being canceled. + /// </remarks> + /// <exception cref="T:System.ArgumentNullException"> + /// The <paramref name="continuationFunction"/> argument is null. + /// </exception> + /// <exception cref="T:System.ArgumentNullException"> + /// The <paramref name="scheduler"/> argument is null. + /// </exception> + [MethodImplAttribute(MethodImplOptions.NoInlining)] // Methods containing StackCrawlMark local var have to be marked non-inlineable + public Task<TResult> ContinueWith<TResult>(Func<Task, TResult> continuationFunction, TaskScheduler scheduler) + { + StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller; + return ContinueWith<TResult>(continuationFunction, scheduler, default(CancellationToken), TaskContinuationOptions.None, ref stackMark); + } + + /// <summary> + /// Creates a continuation that executes when the target <see cref="Task"/> completes. + /// </summary> + /// <typeparam name="TResult"> + /// The type of the result produced by the continuation. + /// </typeparam> + /// <param name="continuationFunction"> + /// A function to run when the <see cref="Task"/> completes. When run, the delegate will be + /// passed the completed task as an argument. + /// </param> + /// <param name="continuationOptions"> + /// Options for when the continuation is scheduled and how it behaves. This includes criteria, such + /// as <see + /// cref="System.Threading.Tasks.TaskContinuationOptions.OnlyOnCanceled">OnlyOnCanceled</see>, as + /// well as execution options, such as <see + /// cref="System.Threading.Tasks.TaskContinuationOptions.ExecuteSynchronously">ExecuteSynchronously</see>. + /// </param> + /// <returns>A new continuation <see cref="Task{TResult}"/>.</returns> + /// <remarks> + /// The returned <see cref="Task{TResult}"/> will not be scheduled for execution until the current task has + /// completed. If the continuation criteria specified through the <paramref + /// name="continuationOptions"/> parameter are not met, the continuation task will be canceled + /// instead of scheduled. + /// </remarks> + /// <exception cref="T:System.ArgumentNullException"> + /// The <paramref name="continuationFunction"/> argument is null. + /// </exception> + /// <exception cref="T:System.ArgumentOutOfRangeException"> + /// The <paramref name="continuationOptions"/> argument specifies an invalid value for <see + /// cref="T:System.Threading.Tasks.TaskContinuationOptions">TaskContinuationOptions</see>. + /// </exception> + [MethodImplAttribute(MethodImplOptions.NoInlining)] // Methods containing StackCrawlMark local var have to be marked non-inlineable + public Task<TResult> ContinueWith<TResult>(Func<Task, TResult> continuationFunction, TaskContinuationOptions continuationOptions) + { + StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller; + return ContinueWith<TResult>(continuationFunction, TaskScheduler.Current, default(CancellationToken), continuationOptions, ref stackMark); + } + + /// <summary> + /// Creates a continuation that executes when the target <see cref="Task"/> completes. + /// </summary> + /// <typeparam name="TResult"> + /// The type of the result produced by the continuation. + /// </typeparam> + /// <param name="continuationFunction"> + /// A function to run when the <see cref="Task"/> completes. When run, the delegate will be + /// passed the completed task as an argument. + /// </param> + /// <param name="cancellationToken">The <see cref="CancellationToken"/> that will be assigned to the new continuation task.</param> + /// <param name="continuationOptions"> + /// Options for when the continuation is scheduled and how it behaves. This includes criteria, such + /// as <see + /// cref="System.Threading.Tasks.TaskContinuationOptions.OnlyOnCanceled">OnlyOnCanceled</see>, as + /// well as execution options, such as <see + /// cref="System.Threading.Tasks.TaskContinuationOptions.ExecuteSynchronously">ExecuteSynchronously</see>. + /// </param> + /// <param name="scheduler"> + /// The <see cref="TaskScheduler"/> to associate with the continuation task and to use for its + /// execution. + /// </param> + /// <returns>A new continuation <see cref="Task{TResult}"/>.</returns> + /// <remarks> + /// The returned <see cref="Task{TResult}"/> will not be scheduled for execution until the current task has + /// completed. If the criteria specified through the <paramref name="continuationOptions"/> parameter + /// are not met, the continuation task will be canceled instead of scheduled. + /// </remarks> + /// <exception cref="T:System.ArgumentNullException"> + /// The <paramref name="continuationFunction"/> argument is null. + /// </exception> + /// <exception cref="T:System.ArgumentOutOfRangeException"> + /// The <paramref name="continuationOptions"/> argument specifies an invalid value for <see + /// cref="T:System.Threading.Tasks.TaskContinuationOptions">TaskContinuationOptions</see>. + /// </exception> + /// <exception cref="T:System.ArgumentNullException"> + /// The <paramref name="scheduler"/> argument is null. + /// </exception> + /// <exception cref="T:System.ObjectDisposedException">The provided <see cref="System.Threading.CancellationToken">CancellationToken</see> + /// has already been disposed. + /// </exception> + [MethodImplAttribute(MethodImplOptions.NoInlining)] // Methods containing StackCrawlMark local var have to be marked non-inlineable + public Task<TResult> ContinueWith<TResult>(Func<Task, TResult> continuationFunction, CancellationToken cancellationToken, + TaskContinuationOptions continuationOptions, TaskScheduler scheduler) + { + StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller; + return ContinueWith<TResult>(continuationFunction, scheduler, cancellationToken, continuationOptions, ref stackMark); + } + + // Same as the above overload, just with a stack mark parameter. + private Task<TResult> ContinueWith<TResult>(Func<Task, TResult> continuationFunction, TaskScheduler scheduler, + CancellationToken cancellationToken, TaskContinuationOptions continuationOptions, ref StackCrawlMark stackMark) + { + // Throw on continuation with null function + if (continuationFunction == null) + { + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.continuationFunction); + } + + // Throw on continuation with null task scheduler + if (scheduler == null) + { + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.scheduler); + } + Contract.EndContractBlock(); + + TaskCreationOptions creationOptions; + InternalTaskOptions internalOptions; + CreationOptionsFromContinuationOptions(continuationOptions, out creationOptions, out internalOptions); + + Task<TResult> continuationTask = new ContinuationResultTaskFromTask<TResult>( + this, continuationFunction, null, + creationOptions, internalOptions, + ref stackMark + ); + + // Register the continuation. If synchronous execution is requested, this may + // actually invoke the continuation before returning. + ContinueWithCore(continuationTask, scheduler, cancellationToken, continuationOptions); + + return continuationTask; + } + #endregion + + #region Func<Task, Object, TResult> continuation + + /// <summary> + /// Creates a continuation that executes when the target <see cref="Task"/> completes. + /// </summary> + /// <typeparam name="TResult"> + /// The type of the result produced by the continuation. + /// </typeparam> + /// <param name="continuationFunction"> + /// A function to run when the <see cref="Task"/> completes. When run, the delegate will be + /// passed the completed task and the caller-supplied state object as arguments. + /// </param> + /// <param name="state">An object representing data to be used by the continuation function.</param> + /// <returns>A new continuation <see cref="Task{TResult}"/>.</returns> + /// <remarks> + /// The returned <see cref="Task{TResult}"/> will not be scheduled for execution until the current task has + /// completed, whether it completes due to running to completion successfully, faulting due to an + /// unhandled exception, or exiting out early due to being canceled. + /// </remarks> + /// <exception cref="T:System.ArgumentNullException"> + /// The <paramref name="continuationFunction"/> argument is null. + /// </exception> + [MethodImplAttribute(MethodImplOptions.NoInlining)] // Methods containing StackCrawlMark local var have to be marked non-inlineable + public Task<TResult> ContinueWith<TResult>(Func<Task, Object, TResult> continuationFunction, Object state) + { + StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller; + return ContinueWith<TResult>(continuationFunction, state, TaskScheduler.Current, default(CancellationToken), + TaskContinuationOptions.None, ref stackMark); + } + + + /// <summary> + /// Creates a continuation that executes when the target <see cref="Task"/> completes. + /// </summary> + /// <typeparam name="TResult"> + /// The type of the result produced by the continuation. + /// </typeparam> + /// <param name="continuationFunction"> + /// A function to run when the <see cref="Task"/> completes. When run, the delegate will be + /// passed the completed task and the caller-supplied state object as arguments. + /// </param> + /// <param name="state">An object representing data to be used by the continuation function.</param> + /// <param name="cancellationToken">The <see cref="CancellationToken"/> that will be assigned to the new continuation task.</param> + /// <returns>A new continuation <see cref="Task{TResult}"/>.</returns> + /// <remarks> + /// The returned <see cref="Task{TResult}"/> will not be scheduled for execution until the current task has + /// completed, whether it completes due to running to completion successfully, faulting due to an + /// unhandled exception, or exiting out early due to being canceled. + /// </remarks> + /// <exception cref="T:System.ArgumentNullException"> + /// The <paramref name="continuationFunction"/> argument is null. + /// </exception> + /// <exception cref="T:System.ObjectDisposedException">The provided <see cref="System.Threading.CancellationToken">CancellationToken</see> + /// has already been disposed. + /// </exception> + [MethodImplAttribute(MethodImplOptions.NoInlining)] // Methods containing StackCrawlMark local var have to be marked non-inlineable + public Task<TResult> ContinueWith<TResult>(Func<Task, Object, TResult> continuationFunction, Object state, CancellationToken cancellationToken) + { + StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller; + return ContinueWith<TResult>(continuationFunction, state, TaskScheduler.Current, cancellationToken, TaskContinuationOptions.None, ref stackMark); + } + + /// <summary> + /// Creates a continuation that executes when the target <see cref="Task"/> completes. + /// </summary> + /// <typeparam name="TResult"> + /// The type of the result produced by the continuation. + /// </typeparam> + /// <param name="continuationFunction"> + /// A function to run when the <see cref="Task"/> completes. When run, the delegate will be + /// passed the completed task and the caller-supplied state object as arguments. + /// </param> + /// <param name="state">An object representing data to be used by the continuation function.</param> + /// <param name="scheduler"> + /// The <see cref="TaskScheduler"/> to associate with the continuation task and to use for its execution. + /// </param> + /// <returns>A new continuation <see cref="Task{TResult}"/>.</returns> + /// <remarks> + /// The returned <see cref="Task{TResult}"/> will not be scheduled for execution until the current task has + /// completed, whether it completes due to running to completion successfully, faulting due to an + /// unhandled exception, or exiting out early due to being canceled. + /// </remarks> + /// <exception cref="T:System.ArgumentNullException"> + /// The <paramref name="continuationFunction"/> argument is null. + /// </exception> + /// <exception cref="T:System.ArgumentNullException"> + /// The <paramref name="scheduler"/> argument is null. + /// </exception> + [MethodImplAttribute(MethodImplOptions.NoInlining)] // Methods containing StackCrawlMark local var have to be marked non-inlineable + public Task<TResult> ContinueWith<TResult>(Func<Task, Object, TResult> continuationFunction, Object state, TaskScheduler scheduler) + { + StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller; + return ContinueWith<TResult>(continuationFunction, state, scheduler, default(CancellationToken), TaskContinuationOptions.None, ref stackMark); + } + + /// <summary> + /// Creates a continuation that executes when the target <see cref="Task"/> completes. + /// </summary> + /// <typeparam name="TResult"> + /// The type of the result produced by the continuation. + /// </typeparam> + /// <param name="continuationFunction"> + /// A function to run when the <see cref="Task"/> completes. When run, the delegate will be + /// passed the completed task and the caller-supplied state object as arguments. + /// </param> + /// <param name="state">An object representing data to be used by the continuation function.</param> + /// <param name="continuationOptions"> + /// Options for when the continuation is scheduled and how it behaves. This includes criteria, such + /// as <see + /// cref="System.Threading.Tasks.TaskContinuationOptions.OnlyOnCanceled">OnlyOnCanceled</see>, as + /// well as execution options, such as <see + /// cref="System.Threading.Tasks.TaskContinuationOptions.ExecuteSynchronously">ExecuteSynchronously</see>. + /// </param> + /// <returns>A new continuation <see cref="Task{TResult}"/>.</returns> + /// <remarks> + /// The returned <see cref="Task{TResult}"/> will not be scheduled for execution until the current task has + /// completed. If the continuation criteria specified through the <paramref + /// name="continuationOptions"/> parameter are not met, the continuation task will be canceled + /// instead of scheduled. + /// </remarks> + /// <exception cref="T:System.ArgumentNullException"> + /// The <paramref name="continuationFunction"/> argument is null. + /// </exception> + /// <exception cref="T:System.ArgumentOutOfRangeException"> + /// The <paramref name="continuationOptions"/> argument specifies an invalid value for <see + /// cref="T:System.Threading.Tasks.TaskContinuationOptions">TaskContinuationOptions</see>. + /// </exception> + [MethodImplAttribute(MethodImplOptions.NoInlining)] // Methods containing StackCrawlMark local var have to be marked non-inlineable + public Task<TResult> ContinueWith<TResult>(Func<Task, Object, TResult> continuationFunction, Object state, TaskContinuationOptions continuationOptions) + { + StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller; + return ContinueWith<TResult>(continuationFunction, state, TaskScheduler.Current, default(CancellationToken), continuationOptions, ref stackMark); + } + + /// <summary> + /// Creates a continuation that executes when the target <see cref="Task"/> completes. + /// </summary> + /// <typeparam name="TResult"> + /// The type of the result produced by the continuation. + /// </typeparam> + /// <param name="continuationFunction"> + /// A function to run when the <see cref="Task"/> completes. When run, the delegate will be + /// passed the completed task and the caller-supplied state object as arguments. + /// </param> + /// <param name="state">An object representing data to be used by the continuation function.</param> + /// <param name="cancellationToken">The <see cref="CancellationToken"/> that will be assigned to the new continuation task.</param> + /// <param name="continuationOptions"> + /// Options for when the continuation is scheduled and how it behaves. This includes criteria, such + /// as <see + /// cref="System.Threading.Tasks.TaskContinuationOptions.OnlyOnCanceled">OnlyOnCanceled</see>, as + /// well as execution options, such as <see + /// cref="System.Threading.Tasks.TaskContinuationOptions.ExecuteSynchronously">ExecuteSynchronously</see>. + /// </param> + /// <param name="scheduler"> + /// The <see cref="TaskScheduler"/> to associate with the continuation task and to use for its + /// execution. + /// </param> + /// <returns>A new continuation <see cref="Task{TResult}"/>.</returns> + /// <remarks> + /// The returned <see cref="Task{TResult}"/> will not be scheduled for execution until the current task has + /// completed. If the criteria specified through the <paramref name="continuationOptions"/> parameter + /// are not met, the continuation task will be canceled instead of scheduled. + /// </remarks> + /// <exception cref="T:System.ArgumentNullException"> + /// The <paramref name="continuationFunction"/> argument is null. + /// </exception> + /// <exception cref="T:System.ArgumentOutOfRangeException"> + /// The <paramref name="continuationOptions"/> argument specifies an invalid value for <see + /// cref="T:System.Threading.Tasks.TaskContinuationOptions">TaskContinuationOptions</see>. + /// </exception> + /// <exception cref="T:System.ArgumentNullException"> + /// The <paramref name="scheduler"/> argument is null. + /// </exception> + /// <exception cref="T:System.ObjectDisposedException">The provided <see cref="System.Threading.CancellationToken">CancellationToken</see> + /// has already been disposed. + /// </exception> + [MethodImplAttribute(MethodImplOptions.NoInlining)] // Methods containing StackCrawlMark local var have to be marked non-inlineable + public Task<TResult> ContinueWith<TResult>(Func<Task, Object, TResult> continuationFunction, Object state, CancellationToken cancellationToken, + TaskContinuationOptions continuationOptions, TaskScheduler scheduler) + { + StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller; + return ContinueWith<TResult>(continuationFunction, state, scheduler, cancellationToken, continuationOptions, ref stackMark); + } + + // Same as the above overload, just with a stack mark parameter. + private Task<TResult> ContinueWith<TResult>(Func<Task, Object, TResult> continuationFunction, Object state, TaskScheduler scheduler, + CancellationToken cancellationToken, TaskContinuationOptions continuationOptions, ref StackCrawlMark stackMark) + { + // Throw on continuation with null function + if (continuationFunction == null) + { + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.continuationFunction); + } + + // Throw on continuation with null task scheduler + if (scheduler == null) + { + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.scheduler); + } + Contract.EndContractBlock(); + + TaskCreationOptions creationOptions; + InternalTaskOptions internalOptions; + CreationOptionsFromContinuationOptions(continuationOptions, out creationOptions, out internalOptions); + + Task<TResult> continuationTask = new ContinuationResultTaskFromTask<TResult>( + this, continuationFunction, state, + creationOptions, internalOptions, + ref stackMark + ); + + // Register the continuation. If synchronous execution is requested, this may + // actually invoke the continuation before returning. + ContinueWithCore(continuationTask, scheduler, cancellationToken, continuationOptions); + + return continuationTask; + } + #endregion + + /// <summary> + /// Converts TaskContinuationOptions to TaskCreationOptions, and also does + /// some validity checking along the way. + /// </summary> + /// <param name="continuationOptions">Incoming TaskContinuationOptions</param> + /// <param name="creationOptions">Outgoing TaskCreationOptions</param> + /// <param name="internalOptions">Outgoing InternalTaskOptions</param> + internal static void CreationOptionsFromContinuationOptions( + TaskContinuationOptions continuationOptions, + out TaskCreationOptions creationOptions, + out InternalTaskOptions internalOptions) + { + // This is used a couple of times below + TaskContinuationOptions NotOnAnything = + TaskContinuationOptions.NotOnCanceled | + TaskContinuationOptions.NotOnFaulted | + TaskContinuationOptions.NotOnRanToCompletion; + + TaskContinuationOptions creationOptionsMask = + TaskContinuationOptions.PreferFairness | + TaskContinuationOptions.LongRunning | + TaskContinuationOptions.DenyChildAttach | + TaskContinuationOptions.HideScheduler | + TaskContinuationOptions.AttachedToParent| + TaskContinuationOptions.RunContinuationsAsynchronously; + + // Check that LongRunning and ExecuteSynchronously are not specified together + TaskContinuationOptions illegalMask = TaskContinuationOptions.ExecuteSynchronously | TaskContinuationOptions.LongRunning; + if ((continuationOptions & illegalMask) == illegalMask) + { + ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.continuationOptions, ExceptionResource.Task_ContinueWith_ESandLR); + } + + // Check that no illegal options were specified + if ((continuationOptions & + ~(creationOptionsMask | NotOnAnything | + TaskContinuationOptions.LazyCancellation | TaskContinuationOptions.ExecuteSynchronously)) != 0) + { + ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.continuationOptions); + } + + // Check that we didn't specify "not on anything" + if ((continuationOptions & NotOnAnything) == NotOnAnything) + { + ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.continuationOptions, ExceptionResource.Task_ContinueWith_NotOnAnything); + } + + // This passes over all but LazyCancellation, which has no representation in TaskCreationOptions + creationOptions = (TaskCreationOptions)(continuationOptions & creationOptionsMask); + + // internalOptions has at least ContinuationTask ... + internalOptions = InternalTaskOptions.ContinuationTask; + + // ... and possibly LazyCancellation + if ((continuationOptions & TaskContinuationOptions.LazyCancellation) != 0) + internalOptions |= InternalTaskOptions.LazyCancellation; + } + + + /// <summary> + /// Registers the continuation and possibly runs it (if the task is already finished). + /// </summary> + /// <param name="continuationTask">The continuation task itself.</param> + /// <param name="scheduler">TaskScheduler with which to associate continuation task.</param> + /// <param name="options">Restrictions on when the continuation becomes active.</param> + internal void ContinueWithCore(Task continuationTask, + TaskScheduler scheduler, + CancellationToken cancellationToken, + TaskContinuationOptions options) + { + Contract.Requires(continuationTask != null, "Task.ContinueWithCore(): null continuationTask"); + Contract.Requires(scheduler != null, "Task.ContinueWithCore(): null scheduler"); + Contract.Requires(!continuationTask.IsCompleted, "Did not expect continuationTask to be completed"); + + // Create a TaskContinuation + TaskContinuation continuation = new StandardTaskContinuation(continuationTask, options, scheduler); + + // If cancellationToken is cancellable, then assign it. + if (cancellationToken.CanBeCanceled) + { + if (IsCompleted || cancellationToken.IsCancellationRequested) + { + // If the antecedent has completed, then we will not be queuing up + // the continuation in the antecedent's continuation list. Likewise, + // if the cancellationToken has been canceled, continuationTask will + // be completed in the AssignCancellationToken call below, and there + // is no need to queue the continuation to the antecedent's continuation + // list. In either of these two cases, we will pass "null" for the antecedent, + // meaning "the cancellation callback should not attempt to remove the + // continuation from its antecedent's continuation list". + continuationTask.AssignCancellationToken(cancellationToken, null, null); + } + else + { + // The antecedent is not yet complete, so there is a pretty good chance + // that the continuation will be queued up in the antecedent. Assign the + // cancellation token with information about the antecedent, so that the + // continuation can be dequeued upon the signalling of the token. + // + // It's possible that the antecedent completes before the call to AddTaskContinuation, + // and that is a benign race condition. It just means that the cancellation will result in + // a futile search of the antecedent's continuation list. + continuationTask.AssignCancellationToken(cancellationToken, this, continuation); + } + } + + // In the case of a pre-canceled token, continuationTask will have been completed + // in a Canceled state by now. If such is the case, there is no need to go through + // the motions of queuing up the continuation for eventual execution. + if (!continuationTask.IsCompleted) + { + // We need additional correlation produced here to ensure that at least the continuation + // code will be correlatable to the currrent activity that initiated "this" task: + // . when the antecendent ("this") is a promise we have very little control over where + // the code for the promise will run (e.g. it can be a task from a user provided + // TaskCompletionSource or from a classic Begin/End async operation); this user or + // system code will likely not have stamped an activity id on the thread, so there's + // generally no easy correlation that can be provided between the current activity + // and the promise. Also the continuation code may run practically on any thread. + // Since there may be no correlation between the current activity and the TCS's task + // activity, we ensure we at least create a correlation from the current activity to + // the continuation that runs when the promise completes. + if ((this.Options & (TaskCreationOptions)InternalTaskOptions.PromiseTask) != 0 && + !(this is ITaskCompletionAction)) + { + var etwLog = TplEtwProvider.Log; + if (etwLog.IsEnabled()) + { + etwLog.AwaitTaskContinuationScheduled(TaskScheduler.Current.Id, Task.CurrentId ?? 0, continuationTask.Id); + } + } + + // Attempt to enqueue the continuation + bool continuationQueued = AddTaskContinuation(continuation, addBeforeOthers: false); + + // If the continuation was not queued (because the task completed), then run it now. + if (!continuationQueued) continuation.Run(this, bCanInlineContinuationTask: true); + } + } + #endregion + + // Adds a lightweight completion action to a task. This is similar to a continuation + // task except that it is stored as an action, and thus does not require the allocation/ + // execution resources of a continuation task. + // + // Used internally by ContinueWhenAll() and ContinueWhenAny(). + internal void AddCompletionAction(ITaskCompletionAction action) + { + AddCompletionAction(action, addBeforeOthers: false); + } + + private void AddCompletionAction(ITaskCompletionAction action, bool addBeforeOthers) + { + if (!AddTaskContinuation(action, addBeforeOthers)) + action.Invoke(this); // run the action directly if we failed to queue the continuation (i.e., the task completed) + } + + // Support method for AddTaskContinuation that takes care of multi-continuation logic. + // Returns true if and only if the continuation was successfully queued. + // THIS METHOD ASSUMES THAT m_continuationObject IS NOT NULL. That case was taken + // care of in the calling method, AddTaskContinuation(). + private bool AddTaskContinuationComplex(object tc, bool addBeforeOthers) + { + Contract.Requires(tc != null, "Expected non-null tc object in AddTaskContinuationComplex"); + + object oldValue = m_continuationObject; + + // Logic for the case where we were previously storing a single continuation + if ((oldValue != s_taskCompletionSentinel) && (!(oldValue is List<object>))) + { + // Construct a new TaskContinuation list + List<object> newList = new List<object>(); + + // Add in the old single value + newList.Add(oldValue); + + // Now CAS in the new list + Interlocked.CompareExchange(ref m_continuationObject, newList, oldValue); + + // We might be racing against another thread converting the single into + // a list, or we might be racing against task completion, so resample "list" + // below. + } + + // m_continuationObject is guaranteed at this point to be either a List or + // s_taskCompletionSentinel. + List<object> list = m_continuationObject as List<object>; + Contract.Assert((list != null) || (m_continuationObject == s_taskCompletionSentinel), + "Expected m_continuationObject to be list or sentinel"); + + // If list is null, it can only mean that s_taskCompletionSentinel has been exchanged + // into m_continuationObject. Thus, the task has completed and we should return false + // from this method, as we will not be queuing up the continuation. + if (list != null) + { + lock (list) + { + // It is possible for the task to complete right after we snap the copy of + // the list. If so, then fall through and return false without queuing the + // continuation. + if (m_continuationObject != s_taskCompletionSentinel) + { + // Before growing the list we remove possible null entries that are the + // result from RemoveContinuations() + if (list.Count == list.Capacity) + { + list.RemoveAll(s_IsTaskContinuationNullPredicate); + } + + if (addBeforeOthers) + list.Insert(0, tc); + else + list.Add(tc); + + return true; // continuation successfully queued, so return true. + } + } + } + + // We didn't succeed in queuing the continuation, so return false. + return false; + } + + // Record a continuation task or action. + // Return true if and only if we successfully queued a continuation. + private bool AddTaskContinuation(object tc, bool addBeforeOthers) + { + Contract.Requires(tc != null); + + // Make sure that, if someone calls ContinueWith() right after waiting for the predecessor to complete, + // we don't queue up a continuation. + if (IsCompleted) return false; + + // Try to just jam tc into m_continuationObject + if ((m_continuationObject != null) || (Interlocked.CompareExchange(ref m_continuationObject, tc, null) != null)) + { + // If we get here, it means that we failed to CAS tc into m_continuationObject. + // Therefore, we must go the more complicated route. + return AddTaskContinuationComplex(tc, addBeforeOthers); + } + else return true; + } + + // Removes a continuation task from m_continuations + internal void RemoveContinuation(object continuationObject) // could be TaskContinuation or Action<Task> + { + // We need to snap a local reference to m_continuations since reading a volatile object is more costly. + // Also to prevent the value to be changed as result of a race condition with another method. + object continuationsLocalRef = m_continuationObject; + + // Task is completed. Nothing to do here. + if (continuationsLocalRef == s_taskCompletionSentinel) return; + + List<object> continuationsLocalListRef = continuationsLocalRef as List<object>; + + if (continuationsLocalListRef == null) + { + // This is not a list. If we have a single object (the one we want to remove) we try to replace it with an empty list. + // Note we cannot go back to a null state, since it will mess up the AddTaskContinuation logic. + if (Interlocked.CompareExchange(ref m_continuationObject, new List<object>(), continuationObject) != continuationObject) + { + // If we fail it means that either AddContinuationComplex won the race condition and m_continuationObject is now a List + // that contains the element we want to remove. Or FinishContinuations set the s_taskCompletionSentinel. + // So we should try to get a list one more time + continuationsLocalListRef = m_continuationObject as List<object>; + } + else + { + // Exchange was successful so we can skip the last comparison + return; + } + } + + // if continuationsLocalRef == null it means s_taskCompletionSentinel has been set already and there is nothing else to do. + if (continuationsLocalListRef != null) + { + lock (continuationsLocalListRef) + { + // There is a small chance that this task completed since we took a local snapshot into + // continuationsLocalRef. In that case, just return; we don't want to be manipulating the + // continuation list as it is being processed. + if (m_continuationObject == s_taskCompletionSentinel) return; + + // Find continuationObject in the continuation list + int index = continuationsLocalListRef.IndexOf(continuationObject); + + if (index != -1) + { + // null out that TaskContinuation entry, which will be interpreted as "to be cleaned up" + continuationsLocalListRef[index] = null; + + } + } + } + } + + // statically allocated delegate for the RemoveAll expression in RemoveContinuations() and AddContinuationComplex() + private readonly static Predicate<object> s_IsTaskContinuationNullPredicate = + new Predicate<object>((tc) => { return (tc == null); }); + + + // + // Wait methods + // + + /// <summary> + /// Waits for all of the provided <see cref="Task"/> objects to complete execution. + /// </summary> + /// <param name="tasks"> + /// An array of <see cref="Task"/> instances on which to wait. + /// </param> + /// <exception cref="T:System.ArgumentNullException"> + /// The <paramref name="tasks"/> argument is null. + /// </exception> + /// <exception cref="T:System.ArgumentNullException"> + /// The <paramref name="tasks"/> argument contains a null element. + /// </exception> + /// <exception cref="T:System.AggregateException"> + /// At least one of the <see cref="Task"/> instances was canceled -or- an exception was thrown during + /// the execution of at least one of the <see cref="Task"/> instances. + /// </exception> + [MethodImpl(MethodImplOptions.NoOptimization)] // this is needed for the parallel debugger + public static void WaitAll(params Task[] tasks) + { +#if DEBUG + bool waitResult = +#endif + WaitAll(tasks, Timeout.Infinite); + +#if DEBUG + Contract.Assert(waitResult, "expected wait to succeed"); +#endif + } + + /// <summary> + /// Waits for all of the provided <see cref="Task"/> objects to complete execution. + /// </summary> + /// <returns> + /// true if all of the <see cref="Task"/> instances completed execution within the allotted time; + /// otherwise, false. + /// </returns> + /// <param name="tasks"> + /// An array of <see cref="Task"/> instances on which to wait. + /// </param> + /// <param name="timeout"> + /// A <see cref="System.TimeSpan"/> that represents the number of milliseconds to wait, or a <see + /// cref="System.TimeSpan"/> that represents -1 milliseconds to wait indefinitely. + /// </param> + /// <exception cref="T:System.ArgumentNullException"> + /// The <paramref name="tasks"/> argument is null. + /// </exception> + /// <exception cref="T:System.ArgumentException"> + /// The <paramref name="tasks"/> argument contains a null element. + /// </exception> + /// <exception cref="T:System.AggregateException"> + /// At least one of the <see cref="Task"/> instances was canceled -or- an exception was thrown during + /// the execution of at least one of the <see cref="Task"/> instances. + /// </exception> + /// <exception cref="T:System.ArgumentOutOfRangeException"> + /// <paramref name="timeout"/> is a negative number other than -1 milliseconds, which represents an + /// infinite time-out -or- timeout is greater than + /// <see cref="System.Int32.MaxValue"/>. + /// </exception> + [MethodImpl(MethodImplOptions.NoOptimization)] // this is needed for the parallel debugger + public static bool WaitAll(Task[] tasks, TimeSpan timeout) + { + long totalMilliseconds = (long)timeout.TotalMilliseconds; + if (totalMilliseconds < -1 || totalMilliseconds > Int32.MaxValue) + { + ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.timeout); + } + + return WaitAll(tasks, (int)totalMilliseconds); + + } + + /// <summary> + /// Waits for all of the provided <see cref="Task"/> objects to complete execution. + /// </summary> + /// <returns> + /// true if all of the <see cref="Task"/> instances completed execution within the allotted time; + /// otherwise, false. + /// </returns> + /// <param name="millisecondsTimeout"> + /// The number of milliseconds to wait, or <see cref="System.Threading.Timeout.Infinite"/> (-1) to + /// wait indefinitely.</param> + /// <param name="tasks">An array of <see cref="Task"/> instances on which to wait. + /// </param> + /// <exception cref="T:System.ArgumentNullException"> + /// The <paramref name="tasks"/> argument is null. + /// </exception> + /// <exception cref="T:System.ArgumentException"> + /// The <paramref name="tasks"/> argument contains a null element. + /// </exception> + /// <exception cref="T:System.AggregateException"> + /// At least one of the <see cref="Task"/> instances was canceled -or- an exception was thrown during + /// the execution of at least one of the <see cref="Task"/> instances. + /// </exception> + /// <exception cref="T:System.ArgumentOutOfRangeException"> + /// <paramref name="millisecondsTimeout"/> is a negative number other than -1, which represents an + /// infinite time-out. + /// </exception> + [MethodImpl(MethodImplOptions.NoOptimization)] // this is needed for the parallel debugger + public static bool WaitAll(Task[] tasks, int millisecondsTimeout) + { + return WaitAll(tasks, millisecondsTimeout, default(CancellationToken)); + } + + /// <summary> + /// Waits for all of the provided <see cref="Task"/> objects to complete execution. + /// </summary> + /// <returns> + /// true if all of the <see cref="Task"/> instances completed execution within the allotted time; + /// otherwise, false. + /// </returns> + /// <param name="tasks"> + /// An array of <see cref="Task"/> instances on which to wait. + /// </param> + /// <param name="cancellationToken"> + /// A <see cref="CancellationToken"/> to observe while waiting for the tasks to complete. + /// </param> + /// <exception cref="T:System.ArgumentNullException"> + /// The <paramref name="tasks"/> argument is null. + /// </exception> + /// <exception cref="T:System.ArgumentException"> + /// The <paramref name="tasks"/> argument contains a null element. + /// </exception> + /// <exception cref="T:System.AggregateException"> + /// At least one of the <see cref="Task"/> instances was canceled -or- an exception was thrown during + /// the execution of at least one of the <see cref="Task"/> instances. + /// </exception> + /// <exception cref="T:System.OperationCanceledException"> + /// The <paramref name="cancellationToken"/> was canceled. + /// </exception> + [MethodImpl(MethodImplOptions.NoOptimization)] // this is needed for the parallel debugger + public static void WaitAll(Task[] tasks, CancellationToken cancellationToken) + { + WaitAll(tasks, Timeout.Infinite, cancellationToken); + } + + /// <summary> + /// Waits for all of the provided <see cref="Task"/> objects to complete execution. + /// </summary> + /// <returns> + /// true if all of the <see cref="Task"/> instances completed execution within the allotted time; + /// otherwise, false. + /// </returns> + /// <param name="tasks"> + /// An array of <see cref="Task"/> instances on which to wait. + /// </param> + /// <param name="millisecondsTimeout"> + /// The number of milliseconds to wait, or <see cref="System.Threading.Timeout.Infinite"/> (-1) to + /// wait indefinitely. + /// </param> + /// <param name="cancellationToken"> + /// A <see cref="CancellationToken"/> to observe while waiting for the tasks to complete. + /// </param> + /// <exception cref="T:System.ArgumentNullException"> + /// The <paramref name="tasks"/> argument is null. + /// </exception> + /// <exception cref="T:System.ArgumentException"> + /// The <paramref name="tasks"/> argument contains a null element. + /// </exception> + /// <exception cref="T:System.AggregateException"> + /// At least one of the <see cref="Task"/> instances was canceled -or- an exception was thrown during + /// the execution of at least one of the <see cref="Task"/> instances. + /// </exception> + /// <exception cref="T:System.ArgumentOutOfRangeException"> + /// <paramref name="millisecondsTimeout"/> is a negative number other than -1, which represents an + /// infinite time-out. + /// </exception> + /// <exception cref="T:System.OperationCanceledException"> + /// The <paramref name="cancellationToken"/> was canceled. + /// </exception> + [MethodImpl(MethodImplOptions.NoOptimization)] // this is needed for the parallel debugger + public static bool WaitAll(Task[] tasks, int millisecondsTimeout, CancellationToken cancellationToken) + { + if (tasks == null) + { + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.tasks); + } + if (millisecondsTimeout < -1) + { + ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.millisecondsTimeout); + } + Contract.EndContractBlock(); + + cancellationToken.ThrowIfCancellationRequested(); // early check before we make any allocations + + // + // In this WaitAll() implementation we have 2 alternate code paths for a task to be handled: + // CODEPATH1: skip an already completed task, CODEPATH2: actually wait on tasks + // We make sure that the exception behavior of Task.Wait() is replicated the same for tasks handled in either of these codepaths + // + + List<Exception> exceptions = null; + List<Task> waitedOnTaskList = null; + List<Task> notificationTasks = null; + + // If any of the waited-upon tasks end as Faulted or Canceled, set these to true. + bool exceptionSeen = false, cancellationSeen = false; + + bool returnValue = true; + + // Collects incomplete tasks in "waitedOnTaskList" + for (int i = tasks.Length - 1; i >= 0; i--) + { + Task task = tasks[i]; + + if (task == null) + { + ThrowHelper.ThrowArgumentException(ExceptionResource.Task_WaitMulti_NullTask, ExceptionArgument.tasks); + } + + bool taskIsCompleted = task.IsCompleted; + if (!taskIsCompleted) + { + // try inlining the task only if we have an infinite timeout and an empty cancellation token + if (millisecondsTimeout != Timeout.Infinite || cancellationToken.CanBeCanceled) + { + // We either didn't attempt inline execution because we had a non-infinite timeout or we had a cancellable token. + // In all cases we need to do a full wait on the task (=> add its event into the list.) + AddToList(task, ref waitedOnTaskList, initSize: tasks.Length); + } + else + { + // We are eligible for inlining. If it doesn't work, we'll do a full wait. + taskIsCompleted = task.WrappedTryRunInline() && task.IsCompleted; // A successful TryRunInline doesn't guarantee completion + if (!taskIsCompleted) AddToList(task, ref waitedOnTaskList, initSize: tasks.Length); + } + } + + if (taskIsCompleted) + { + if (task.IsFaulted) exceptionSeen = true; + else if (task.IsCanceled) cancellationSeen = true; + if (task.IsWaitNotificationEnabled) AddToList(task, ref notificationTasks, initSize: 1); + } + } + + if (waitedOnTaskList != null) + { + // Block waiting for the tasks to complete. + returnValue = WaitAllBlockingCore(waitedOnTaskList, millisecondsTimeout, cancellationToken); + + // If the wait didn't time out, ensure exceptions are propagated, and if a debugger is + // attached and one of these tasks requires it, that we notify the debugger of a wait completion. + if (returnValue) + { + // Add any exceptions for this task to the collection, and if it's wait + // notification bit is set, store it to operate on at the end. + foreach (var task in waitedOnTaskList) + { + if (task.IsFaulted) exceptionSeen = true; + else if (task.IsCanceled) cancellationSeen = true; + if (task.IsWaitNotificationEnabled) AddToList(task, ref notificationTasks, initSize: 1); + } + } + + // We need to prevent the tasks array from being GC'ed until we come out of the wait. + // This is necessary so that the Parallel Debugger can traverse it during the long wait and + // deduce waiter/waitee relationships + GC.KeepAlive(tasks); + } + + // Now that we're done and about to exit, if the wait completed and if we have + // any tasks with a notification bit set, signal the debugger if any requires it. + if (returnValue && notificationTasks != null) + { + // Loop through each task tha that had its bit set, and notify the debugger + // about the first one that requires it. The debugger will reset the bit + // for any tasks we don't notify of as soon as we break, so we only need to notify + // for one. + foreach (var task in notificationTasks) + { + if (task.NotifyDebuggerOfWaitCompletionIfNecessary()) break; + } + } + + // If one or more threw exceptions, aggregate and throw them. + if (returnValue && (exceptionSeen || cancellationSeen)) + { + // If the WaitAll was canceled and tasks were canceled but not faulted, + // prioritize throwing an OCE for canceling the WaitAll over throwing an + // AggregateException for all of the canceled Tasks. This helps + // to bring determinism to an otherwise non-determistic case of using + // the same token to cancel both the WaitAll and the Tasks. + if (!exceptionSeen) cancellationToken.ThrowIfCancellationRequested(); + + // Now gather up and throw all of the exceptions. + foreach (var task in tasks) AddExceptionsForCompletedTask(ref exceptions, task); + Contract.Assert(exceptions != null, "Should have seen at least one exception"); + ThrowHelper.ThrowAggregateException(exceptions); + } + + return returnValue; + } + + /// <summary>Adds an element to the list, initializing the list if it's null.</summary> + /// <typeparam name="T">Specifies the type of data stored in the list.</typeparam> + /// <param name="item">The item to add.</param> + /// <param name="list">The list.</param> + /// <param name="initSize">The size to which to initialize the list if the list is null.</param> + private static void AddToList<T>(T item, ref List<T> list, int initSize) + { + if (list == null) list = new List<T>(initSize); + list.Add(item); + } + + /// <summary>Performs a blocking WaitAll on the vetted list of tasks.</summary> + /// <param name="tasks">The tasks, which have already been checked and filtered for completion.</param> + /// <param name="millisecondsTimeout">The timeout.</param> + /// <param name="cancellationToken">The cancellation token.</param> + /// <returns>true if all of the tasks completed; otherwise, false.</returns> + private static bool WaitAllBlockingCore(List<Task> tasks, int millisecondsTimeout, CancellationToken cancellationToken) + { + Contract.Assert(tasks != null, "Expected a non-null list of tasks"); + Contract.Assert(tasks.Count > 0, "Expected at least one task"); + + bool waitCompleted = false; + var mres = new SetOnCountdownMres(tasks.Count); + try + { + foreach (var task in tasks) + { + task.AddCompletionAction(mres, addBeforeOthers: true); + } + waitCompleted = mres.Wait(millisecondsTimeout, cancellationToken); + } + finally + { + if (!waitCompleted) + { + foreach (var task in tasks) + { + if (!task.IsCompleted) task.RemoveContinuation(mres); + } + } + // It's ok that we don't dispose of the MRES here, as we never + // access the MRES' WaitHandle, and thus no finalizable resources + // are actually created. We don't always just Dispose it because + // a continuation that's accessing the MRES could still be executing. + } + return waitCompleted; + } + + // A ManualResetEventSlim that will get Set after Invoke is called count times. + // This allows us to replace this logic: + // var mres = new ManualResetEventSlim(tasks.Count); + // Action<Task> completionAction = delegate { if(Interlocked.Decrement(ref count) == 0) mres.Set(); }; + // foreach(var task in tasks) task.AddCompletionAction(completionAction); + // with this logic: + // var mres = new SetOnCountdownMres(tasks.Count); + // foreach(var task in tasks) task.AddCompletionAction(mres); + // which saves a couple of allocations. + // + // Used in WaitAllBlockingCore (above). + private sealed class SetOnCountdownMres : ManualResetEventSlim, ITaskCompletionAction + { + private int _count; + + internal SetOnCountdownMres(int count) + { + Contract.Assert(count > 0, "Expected count > 0"); + _count = count; + } + + public void Invoke(Task completingTask) + { + if (Interlocked.Decrement(ref _count) == 0) Set(); + Contract.Assert(_count >= 0, "Count should never go below 0"); + } + + public bool InvokeMayRunArbitraryCode { get { return false; } } + } + + /// <summary> + /// Internal WaitAll implementation which is meant to be used with small number of tasks, + /// optimized for Parallel.Invoke and other structured primitives. + /// </summary> + internal static void FastWaitAll(Task[] tasks) + { + Contract.Requires(tasks != null); + + List<Exception> exceptions = null; + + // Collects incomplete tasks in "waitedOnTaskList" and their cooperative events in "cooperativeEventList" + for (int i = tasks.Length - 1; i >= 0; i--) + { + if (!tasks[i].IsCompleted) + { + // Just attempting to inline here... result doesn't matter. + // We'll do a second pass to do actual wait on each task, and to aggregate their exceptions. + // If the task is inlined here, it will register as IsCompleted in the second pass + // and will just give us the exception. + tasks[i].WrappedTryRunInline(); + } + } + + // Wait on the tasks. + for (int i = tasks.Length - 1; i >= 0; i--) + { + var task = tasks[i]; + task.SpinThenBlockingWait(Timeout.Infinite, default(CancellationToken)); + AddExceptionsForCompletedTask(ref exceptions, task); + + // Note that unlike other wait code paths, we do not check + // task.NotifyDebuggerOfWaitCompletionIfNecessary() here, because this method is currently + // only used from contexts where the tasks couldn't have that bit set, namely + // Parallel.Invoke. If that ever changes, such checks should be added here. + } + + // If one or more threw exceptions, aggregate them. + if (exceptions != null) + { + ThrowHelper.ThrowAggregateException(exceptions); + } + } + + /// <summary> + /// This internal function is only meant to be called by WaitAll() + /// If the completed task is canceled or it has other exceptions, here we will add those + /// into the passed in exception list (which will be lazily initialized here). + /// </summary> + internal static void AddExceptionsForCompletedTask(ref List<Exception> exceptions, Task t) + { + AggregateException ex = t.GetExceptions(true); + if (ex != null) + { + // make sure the task's exception observed status is set appropriately + // it's possible that WaitAll was called by the parent of an attached child, + // this will make sure it won't throw again in the implicit wait + t.UpdateExceptionObservedStatus(); + + if (exceptions == null) + { + exceptions = new List<Exception>(ex.InnerExceptions.Count); + } + + exceptions.AddRange(ex.InnerExceptions); + } + } + + + /// <summary> + /// Waits for any of the provided <see cref="Task"/> objects to complete execution. + /// </summary> + /// <param name="tasks"> + /// An array of <see cref="Task"/> instances on which to wait. + /// </param> + /// <returns>The index of the completed task in the <paramref name="tasks"/> array argument.</returns> + /// <exception cref="T:System.ArgumentNullException"> + /// The <paramref name="tasks"/> argument is null. + /// </exception> + /// <exception cref="T:System.ArgumentException"> + /// The <paramref name="tasks"/> argument contains a null element. + /// </exception> + [MethodImpl(MethodImplOptions.NoOptimization)] // this is needed for the parallel debugger + public static int WaitAny(params Task[] tasks) + { + int waitResult = WaitAny(tasks, Timeout.Infinite); + Contract.Assert(tasks.Length == 0 || waitResult != -1, "expected wait to succeed"); + return waitResult; + } + + /// <summary> + /// Waits for any of the provided <see cref="Task"/> objects to complete execution. + /// </summary> + /// <param name="tasks"> + /// An array of <see cref="Task"/> instances on which to wait. + /// </param> + /// <param name="timeout"> + /// A <see cref="System.TimeSpan"/> that represents the number of milliseconds to wait, or a <see + /// cref="System.TimeSpan"/> that represents -1 milliseconds to wait indefinitely. + /// </param> + /// <returns> + /// The index of the completed task in the <paramref name="tasks"/> array argument, or -1 if the + /// timeout occurred. + /// </returns> + /// <exception cref="T:System.ArgumentNullException"> + /// The <paramref name="tasks"/> argument is null. + /// </exception> + /// <exception cref="T:System.ArgumentException"> + /// The <paramref name="tasks"/> argument contains a null element. + /// </exception> + /// <exception cref="T:System.ArgumentOutOfRangeException"> + /// <paramref name="timeout"/> is a negative number other than -1 milliseconds, which represents an + /// infinite time-out -or- timeout is greater than + /// <see cref="System.Int32.MaxValue"/>. + /// </exception> + [MethodImpl(MethodImplOptions.NoOptimization)] // this is needed for the parallel debugger + public static int WaitAny(Task[] tasks, TimeSpan timeout) + { + long totalMilliseconds = (long)timeout.TotalMilliseconds; + if (totalMilliseconds < -1 || totalMilliseconds > Int32.MaxValue) + { + ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.timeout); + } + + return WaitAny(tasks, (int)totalMilliseconds); + } + + /// <summary> + /// Waits for any of the provided <see cref="Task"/> objects to complete execution. + /// </summary> + /// <param name="tasks"> + /// An array of <see cref="Task"/> instances on which to wait. + /// </param> + /// <param name="cancellationToken"> + /// A <see cref="CancellationToken"/> to observe while waiting for a task to complete. + /// </param> + /// <returns> + /// The index of the completed task in the <paramref name="tasks"/> array argument. + /// </returns> + /// <exception cref="T:System.ArgumentNullException"> + /// The <paramref name="tasks"/> argument is null. + /// </exception> + /// <exception cref="T:System.ArgumentException"> + /// The <paramref name="tasks"/> argument contains a null element. + /// </exception> + /// <exception cref="T:System.OperationCanceledException"> + /// The <paramref name="cancellationToken"/> was canceled. + /// </exception> + [MethodImpl(MethodImplOptions.NoOptimization)] // this is needed for the parallel debugger + public static int WaitAny(Task[] tasks, CancellationToken cancellationToken) + { + return WaitAny(tasks, Timeout.Infinite, cancellationToken); + } + + /// <summary> + /// Waits for any of the provided <see cref="Task"/> objects to complete execution. + /// </summary> + /// <param name="tasks"> + /// An array of <see cref="Task"/> instances on which to wait. + /// </param> + /// <param name="millisecondsTimeout"> + /// The number of milliseconds to wait, or <see cref="System.Threading.Timeout.Infinite"/> (-1) to + /// wait indefinitely. + /// </param> + /// <returns> + /// The index of the completed task in the <paramref name="tasks"/> array argument, or -1 if the + /// timeout occurred. + /// </returns> + /// <exception cref="T:System.ArgumentNullException"> + /// The <paramref name="tasks"/> argument is null. + /// </exception> + /// <exception cref="T:System.ArgumentException"> + /// The <paramref name="tasks"/> argument contains a null element. + /// </exception> + /// <exception cref="T:System.ArgumentOutOfRangeException"> + /// <paramref name="millisecondsTimeout"/> is a negative number other than -1, which represents an + /// infinite time-out. + /// </exception> + [MethodImpl(MethodImplOptions.NoOptimization)] // this is needed for the parallel debugger + public static int WaitAny(Task[] tasks, int millisecondsTimeout) + { + return WaitAny(tasks, millisecondsTimeout, default(CancellationToken)); + } + + /// <summary> + /// Waits for any of the provided <see cref="Task"/> objects to complete execution. + /// </summary> + /// <param name="tasks"> + /// An array of <see cref="Task"/> instances on which to wait. + /// </param> + /// <param name="millisecondsTimeout"> + /// The number of milliseconds to wait, or <see cref="System.Threading.Timeout.Infinite"/> (-1) to + /// wait indefinitely. + /// </param> + /// <param name="cancellationToken"> + /// A <see cref="CancellationToken"/> to observe while waiting for a task to complete. + /// </param> + /// <returns> + /// The index of the completed task in the <paramref name="tasks"/> array argument, or -1 if the + /// timeout occurred. + /// </returns> + /// <exception cref="T:System.ArgumentNullException"> + /// The <paramref name="tasks"/> argument is null. + /// </exception> + /// <exception cref="T:System.ArgumentException"> + /// The <paramref name="tasks"/> argument contains a null element. + /// </exception> + /// <exception cref="T:System.ArgumentOutOfRangeException"> + /// <paramref name="millisecondsTimeout"/> is a negative number other than -1, which represents an + /// infinite time-out. + /// </exception> + /// <exception cref="T:System.OperationCanceledException"> + /// The <paramref name="cancellationToken"/> was canceled. + /// </exception> + [MethodImpl(MethodImplOptions.NoOptimization)] // this is needed for the parallel debugger + public static int WaitAny(Task[] tasks, int millisecondsTimeout, CancellationToken cancellationToken) + { + if (tasks == null) + { + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.tasks); + } + if (millisecondsTimeout < -1) + { + ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.millisecondsTimeout); + } + Contract.EndContractBlock(); + + cancellationToken.ThrowIfCancellationRequested(); // early check before we make any allocations + + int signaledTaskIndex = -1; + + // Make a pass through the loop to check for any tasks that may have + // already been completed, and to verify that no tasks are null. + + for (int taskIndex = 0; taskIndex < tasks.Length; taskIndex++) + { + Task task = tasks[taskIndex]; + + if (task == null) + { + ThrowHelper.ThrowArgumentException(ExceptionResource.Task_WaitMulti_NullTask, ExceptionArgument.tasks); + } + + if (signaledTaskIndex == -1 && task.IsCompleted) + { + // We found our first completed task. Store it, but we can't just return here, + // as we still need to validate the whole array for nulls. + signaledTaskIndex = taskIndex; + } + } + + if (signaledTaskIndex == -1 && tasks.Length != 0) + { + Task<Task> firstCompleted = TaskFactory.CommonCWAnyLogic(tasks); + bool waitCompleted = firstCompleted.Wait(millisecondsTimeout, cancellationToken); + if (waitCompleted) + { + Contract.Assert(firstCompleted.Status == TaskStatus.RanToCompletion); + signaledTaskIndex = Array.IndexOf(tasks, firstCompleted.Result); + Contract.Assert(signaledTaskIndex >= 0); + } + } + + // We need to prevent the tasks array from being GC'ed until we come out of the wait. + // This is necessary so that the Parallel Debugger can traverse it during the long wait + // and deduce waiter/waitee relationships + GC.KeepAlive(tasks); + + // Return the index + return signaledTaskIndex; + } + + #region FromResult / FromException / FromCanceled + + /// <summary>Creates a <see cref="Task{TResult}"/> that's completed successfully with the specified result.</summary> + /// <typeparam name="TResult">The type of the result returned by the task.</typeparam> + /// <param name="result">The result to store into the completed task.</param> + /// <returns>The successfully completed task.</returns> + public static Task<TResult> FromResult<TResult>(TResult result) + { + return new Task<TResult>(result); + } + + /// <summary>Creates a <see cref="Task{TResult}"/> that's completed exceptionally with the specified exception.</summary> + /// <typeparam name="TResult">The type of the result returned by the task.</typeparam> + /// <param name="exception">The exception with which to complete the task.</param> + /// <returns>The faulted task.</returns> + public static Task FromException(Exception exception) + { + return FromException<VoidTaskResult>(exception); + } + + /// <summary>Creates a <see cref="Task{TResult}"/> that's completed exceptionally with the specified exception.</summary> + /// <typeparam name="TResult">The type of the result returned by the task.</typeparam> + /// <param name="exception">The exception with which to complete the task.</param> + /// <returns>The faulted task.</returns> + public static Task<TResult> FromException<TResult>(Exception exception) + { + if (exception == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.exception); + Contract.EndContractBlock(); + + var task = new Task<TResult>(); + bool succeeded = task.TrySetException(exception); + Contract.Assert(succeeded, "This should always succeed on a new task."); + return task; + } + + /// <summary>Creates a <see cref="Task"/> that's completed due to cancellation with the specified token.</summary> + /// <param name="cancellationToken">The token with which to complete the task.</param> + /// <returns>The canceled task.</returns> + public static Task FromCanceled(CancellationToken cancellationToken) + { + if (!cancellationToken.IsCancellationRequested) + ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.cancellationToken); + Contract.EndContractBlock(); + return new Task(true, TaskCreationOptions.None, cancellationToken); + } + + /// <summary>Creates a <see cref="Task{TResult}"/> that's completed due to cancellation with the specified token.</summary> + /// <typeparam name="TResult">The type of the result returned by the task.</typeparam> + /// <param name="cancellationToken">The token with which to complete the task.</param> + /// <returns>The canceled task.</returns> + public static Task<TResult> FromCanceled<TResult>(CancellationToken cancellationToken) + { + if (!cancellationToken.IsCancellationRequested) + ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.cancellationToken); + Contract.EndContractBlock(); + return new Task<TResult>(true, default(TResult), TaskCreationOptions.None, cancellationToken); + } + + /// <summary>Creates a <see cref="Task{TResult}"/> that's completed due to cancellation with the specified exception.</summary> + /// <typeparam name="TResult">The type of the result returned by the task.</typeparam> + /// <param name="exception">The exception with which to complete the task.</param> + /// <returns>The canceled task.</returns> + internal static Task<TResult> FromCancellation<TResult>(OperationCanceledException exception) + { + if (exception == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.exception); + Contract.EndContractBlock(); + + var task = new Task<TResult>(); + bool succeeded = task.TrySetCanceled(exception.CancellationToken, exception); + Contract.Assert(succeeded, "This should always succeed on a new task."); + return task; + } + + /// <summary>Creates a <see cref="Task"/> that's completed due to cancellation with the specified token.</summary> + /// <param name="cancellationToken">The token with which to complete the task.</param> + /// <returns>The canceled task.</returns> + [FriendAccessAllowed] + internal static Task FromCancellation(CancellationToken cancellationToken) + { + return FromCanceled(cancellationToken); + } + + /// <summary>Creates a <see cref="Task{TResult}"/> that's completed due to cancellation with the specified token.</summary> + /// <typeparam name="TResult">The type of the result returned by the task.</typeparam> + /// <param name="cancellationToken">The token with which to complete the task.</param> + /// <returns>The canceled task.</returns> + [FriendAccessAllowed] + internal static Task<TResult> FromCancellation<TResult>(CancellationToken cancellationToken) + { + return FromCanceled<TResult>(cancellationToken); + } + + #endregion + + #region Run methods + + + /// <summary> + /// Queues the specified work to run on the ThreadPool and returns a Task handle for that work. + /// </summary> + /// <param name="action">The work to execute asynchronously</param> + /// <returns>A Task that represents the work queued to execute in the ThreadPool.</returns> + /// <exception cref="T:System.ArgumentNullException"> + /// The <paramref name="action"/> parameter was null. + /// </exception> + [MethodImplAttribute(MethodImplOptions.NoInlining)] // Methods containing StackCrawlMark local var have to be marked non-inlineable + public static Task Run(Action action) + { + StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller; + return Task.InternalStartNew(null, action, null, default(CancellationToken), TaskScheduler.Default, + TaskCreationOptions.DenyChildAttach, InternalTaskOptions.None, ref stackMark); + } + + /// <summary> + /// Queues the specified work to run on the ThreadPool and returns a Task handle for that work. + /// </summary> + /// <param name="action">The work to execute asynchronously</param> + /// <param name="cancellationToken">A cancellation token that should be used to cancel the work</param> + /// <returns>A Task that represents the work queued to execute in the ThreadPool.</returns> + /// <exception cref="T:System.ArgumentNullException"> + /// The <paramref name="action"/> parameter was null. + /// </exception> + /// <exception cref="T:System.ObjectDisposedException"> + /// The <see cref="T:System.CancellationTokenSource"/> associated with <paramref name="cancellationToken"/> was disposed. + /// </exception> + [MethodImplAttribute(MethodImplOptions.NoInlining)] // Methods containing StackCrawlMark local var have to be marked non-inlineable + public static Task Run(Action action, CancellationToken cancellationToken) + { + StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller; + return Task.InternalStartNew(null, action, null, cancellationToken, TaskScheduler.Default, + TaskCreationOptions.DenyChildAttach, InternalTaskOptions.None, ref stackMark); + } + + /// <summary> + /// Queues the specified work to run on the ThreadPool and returns a Task(TResult) handle for that work. + /// </summary> + /// <param name="function">The work to execute asynchronously</param> + /// <returns>A Task(TResult) that represents the work queued to execute in the ThreadPool.</returns> + /// <exception cref="T:System.ArgumentNullException"> + /// The <paramref name="function"/> parameter was null. + /// </exception> + [MethodImplAttribute(MethodImplOptions.NoInlining)] // Methods containing StackCrawlMark local var have to be marked non-inlineable + public static Task<TResult> Run<TResult>(Func<TResult> function) + { + StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller; + return Task<TResult>.StartNew(null, function, default(CancellationToken), + TaskCreationOptions.DenyChildAttach, InternalTaskOptions.None, TaskScheduler.Default, ref stackMark); + } + + /// <summary> + /// Queues the specified work to run on the ThreadPool and returns a Task(TResult) handle for that work. + /// </summary> + /// <param name="function">The work to execute asynchronously</param> + /// <param name="cancellationToken">A cancellation token that should be used to cancel the work</param> + /// <returns>A Task(TResult) that represents the work queued to execute in the ThreadPool.</returns> + /// <exception cref="T:System.ArgumentNullException"> + /// The <paramref name="function"/> parameter was null. + /// </exception> + /// <exception cref="T:System.ObjectDisposedException"> + /// The <see cref="T:System.CancellationTokenSource"/> associated with <paramref name="cancellationToken"/> was disposed. + /// </exception> + [MethodImplAttribute(MethodImplOptions.NoInlining)] // Methods containing StackCrawlMark local var have to be marked non-inlineable + public static Task<TResult> Run<TResult>(Func<TResult> function, CancellationToken cancellationToken) + { + StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller; + return Task<TResult>.StartNew(null, function, cancellationToken, + TaskCreationOptions.DenyChildAttach, InternalTaskOptions.None, TaskScheduler.Default, ref stackMark); + } + + /// <summary> + /// Queues the specified work to run on the ThreadPool and returns a proxy for the + /// Task returned by <paramref name="function"/>. + /// </summary> + /// <param name="function">The work to execute asynchronously</param> + /// <returns>A Task that represents a proxy for the Task returned by <paramref name="function"/>.</returns> + /// <exception cref="T:System.ArgumentNullException"> + /// The <paramref name="function"/> parameter was null. + /// </exception> + public static Task Run(Func<Task> function) + { + return Run(function, default(CancellationToken)); + } + + + /// <summary> + /// Queues the specified work to run on the ThreadPool and returns a proxy for the + /// Task returned by <paramref name="function"/>. + /// </summary> + /// <param name="function">The work to execute asynchronously</param> + /// <param name="cancellationToken">A cancellation token that should be used to cancel the work</param> + /// <returns>A Task that represents a proxy for the Task returned by <paramref name="function"/>.</returns> + /// <exception cref="T:System.ArgumentNullException"> + /// The <paramref name="function"/> parameter was null. + /// </exception> + /// <exception cref="T:System.ObjectDisposedException"> + /// The <see cref="T:System.CancellationTokenSource"/> associated with <paramref name="cancellationToken"/> was disposed. + /// </exception> + public static Task Run(Func<Task> function, CancellationToken cancellationToken) + { + // Check arguments + if (function == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.function); + Contract.EndContractBlock(); + + if (AppContextSwitches.ThrowExceptionIfDisposedCancellationTokenSource) + { + cancellationToken.ThrowIfSourceDisposed(); + } + + // Short-circuit if we are given a pre-canceled token + if (cancellationToken.IsCancellationRequested) + return Task.FromCanceled(cancellationToken); + + // Kick off initial Task, which will call the user-supplied function and yield a Task. + Task<Task> task1 = Task<Task>.Factory.StartNew(function, cancellationToken, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default); + + // Create a promise-style Task to be used as a proxy for the operation + // Set lookForOce == true so that unwrap logic can be on the lookout for OCEs thrown as faults from task1, to support in-delegate cancellation. + UnwrapPromise<VoidTaskResult> promise = new UnwrapPromise<VoidTaskResult>(task1, lookForOce: true); + + return promise; + } + + /// <summary> + /// Queues the specified work to run on the ThreadPool and returns a proxy for the + /// Task(TResult) returned by <paramref name="function"/>. + /// </summary> + /// <typeparam name="TResult">The type of the result returned by the proxy Task.</typeparam> + /// <param name="function">The work to execute asynchronously</param> + /// <returns>A Task(TResult) that represents a proxy for the Task(TResult) returned by <paramref name="function"/>.</returns> + /// <exception cref="T:System.ArgumentNullException"> + /// The <paramref name="function"/> parameter was null. + /// </exception> + public static Task<TResult> Run<TResult>(Func<Task<TResult>> function) + { + return Run(function, default(CancellationToken)); + } + + /// <summary> + /// Queues the specified work to run on the ThreadPool and returns a proxy for the + /// Task(TResult) returned by <paramref name="function"/>. + /// </summary> + /// <typeparam name="TResult">The type of the result returned by the proxy Task.</typeparam> + /// <param name="function">The work to execute asynchronously</param> + /// <param name="cancellationToken">A cancellation token that should be used to cancel the work</param> + /// <returns>A Task(TResult) that represents a proxy for the Task(TResult) returned by <paramref name="function"/>.</returns> + /// <exception cref="T:System.ArgumentNullException"> + /// The <paramref name="function"/> parameter was null. + /// </exception> + public static Task<TResult> Run<TResult>(Func<Task<TResult>> function, CancellationToken cancellationToken) + { + // Check arguments + if (function == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.function); + Contract.EndContractBlock(); + + if (AppContextSwitches.ThrowExceptionIfDisposedCancellationTokenSource) + { + cancellationToken.ThrowIfSourceDisposed(); + } + + // Short-circuit if we are given a pre-canceled token + if (cancellationToken.IsCancellationRequested) + return Task.FromCanceled<TResult>(cancellationToken); + + // Kick off initial Task, which will call the user-supplied function and yield a Task. + Task<Task<TResult>> task1 = Task<Task<TResult>>.Factory.StartNew(function, cancellationToken, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default); + + // Create a promise-style Task to be used as a proxy for the operation + // Set lookForOce == true so that unwrap logic can be on the lookout for OCEs thrown as faults from task1, to support in-delegate cancellation. + UnwrapPromise<TResult> promise = new UnwrapPromise<TResult>(task1, lookForOce: true); + + return promise; + } + + + #endregion + + #region Delay methods + + /// <summary> + /// Creates a Task that will complete after a time delay. + /// </summary> + /// <param name="delay">The time span to wait before completing the returned Task</param> + /// <returns>A Task that represents the time delay</returns> + /// <exception cref="T:System.ArgumentOutOfRangeException"> + /// The <paramref name="delay"/> is less than -1 or greater than Int32.MaxValue. + /// </exception> + /// <remarks> + /// After the specified time delay, the Task is completed in RanToCompletion state. + /// </remarks> + public static Task Delay(TimeSpan delay) + { + return Delay(delay, default(CancellationToken)); + } + + /// <summary> + /// Creates a Task that will complete after a time delay. + /// </summary> + /// <param name="delay">The time span to wait before completing the returned Task</param> + /// <param name="cancellationToken">The cancellation token that will be checked prior to completing the returned Task</param> + /// <returns>A Task that represents the time delay</returns> + /// <exception cref="T:System.ArgumentOutOfRangeException"> + /// The <paramref name="delay"/> is less than -1 or greater than Int32.MaxValue. + /// </exception> + /// <exception cref="T:System.ObjectDisposedException"> + /// The provided <paramref name="cancellationToken"/> has already been disposed. + /// </exception> + /// <remarks> + /// If the cancellation token is signaled before the specified time delay, then the Task is completed in + /// Canceled state. Otherwise, the Task is completed in RanToCompletion state once the specified time + /// delay has expired. + /// </remarks> + public static Task Delay(TimeSpan delay, CancellationToken cancellationToken) + { + long totalMilliseconds = (long)delay.TotalMilliseconds; + if (totalMilliseconds < -1 || totalMilliseconds > Int32.MaxValue) + { + ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.delay, ExceptionResource.Task_Delay_InvalidDelay); + } + + return Delay((int)totalMilliseconds, cancellationToken); + } + + /// <summary> + /// Creates a Task that will complete after a time delay. + /// </summary> + /// <param name="millisecondsDelay">The number of milliseconds to wait before completing the returned Task</param> + /// <returns>A Task that represents the time delay</returns> + /// <exception cref="T:System.ArgumentOutOfRangeException"> + /// The <paramref name="millisecondsDelay"/> is less than -1. + /// </exception> + /// <remarks> + /// After the specified time delay, the Task is completed in RanToCompletion state. + /// </remarks> + public static Task Delay(int millisecondsDelay) + { + return Delay(millisecondsDelay, default(CancellationToken)); + } + + /// <summary> + /// Creates a Task that will complete after a time delay. + /// </summary> + /// <param name="millisecondsDelay">The number of milliseconds to wait before completing the returned Task</param> + /// <param name="cancellationToken">The cancellation token that will be checked prior to completing the returned Task</param> + /// <returns>A Task that represents the time delay</returns> + /// <exception cref="T:System.ArgumentOutOfRangeException"> + /// The <paramref name="millisecondsDelay"/> is less than -1. + /// </exception> + /// <exception cref="T:System.ObjectDisposedException"> + /// The provided <paramref name="cancellationToken"/> has already been disposed. + /// </exception> + /// <remarks> + /// If the cancellation token is signaled before the specified time delay, then the Task is completed in + /// Canceled state. Otherwise, the Task is completed in RanToCompletion state once the specified time + /// delay has expired. + /// </remarks> + public static Task Delay(int millisecondsDelay, CancellationToken cancellationToken) + { + // Throw on non-sensical time + if (millisecondsDelay < -1) + { + ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.millisecondsDelay, ExceptionResource.Task_Delay_InvalidMillisecondsDelay); + } + Contract.EndContractBlock(); + + // some short-cuts in case quick completion is in order + if (cancellationToken.IsCancellationRequested) + { + // return a Task created as already-Canceled + return Task.FromCanceled(cancellationToken); + } + else if (millisecondsDelay == 0) + { + // return a Task created as already-RanToCompletion + return Task.CompletedTask; + } + + // Construct a promise-style Task to encapsulate our return value + var promise = new DelayPromise(cancellationToken); + + // Register our cancellation token, if necessary. + if (cancellationToken.CanBeCanceled) + { + promise.Registration = cancellationToken.InternalRegisterWithoutEC(state => ((DelayPromise)state).Complete(), promise); + } + + // ... and create our timer and make sure that it stays rooted. + if (millisecondsDelay != Timeout.Infinite) // no need to create the timer if it's an infinite timeout + { + promise.Timer = new Timer(state => ((DelayPromise)state).Complete(), promise, millisecondsDelay, Timeout.Infinite); + promise.Timer.KeepRootedWhileScheduled(); + } + + // Return the timer proxy task + return promise; + } + + /// <summary>Task that also stores the completion closure and logic for Task.Delay implementation.</summary> + private sealed class DelayPromise : Task<VoidTaskResult> + { + internal DelayPromise(CancellationToken token) + : base() + { + this.Token = token; + if (AsyncCausalityTracer.LoggingOn) + AsyncCausalityTracer.TraceOperationCreation(CausalityTraceLevel.Required, this.Id, "Task.Delay", 0); + + if (Task.s_asyncDebuggingEnabled) + { + AddToActiveTasks(this); + } + } + + internal readonly CancellationToken Token; + internal CancellationTokenRegistration Registration; + internal Timer Timer; + + internal void Complete() + { + // Transition the task to completed. + bool setSucceeded; + + if (Token.IsCancellationRequested) + { + setSucceeded = TrySetCanceled(Token); + } + else + { + if (AsyncCausalityTracer.LoggingOn) + AsyncCausalityTracer.TraceOperationCompletion(CausalityTraceLevel.Required, this.Id, AsyncCausalityStatus.Completed); + + if (Task.s_asyncDebuggingEnabled) + { + RemoveFromActiveTasks(this.Id); + } + setSucceeded = TrySetResult(default(VoidTaskResult)); + } + + // If we set the value, also clean up. + if (setSucceeded) + { + if (Timer != null) Timer.Dispose(); + Registration.Dispose(); + } + } + } + #endregion + + #region WhenAll + /// <summary> + /// Creates a task that will complete when all of the supplied tasks have completed. + /// </summary> + /// <param name="tasks">The tasks to wait on for completion.</param> + /// <returns>A task that represents the completion of all of the supplied tasks.</returns> + /// <remarks> + /// <para> + /// If any of the supplied tasks completes in a faulted state, the returned task will also complete in a Faulted state, + /// where its exceptions will contain the aggregation of the set of unwrapped exceptions from each of the supplied tasks. + /// </para> + /// <para> + /// If none of the supplied tasks faulted but at least one of them was canceled, the returned task will end in the Canceled state. + /// </para> + /// <para> + /// If none of the tasks faulted and none of the tasks were canceled, the resulting task will end in the RanToCompletion state. + /// </para> + /// <para> + /// If the supplied array/enumerable contains no tasks, the returned task will immediately transition to a RanToCompletion + /// state before it's returned to the caller. + /// </para> + /// </remarks> + /// <exception cref="T:System.ArgumentNullException"> + /// The <paramref name="tasks"/> argument was null. + /// </exception> + /// <exception cref="T:System.ArgumentException"> + /// The <paramref name="tasks"/> collection contained a null task. + /// </exception> + public static Task WhenAll(IEnumerable<Task> tasks) + { + // Take a more efficient path if tasks is actually an array + Task[] taskArray = tasks as Task[]; + if (taskArray != null) + { + return WhenAll(taskArray); + } + + // Skip a List allocation/copy if tasks is a collection + ICollection<Task> taskCollection = tasks as ICollection<Task>; + if (taskCollection != null) + { + int index = 0; + taskArray = new Task[taskCollection.Count]; + foreach (var task in tasks) + { + if (task == null) ThrowHelper.ThrowArgumentException(ExceptionResource.Task_MultiTaskContinuation_NullTask, ExceptionArgument.tasks); + taskArray[index++] = task; + } + return InternalWhenAll(taskArray); + } + + // Do some argument checking and convert tasks to a List (and later an array). + if (tasks == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.tasks); + List<Task> taskList = new List<Task>(); + foreach (Task task in tasks) + { + if (task == null) ThrowHelper.ThrowArgumentException(ExceptionResource.Task_MultiTaskContinuation_NullTask, ExceptionArgument.tasks); + taskList.Add(task); + } + + // Delegate the rest to InternalWhenAll() + return InternalWhenAll(taskList.ToArray()); + } + + /// <summary> + /// Creates a task that will complete when all of the supplied tasks have completed. + /// </summary> + /// <param name="tasks">The tasks to wait on for completion.</param> + /// <returns>A task that represents the completion of all of the supplied tasks.</returns> + /// <remarks> + /// <para> + /// If any of the supplied tasks completes in a faulted state, the returned task will also complete in a Faulted state, + /// where its exceptions will contain the aggregation of the set of unwrapped exceptions from each of the supplied tasks. + /// </para> + /// <para> + /// If none of the supplied tasks faulted but at least one of them was canceled, the returned task will end in the Canceled state. + /// </para> + /// <para> + /// If none of the tasks faulted and none of the tasks were canceled, the resulting task will end in the RanToCompletion state. + /// </para> + /// <para> + /// If the supplied array/enumerable contains no tasks, the returned task will immediately transition to a RanToCompletion + /// state before it's returned to the caller. + /// </para> + /// </remarks> + /// <exception cref="T:System.ArgumentNullException"> + /// The <paramref name="tasks"/> argument was null. + /// </exception> + /// <exception cref="T:System.ArgumentException"> + /// The <paramref name="tasks"/> array contained a null task. + /// </exception> + public static Task WhenAll(params Task[] tasks) + { + // Do some argument checking and make a defensive copy of the tasks array + if (tasks == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.tasks); + Contract.EndContractBlock(); + + int taskCount = tasks.Length; + if (taskCount == 0) return InternalWhenAll(tasks); // Small optimization in the case of an empty array. + + Task[] tasksCopy = new Task[taskCount]; + for (int i = 0; i < taskCount; i++) + { + Task task = tasks[i]; + if (task == null) ThrowHelper.ThrowArgumentException(ExceptionResource.Task_MultiTaskContinuation_NullTask, ExceptionArgument.tasks); + tasksCopy[i] = task; + } + + // The rest can be delegated to InternalWhenAll() + return InternalWhenAll(tasksCopy); + } + + // Some common logic to support WhenAll() methods + // tasks should be a defensive copy. + private static Task InternalWhenAll(Task[] tasks) + { + Contract.Requires(tasks != null, "Expected a non-null tasks array"); + return (tasks.Length == 0) ? // take shortcut if there are no tasks upon which to wait + Task.CompletedTask : + new WhenAllPromise(tasks); + } + + // A Task<VoidTaskResult> that gets completed when all of its constituent tasks complete. + // Completion logic will analyze the antecedents in order to choose completion status. + // This type allows us to replace this logic: + // Task<VoidTaskResult> promise = new Task<VoidTaskResult>(...); + // Action<Task> completionAction = delegate { <completion logic>}; + // TaskFactory.CommonCWAllLogic(tasksCopy).AddCompletionAction(completionAction); + // return promise; + // which involves several allocations, with this logic: + // return new WhenAllPromise(tasksCopy); + // which saves a couple of allocations and enables debugger notification specialization. + // + // Used in InternalWhenAll(Task[]) + private sealed class WhenAllPromise : Task<VoidTaskResult>, ITaskCompletionAction + { + /// <summary> + /// Stores all of the constituent tasks. Tasks clear themselves out of this + /// array as they complete, but only if they don't have their wait notification bit set. + /// </summary> + private readonly Task[] m_tasks; + /// <summary>The number of tasks remaining to complete.</summary> + private int m_count; + + internal WhenAllPromise(Task[] tasks) : + base() + { + Contract.Requires(tasks != null, "Expected a non-null task array"); + Contract.Requires(tasks.Length > 0, "Expected a non-zero length task array"); + + if (AsyncCausalityTracer.LoggingOn) + AsyncCausalityTracer.TraceOperationCreation(CausalityTraceLevel.Required, this.Id, "Task.WhenAll", 0); + + if (s_asyncDebuggingEnabled) + { + AddToActiveTasks(this); + } + + m_tasks = tasks; + m_count = tasks.Length; + + foreach (var task in tasks) + { + if (task.IsCompleted) this.Invoke(task); // short-circuit the completion action, if possible + else task.AddCompletionAction(this); // simple completion action + } + } + + public void Invoke(Task completedTask) + { + if (AsyncCausalityTracer.LoggingOn) + AsyncCausalityTracer.TraceOperationRelation(CausalityTraceLevel.Important, this.Id, CausalityRelation.Join); + + // Decrement the count, and only continue to complete the promise if we're the last one. + if (Interlocked.Decrement(ref m_count) == 0) + { + // Set up some accounting variables + List<ExceptionDispatchInfo> observedExceptions = null; + Task canceledTask = null; + + // Loop through antecedents: + // If any one of them faults, the result will be faulted + // If none fault, but at least one is canceled, the result will be canceled + // If none fault or are canceled, then result will be RanToCompletion + for (int i = 0; i < m_tasks.Length; i++) + { + var task = m_tasks[i]; + Contract.Assert(task != null, "Constituent task in WhenAll should never be null"); + + if (task.IsFaulted) + { + if (observedExceptions == null) observedExceptions = new List<ExceptionDispatchInfo>(); + observedExceptions.AddRange(task.GetExceptionDispatchInfos()); + } + else if (task.IsCanceled) + { + if (canceledTask == null) canceledTask = task; // use the first task that's canceled + } + + // Regardless of completion state, if the task has its debug bit set, transfer it to the + // WhenAll task. We must do this before we complete the task. + if (task.IsWaitNotificationEnabled) this.SetNotificationForWaitCompletion(enabled: true); + else m_tasks[i] = null; // avoid holding onto tasks unnecessarily + } + + if (observedExceptions != null) + { + Contract.Assert(observedExceptions.Count > 0, "Expected at least one exception"); + + //We don't need to TraceOperationCompleted here because TrySetException will call Finish and we'll log it there + + TrySetException(observedExceptions); + } + else if (canceledTask != null) + { + TrySetCanceled(canceledTask.CancellationToken, canceledTask.GetCancellationExceptionDispatchInfo()); + } + else + { + if (AsyncCausalityTracer.LoggingOn) + AsyncCausalityTracer.TraceOperationCompletion(CausalityTraceLevel.Required, this.Id, AsyncCausalityStatus.Completed); + + if (Task.s_asyncDebuggingEnabled) + { + RemoveFromActiveTasks(this.Id); + } + TrySetResult(default(VoidTaskResult)); + } + } + Contract.Assert(m_count >= 0, "Count should never go below 0"); + } + + public bool InvokeMayRunArbitraryCode { get { return true; } } + + /// <summary> + /// Returns whether we should notify the debugger of a wait completion. This returns + /// true iff at least one constituent task has its bit set. + /// </summary> + internal override bool ShouldNotifyDebuggerOfWaitCompletion + { + get + { + return + base.ShouldNotifyDebuggerOfWaitCompletion && + Task.AnyTaskRequiresNotifyDebuggerOfWaitCompletion(m_tasks); + } + } + } + + /// <summary> + /// Creates a task that will complete when all of the supplied tasks have completed. + /// </summary> + /// <param name="tasks">The tasks to wait on for completion.</param> + /// <returns>A task that represents the completion of all of the supplied tasks.</returns> + /// <remarks> + /// <para> + /// If any of the supplied tasks completes in a faulted state, the returned task will also complete in a Faulted state, + /// where its exceptions will contain the aggregation of the set of unwrapped exceptions from each of the supplied tasks. + /// </para> + /// <para> + /// If none of the supplied tasks faulted but at least one of them was canceled, the returned task will end in the Canceled state. + /// </para> + /// <para> + /// If none of the tasks faulted and none of the tasks were canceled, the resulting task will end in the RanToCompletion state. + /// The Result of the returned task will be set to an array containing all of the results of the + /// supplied tasks in the same order as they were provided (e.g. if the input tasks array contained t1, t2, t3, the output + /// task's Result will return an TResult[] where arr[0] == t1.Result, arr[1] == t2.Result, and arr[2] == t3.Result). + /// </para> + /// <para> + /// If the supplied array/enumerable contains no tasks, the returned task will immediately transition to a RanToCompletion + /// state before it's returned to the caller. The returned TResult[] will be an array of 0 elements. + /// </para> + /// </remarks> + /// <exception cref="T:System.ArgumentNullException"> + /// The <paramref name="tasks"/> argument was null. + /// </exception> + /// <exception cref="T:System.ArgumentException"> + /// The <paramref name="tasks"/> collection contained a null task. + /// </exception> + public static Task<TResult[]> WhenAll<TResult>(IEnumerable<Task<TResult>> tasks) + { + // Take a more efficient route if tasks is actually an array + Task<TResult>[] taskArray = tasks as Task<TResult>[]; + if (taskArray != null) + { + return WhenAll<TResult>(taskArray); + } + + // Skip a List allocation/copy if tasks is a collection + ICollection<Task<TResult>> taskCollection = tasks as ICollection<Task<TResult>>; + if (taskCollection != null) + { + int index = 0; + taskArray = new Task<TResult>[taskCollection.Count]; + foreach (var task in tasks) + { + if (task == null) ThrowHelper.ThrowArgumentException(ExceptionResource.Task_MultiTaskContinuation_NullTask, ExceptionArgument.tasks); + taskArray[index++] = task; + } + return InternalWhenAll<TResult>(taskArray); + } + + // Do some argument checking and convert tasks into a List (later an array) + if (tasks == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.tasks); + List<Task<TResult>> taskList = new List<Task<TResult>>(); + foreach (Task<TResult> task in tasks) + { + if (task == null) ThrowHelper.ThrowArgumentException(ExceptionResource.Task_MultiTaskContinuation_NullTask, ExceptionArgument.tasks); + taskList.Add(task); + } + + // Delegate the rest to InternalWhenAll<TResult>(). + return InternalWhenAll<TResult>(taskList.ToArray()); + } + + /// <summary> + /// Creates a task that will complete when all of the supplied tasks have completed. + /// </summary> + /// <param name="tasks">The tasks to wait on for completion.</param> + /// <returns>A task that represents the completion of all of the supplied tasks.</returns> + /// <remarks> + /// <para> + /// If any of the supplied tasks completes in a faulted state, the returned task will also complete in a Faulted state, + /// where its exceptions will contain the aggregation of the set of unwrapped exceptions from each of the supplied tasks. + /// </para> + /// <para> + /// If none of the supplied tasks faulted but at least one of them was canceled, the returned task will end in the Canceled state. + /// </para> + /// <para> + /// If none of the tasks faulted and none of the tasks were canceled, the resulting task will end in the RanToCompletion state. + /// The Result of the returned task will be set to an array containing all of the results of the + /// supplied tasks in the same order as they were provided (e.g. if the input tasks array contained t1, t2, t3, the output + /// task's Result will return an TResult[] where arr[0] == t1.Result, arr[1] == t2.Result, and arr[2] == t3.Result). + /// </para> + /// <para> + /// If the supplied array/enumerable contains no tasks, the returned task will immediately transition to a RanToCompletion + /// state before it's returned to the caller. The returned TResult[] will be an array of 0 elements. + /// </para> + /// </remarks> + /// <exception cref="T:System.ArgumentNullException"> + /// The <paramref name="tasks"/> argument was null. + /// </exception> + /// <exception cref="T:System.ArgumentException"> + /// The <paramref name="tasks"/> array contained a null task. + /// </exception> + public static Task<TResult[]> WhenAll<TResult>(params Task<TResult>[] tasks) + { + // Do some argument checking and make a defensive copy of the tasks array + if (tasks == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.tasks); + Contract.EndContractBlock(); + + int taskCount = tasks.Length; + if (taskCount == 0) return InternalWhenAll<TResult>(tasks); // small optimization in the case of an empty task array + + Task<TResult>[] tasksCopy = new Task<TResult>[taskCount]; + for (int i = 0; i < taskCount; i++) + { + Task<TResult> task = tasks[i]; + if (task == null) ThrowHelper.ThrowArgumentException(ExceptionResource.Task_MultiTaskContinuation_NullTask, ExceptionArgument.tasks); + tasksCopy[i] = task; + } + + // Delegate the rest to InternalWhenAll<TResult>() + return InternalWhenAll<TResult>(tasksCopy); + } + + // Some common logic to support WhenAll<TResult> methods + private static Task<TResult[]> InternalWhenAll<TResult>(Task<TResult>[] tasks) + { + Contract.Requires(tasks != null, "Expected a non-null tasks array"); + return (tasks.Length == 0) ? // take shortcut if there are no tasks upon which to wait + new Task<TResult[]>(false, new TResult[0], TaskCreationOptions.None, default(CancellationToken)) : + new WhenAllPromise<TResult>(tasks); + } + + // A Task<T> that gets completed when all of its constituent tasks complete. + // Completion logic will analyze the antecedents in order to choose completion status. + // See comments for non-generic version of WhenAllPromise class. + // + // Used in InternalWhenAll<TResult>(Task<TResult>[]) + private sealed class WhenAllPromise<T> : Task<T[]>, ITaskCompletionAction + { + /// <summary> + /// Stores all of the constituent tasks. Tasks clear themselves out of this + /// array as they complete, but only if they don't have their wait notification bit set. + /// </summary> + private readonly Task<T>[] m_tasks; + /// <summary>The number of tasks remaining to complete.</summary> + private int m_count; + + internal WhenAllPromise(Task<T>[] tasks) : + base() + { + Contract.Requires(tasks != null, "Expected a non-null task array"); + Contract.Requires(tasks.Length > 0, "Expected a non-zero length task array"); + + m_tasks = tasks; + m_count = tasks.Length; + + if (AsyncCausalityTracer.LoggingOn) + AsyncCausalityTracer.TraceOperationCreation(CausalityTraceLevel.Required, this.Id, "Task.WhenAll", 0); + + if (s_asyncDebuggingEnabled) + { + AddToActiveTasks(this); + } + + foreach (var task in tasks) + { + if (task.IsCompleted) this.Invoke(task); // short-circuit the completion action, if possible + else task.AddCompletionAction(this); // simple completion action + } + } + + public void Invoke(Task ignored) + { + if (AsyncCausalityTracer.LoggingOn) + AsyncCausalityTracer.TraceOperationRelation(CausalityTraceLevel.Important, this.Id, CausalityRelation.Join); + + // Decrement the count, and only continue to complete the promise if we're the last one. + if (Interlocked.Decrement(ref m_count) == 0) + { + // Set up some accounting variables + T[] results = new T[m_tasks.Length]; + List<ExceptionDispatchInfo> observedExceptions = null; + Task canceledTask = null; + + // Loop through antecedents: + // If any one of them faults, the result will be faulted + // If none fault, but at least one is canceled, the result will be canceled + // If none fault or are canceled, then result will be RanToCompletion + for (int i = 0; i < m_tasks.Length; i++) + { + Task<T> task = m_tasks[i]; + Contract.Assert(task != null, "Constituent task in WhenAll should never be null"); + + if (task.IsFaulted) + { + if (observedExceptions == null) observedExceptions = new List<ExceptionDispatchInfo>(); + observedExceptions.AddRange(task.GetExceptionDispatchInfos()); + } + else if (task.IsCanceled) + { + if (canceledTask == null) canceledTask = task; // use the first task that's canceled + } + else + { + Contract.Assert(task.Status == TaskStatus.RanToCompletion); + results[i] = task.GetResultCore(waitCompletionNotification: false); // avoid Result, which would triggering debug notification + } + + // Regardless of completion state, if the task has its debug bit set, transfer it to the + // WhenAll task. We must do this before we complete the task. + if (task.IsWaitNotificationEnabled) this.SetNotificationForWaitCompletion(enabled: true); + else m_tasks[i] = null; // avoid holding onto tasks unnecessarily + } + + if (observedExceptions != null) + { + Contract.Assert(observedExceptions.Count > 0, "Expected at least one exception"); + + //We don't need to TraceOperationCompleted here because TrySetException will call Finish and we'll log it there + + TrySetException(observedExceptions); + } + else if (canceledTask != null) + { + TrySetCanceled(canceledTask.CancellationToken, canceledTask.GetCancellationExceptionDispatchInfo()); + } + else + { + if (AsyncCausalityTracer.LoggingOn) + AsyncCausalityTracer.TraceOperationCompletion(CausalityTraceLevel.Required, this.Id, AsyncCausalityStatus.Completed); + + if (Task.s_asyncDebuggingEnabled) + { + RemoveFromActiveTasks(this.Id); + } + TrySetResult(results); + } + } + Contract.Assert(m_count >= 0, "Count should never go below 0"); + } + + public bool InvokeMayRunArbitraryCode { get { return true; } } + + /// <summary> + /// Returns whether we should notify the debugger of a wait completion. This returns true + /// iff at least one constituent task has its bit set. + /// </summary> + internal override bool ShouldNotifyDebuggerOfWaitCompletion + { + get + { + return + base.ShouldNotifyDebuggerOfWaitCompletion && + Task.AnyTaskRequiresNotifyDebuggerOfWaitCompletion(m_tasks); + } + } + } + #endregion + + #region WhenAny + /// <summary> + /// Creates a task that will complete when any of the supplied tasks have completed. + /// </summary> + /// <param name="tasks">The tasks to wait on for completion.</param> + /// <returns>A task that represents the completion of one of the supplied tasks. The return Task's Result is the task that completed.</returns> + /// <remarks> + /// The returned task will complete when any of the supplied tasks has completed. The returned task will always end in the RanToCompletion state + /// with its Result set to the first task to complete. This is true even if the first task to complete ended in the Canceled or Faulted state. + /// </remarks> + /// <exception cref="T:System.ArgumentNullException"> + /// The <paramref name="tasks"/> argument was null. + /// </exception> + /// <exception cref="T:System.ArgumentException"> + /// The <paramref name="tasks"/> array contained a null task, or was empty. + /// </exception> + public static Task<Task> WhenAny(params Task[] tasks) + { + if (tasks == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.tasks); + if (tasks.Length == 0) + { + ThrowHelper.ThrowArgumentException(ExceptionResource.Task_MultiTaskContinuation_EmptyTaskList, ExceptionArgument.tasks); + } + Contract.EndContractBlock(); + + // Make a defensive copy, as the user may manipulate the tasks array + // after we return but before the WhenAny asynchronously completes. + int taskCount = tasks.Length; + Task[] tasksCopy = new Task[taskCount]; + for (int i = 0; i < taskCount; i++) + { + Task task = tasks[i]; + if (task == null) ThrowHelper.ThrowArgumentException(ExceptionResource.Task_MultiTaskContinuation_NullTask, ExceptionArgument.tasks); + tasksCopy[i] = task; + } + + // Previously implemented CommonCWAnyLogic() can handle the rest + return TaskFactory.CommonCWAnyLogic(tasksCopy); + } + + /// <summary> + /// Creates a task that will complete when any of the supplied tasks have completed. + /// </summary> + /// <param name="tasks">The tasks to wait on for completion.</param> + /// <returns>A task that represents the completion of one of the supplied tasks. The return Task's Result is the task that completed.</returns> + /// <remarks> + /// The returned task will complete when any of the supplied tasks has completed. The returned task will always end in the RanToCompletion state + /// with its Result set to the first task to complete. This is true even if the first task to complete ended in the Canceled or Faulted state. + /// </remarks> + /// <exception cref="T:System.ArgumentNullException"> + /// The <paramref name="tasks"/> argument was null. + /// </exception> + /// <exception cref="T:System.ArgumentException"> + /// The <paramref name="tasks"/> collection contained a null task, or was empty. + /// </exception> + public static Task<Task> WhenAny(IEnumerable<Task> tasks) + { + if (tasks == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.tasks); + Contract.EndContractBlock(); + + // Make a defensive copy, as the user may manipulate the tasks collection + // after we return but before the WhenAny asynchronously completes. + List<Task> taskList = new List<Task>(); + foreach (Task task in tasks) + { + if (task == null) ThrowHelper.ThrowArgumentException(ExceptionResource.Task_MultiTaskContinuation_NullTask, ExceptionArgument.tasks); + taskList.Add(task); + } + + if (taskList.Count == 0) + { + ThrowHelper.ThrowArgumentException(ExceptionResource.Task_MultiTaskContinuation_EmptyTaskList, ExceptionArgument.tasks); + } + + // Previously implemented CommonCWAnyLogic() can handle the rest + return TaskFactory.CommonCWAnyLogic(taskList); + } + + /// <summary> + /// Creates a task that will complete when any of the supplied tasks have completed. + /// </summary> + /// <param name="tasks">The tasks to wait on for completion.</param> + /// <returns>A task that represents the completion of one of the supplied tasks. The return Task's Result is the task that completed.</returns> + /// <remarks> + /// The returned task will complete when any of the supplied tasks has completed. The returned task will always end in the RanToCompletion state + /// with its Result set to the first task to complete. This is true even if the first task to complete ended in the Canceled or Faulted state. + /// </remarks> + /// <exception cref="T:System.ArgumentNullException"> + /// The <paramref name="tasks"/> argument was null. + /// </exception> + /// <exception cref="T:System.ArgumentException"> + /// The <paramref name="tasks"/> array contained a null task, or was empty. + /// </exception> + public static Task<Task<TResult>> WhenAny<TResult>(params Task<TResult>[] tasks) + { + // We would just like to do this: + // return (Task<Task<TResult>>) WhenAny( (Task[]) tasks); + // but classes are not covariant to enable casting Task<TResult> to Task<Task<TResult>>. + + // Call WhenAny(Task[]) for basic functionality + Task<Task> intermediate = WhenAny((Task[])tasks); + + // Return a continuation task with the correct result type + return intermediate.ContinueWith(Task<TResult>.TaskWhenAnyCast, default(CancellationToken), + TaskContinuationOptions.ExecuteSynchronously | TaskContinuationOptions.DenyChildAttach, TaskScheduler.Default); + } + + /// <summary> + /// Creates a task that will complete when any of the supplied tasks have completed. + /// </summary> + /// <param name="tasks">The tasks to wait on for completion.</param> + /// <returns>A task that represents the completion of one of the supplied tasks. The return Task's Result is the task that completed.</returns> + /// <remarks> + /// The returned task will complete when any of the supplied tasks has completed. The returned task will always end in the RanToCompletion state + /// with its Result set to the first task to complete. This is true even if the first task to complete ended in the Canceled or Faulted state. + /// </remarks> + /// <exception cref="T:System.ArgumentNullException"> + /// The <paramref name="tasks"/> argument was null. + /// </exception> + /// <exception cref="T:System.ArgumentException"> + /// The <paramref name="tasks"/> collection contained a null task, or was empty. + /// </exception> + public static Task<Task<TResult>> WhenAny<TResult>(IEnumerable<Task<TResult>> tasks) + { + // We would just like to do this: + // return (Task<Task<TResult>>) WhenAny( (IEnumerable<Task>) tasks); + // but classes are not covariant to enable casting Task<TResult> to Task<Task<TResult>>. + + // Call WhenAny(IEnumerable<Task>) for basic functionality + Task<Task> intermediate = WhenAny((IEnumerable<Task>)tasks); + + // Return a continuation task with the correct result type + return intermediate.ContinueWith(Task<TResult>.TaskWhenAnyCast, default(CancellationToken), + TaskContinuationOptions.ExecuteSynchronously | TaskContinuationOptions.DenyChildAttach, TaskScheduler.Default); + } + #endregion + + [FriendAccessAllowed] + internal static Task<TResult> CreateUnwrapPromise<TResult>(Task outerTask, bool lookForOce) + { + Contract.Requires(outerTask != null); + + return new UnwrapPromise<TResult>(outerTask, lookForOce); + } + + internal virtual Delegate[] GetDelegateContinuationsForDebugger() + { + //Avoid an infinite loop by making sure the continuation object is not a reference to istelf. + if (this.m_continuationObject != this) + return GetDelegatesFromContinuationObject(this.m_continuationObject); + else + return null; + } + + internal static Delegate[] GetDelegatesFromContinuationObject(object continuationObject) + { + if (continuationObject != null) + { + Action singleAction = continuationObject as Action; + if (singleAction != null) + { + return new Delegate[] { AsyncMethodBuilderCore.TryGetStateMachineForDebugger(singleAction) }; + } + + TaskContinuation taskContinuation = continuationObject as TaskContinuation; + if (taskContinuation != null) + { + return taskContinuation.GetDelegateContinuationsForDebugger(); + } + + Task continuationTask = continuationObject as Task; + if (continuationTask != null) + { + Contract.Assert(continuationTask.m_action == null); + Delegate[] delegates = continuationTask.GetDelegateContinuationsForDebugger(); + if (delegates != null) + return delegates; + } + + //We need this ITaskCompletionAction after the Task because in the case of UnwrapPromise + //the VS debugger is more interested in the continuation than the internal invoke() + ITaskCompletionAction singleCompletionAction = continuationObject as ITaskCompletionAction; + if (singleCompletionAction != null) + { + return new Delegate[] { new Action<Task>(singleCompletionAction.Invoke) }; + } + + List<object> continuationList = continuationObject as List<object>; + if (continuationList != null) + { + List<Delegate> result = new List<Delegate>(); + foreach (object obj in continuationList) + { + var innerDelegates = GetDelegatesFromContinuationObject(obj); + if (innerDelegates != null) + { + foreach (var del in innerDelegates) + { + if (del != null) + result.Add(del); + } + } + } + + return result.ToArray(); + } + } + + return null; + } + + private static Task GetActiveTaskFromId(int taskId) + { + Task task = null; + s_currentActiveTasks.TryGetValue(taskId, out task); + return task; + } + + private static Task[] GetActiveTasks() + { + + return new List<Task>(s_currentActiveTasks.Values).ToArray(); + } + + + } + + internal sealed class CompletionActionInvoker : IThreadPoolWorkItem + { + private readonly ITaskCompletionAction m_action; + private readonly Task m_completingTask; + + internal CompletionActionInvoker(ITaskCompletionAction action, Task completingTask) + { + m_action = action; + m_completingTask = completingTask; + } + + [SecurityCritical] + void IThreadPoolWorkItem.ExecuteWorkItem() + { + m_action.Invoke(m_completingTask); + } + + [SecurityCritical] + void IThreadPoolWorkItem.MarkAborted(ThreadAbortException tae) + { + /* NOP */ + } + } + + // Proxy class for better debugging experience + internal class SystemThreadingTasks_TaskDebugView + { + private Task m_task; + + public SystemThreadingTasks_TaskDebugView(Task task) + { + m_task = task; + } + + public object AsyncState { get { return m_task.AsyncState; } } + public TaskCreationOptions CreationOptions { get { return m_task.CreationOptions; } } + public Exception Exception { get { return m_task.Exception; } } + public int Id { get { return m_task.Id; } } + public bool CancellationPending { get { return (m_task.Status == TaskStatus.WaitingToRun) && m_task.CancellationToken.IsCancellationRequested; } } + public TaskStatus Status { get { return m_task.Status; } } + } + + // Special purpose derivation of Task that supports limited replication through + // overriding the ShouldReplicate() method. This is used by the Parallel.For/ForEach + // methods. + internal class ParallelForReplicatingTask : Task + { + // Member variables + private int m_replicationDownCount; // downcounter to control replication + + // + // Constructors + // + + [MethodImplAttribute(MethodImplOptions.NoInlining)] // Methods containing StackCrawlMark local var have to be marked non-inlineable + internal ParallelForReplicatingTask( + ParallelOptions parallelOptions, Action action, TaskCreationOptions creationOptions, InternalTaskOptions internalOptions) + : base(action, null, Task.InternalCurrent, default(CancellationToken), creationOptions, internalOptions | InternalTaskOptions.SelfReplicating, null) + { + // Compute the down count based on scheduler/DOP info in parallelOptions. + m_replicationDownCount = parallelOptions.EffectiveMaxConcurrencyLevel; + + StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller; + PossiblyCaptureContext(ref stackMark); + } + + + // Controls degree of replication. If downcounter is initialized to -1, then + // replication will be allowed to "run wild". Otherwise, this method decrements + // the downcounter each time it is called, calling false when it is called with + // a zero downcounter. This method returning false effectively ends the replication + // of the associated ParallelForReplicatingTask. + internal override bool ShouldReplicate() + { + if (m_replicationDownCount == -1) return true; // "run wild" + + if (m_replicationDownCount > 0) // Decrement and return true if not called with 0 downcount + { + m_replicationDownCount--; + return true; + } + + return false; // We're done replicating + } + + internal override Task CreateReplicaTask(Action<object> taskReplicaDelegate, Object stateObject, Task parentTask, TaskScheduler taskScheduler, + TaskCreationOptions creationOptionsForReplica, InternalTaskOptions internalOptionsForReplica) + { + return new ParallelForReplicaTask(taskReplicaDelegate, stateObject, parentTask, taskScheduler, + creationOptionsForReplica, internalOptionsForReplica); + } + + + } + + internal class ParallelForReplicaTask : Task + { + internal object m_stateForNextReplica; // some replicas may quit prematurely, in which case they will use this variable + // to save state they want to be picked up by the next replica queued to the same thread + + internal object m_stateFromPreviousReplica; // some replicas may quit prematurely, in which case they will use this variable + // to save state they want to be picked up by the next replica queued to the same thread + + internal Task m_handedOverChildReplica; // some replicas may quit prematurely, in which case they will use this variable + // to hand over the child replica they had queued to the next task that will replace them + + internal ParallelForReplicaTask(Action<object> taskReplicaDelegate, Object stateObject, Task parentTask, TaskScheduler taskScheduler, + TaskCreationOptions creationOptionsForReplica, InternalTaskOptions internalOptionsForReplica) : + base(taskReplicaDelegate, stateObject, parentTask, default(CancellationToken), creationOptionsForReplica, internalOptionsForReplica, taskScheduler) + { + } + + // Allows internal deriving classes to support replicas that exit prematurely and want to pass on state to the next replica + internal override Object SavedStateForNextReplica + { + get { return m_stateForNextReplica; } + + set { m_stateForNextReplica = value; } + } + + // Allows internal deriving classes to support replicas that exit prematurely and want to pass on state to the next replica + internal override Object SavedStateFromPreviousReplica + { + get { return m_stateFromPreviousReplica; } + + set { m_stateFromPreviousReplica = value; } + } + + // Allows internal deriving classes to support replicas that exit prematurely and want to hand over the child replica that they + // had queued, so that the replacement replica can work with that child task instead of queuing up yet another one + internal override Task HandedOverChildReplica + { + get { return m_handedOverChildReplica; } + + set { m_handedOverChildReplica = value; } + } + } + + /// <summary> + /// Specifies flags that control optional behavior for the creation and execution of tasks. + /// </summary> + // NOTE: These options are a subset of TaskContinuationsOptions, thus before adding a flag check it is + // not already in use. + [Flags] + [Serializable] + public enum TaskCreationOptions + { + /// <summary> + /// Specifies that the default behavior should be used. + /// </summary> + None = 0x0, + + /// <summary> + /// A hint to a <see cref="System.Threading.Tasks.TaskScheduler">TaskScheduler</see> to schedule a + /// task in as fair a manner as possible, meaning that tasks scheduled sooner will be more likely to + /// be run sooner, and tasks scheduled later will be more likely to be run later. + /// </summary> + PreferFairness = 0x01, + + /// <summary> + /// Specifies that a task will be a long-running, course-grained operation. It provides a hint to the + /// <see cref="System.Threading.Tasks.TaskScheduler">TaskScheduler</see> that oversubscription may be + /// warranted. + /// </summary> + LongRunning = 0x02, + + /// <summary> + /// Specifies that a task is attached to a parent in the task hierarchy. + /// </summary> + AttachedToParent = 0x04, + + /// <summary> + /// Specifies that an InvalidOperationException will be thrown if an attempt is made to attach a child task to the created task. + /// </summary> + DenyChildAttach = 0x08, + + /// <summary> + /// Prevents the ambient scheduler from being seen as the current scheduler in the created task. This means that operations + /// like StartNew or ContinueWith that are performed in the created task will see TaskScheduler.Default as the current scheduler. + /// </summary> + HideScheduler = 0x10, + + // 0x20 is already being used in TaskContinuationOptions + + /// <summary> + /// Forces continuations added to the current task to be executed asynchronously. + /// This option has precedence over TaskContinuationOptions.ExecuteSynchronously + /// </summary> + RunContinuationsAsynchronously = 0x40 + } + + + /// <summary> + /// Task creation flags which are only used internally. + /// </summary> + [Flags] + [Serializable] + internal enum InternalTaskOptions + { + /// <summary> Specifies "No internal task options" </summary> + None, + + /// <summary>Used to filter out internal vs. public task creation options.</summary> + InternalOptionsMask = 0x0000FF00, + + ChildReplica = 0x0100, + ContinuationTask = 0x0200, + PromiseTask = 0x0400, + SelfReplicating = 0x0800, + + /// <summary> + /// Store the presence of TaskContinuationOptions.LazyCancellation, since it does not directly + /// translate into any TaskCreationOptions. + /// </summary> + LazyCancellation = 0x1000, + + /// <summary>Specifies that the task will be queued by the runtime before handing it over to the user. + /// This flag will be used to skip the cancellationtoken registration step, which is only meant for unstarted tasks.</summary> + QueuedByRuntime = 0x2000, + + /// <summary> + /// Denotes that Dispose should be a complete nop for a Task. Used when constructing tasks that are meant to be cached/reused. + /// </summary> + DoNotDispose = 0x4000 + } + + /// <summary> + /// Specifies flags that control optional behavior for the creation and execution of continuation tasks. + /// </summary> + [Flags] + [Serializable] + public enum TaskContinuationOptions + { + /// <summary> + /// Default = "Continue on any, no task options, run asynchronously" + /// Specifies that the default behavior should be used. Continuations, by default, will + /// be scheduled when the antecedent task completes, regardless of the task's final <see + /// cref="System.Threading.Tasks.TaskStatus">TaskStatus</see>. + /// </summary> + None = 0, + + // These are identical to their meanings and values in TaskCreationOptions + + /// <summary> + /// A hint to a <see cref="System.Threading.Tasks.TaskScheduler">TaskScheduler</see> to schedule a + /// task in as fair a manner as possible, meaning that tasks scheduled sooner will be more likely to + /// be run sooner, and tasks scheduled later will be more likely to be run later. + /// </summary> + PreferFairness = 0x01, + + /// <summary> + /// Specifies that a task will be a long-running, course-grained operation. It provides + /// a hint to the <see cref="System.Threading.Tasks.TaskScheduler">TaskScheduler</see> that + /// oversubscription may be warranted. + /// </summary> + LongRunning = 0x02, + /// <summary> + /// Specifies that a task is attached to a parent in the task hierarchy. + /// </summary> + AttachedToParent = 0x04, + + /// <summary> + /// Specifies that an InvalidOperationException will be thrown if an attempt is made to attach a child task to the created task. + /// </summary> + DenyChildAttach = 0x08, + /// <summary> + /// Prevents the ambient scheduler from being seen as the current scheduler in the created task. This means that operations + /// like StartNew or ContinueWith that are performed in the created task will see TaskScheduler.Default as the current scheduler. + /// </summary> + HideScheduler = 0x10, + + /// <summary> + /// In the case of continuation cancellation, prevents completion of the continuation until the antecedent has completed. + /// </summary> + LazyCancellation = 0x20, + + RunContinuationsAsynchronously = 0x40, + + // These are specific to continuations + + /// <summary> + /// Specifies that the continuation task should not be scheduled if its antecedent ran to completion. + /// This option is not valid for multi-task continuations. + /// </summary> + NotOnRanToCompletion = 0x10000, + /// <summary> + /// Specifies that the continuation task should not be scheduled if its antecedent threw an unhandled + /// exception. This option is not valid for multi-task continuations. + /// </summary> + NotOnFaulted = 0x20000, + /// <summary> + /// Specifies that the continuation task should not be scheduled if its antecedent was canceled. This + /// option is not valid for multi-task continuations. + /// </summary> + NotOnCanceled = 0x40000, + /// <summary> + /// Specifies that the continuation task should be scheduled only if its antecedent ran to + /// completion. This option is not valid for multi-task continuations. + /// </summary> + OnlyOnRanToCompletion = NotOnFaulted | NotOnCanceled, + /// <summary> + /// Specifies that the continuation task should be scheduled only if its antecedent threw an + /// unhandled exception. This option is not valid for multi-task continuations. + /// </summary> + OnlyOnFaulted = NotOnRanToCompletion | NotOnCanceled, + /// <summary> + /// Specifies that the continuation task should be scheduled only if its antecedent was canceled. + /// This option is not valid for multi-task continuations. + /// </summary> + OnlyOnCanceled = NotOnRanToCompletion | NotOnFaulted, + /// <summary> + /// Specifies that the continuation task should be executed synchronously. With this option + /// specified, the continuation will be run on the same thread that causes the antecedent task to + /// transition into its final state. If the antecedent is already complete when the continuation is + /// created, the continuation will run on the thread creating the continuation. Only very + /// short-running continuations should be executed synchronously. + /// </summary> + ExecuteSynchronously = 0x80000 + } + + /// <summary> + /// Internal helper class to keep track of stack depth and decide whether we should inline or not. + /// </summary> + internal class StackGuard + { + // current thread's depth of nested inline task executions + private int m_inliningDepth = 0; + + // For relatively small inlining depths we don't want to get into the business of stack probing etc. + // This clearly leaves a window of opportunity for the user code to SO. However a piece of code + // that can SO in 20 inlines on a typical 1MB stack size probably needs to be revisited anyway. + private const int MAX_UNCHECKED_INLINING_DEPTH = 20; + +#if !FEATURE_CORECLR + + private UInt64 m_lastKnownWatermark; + private static int s_pageSize; + + // We are conservative here. We assume that the platform needs a whole 64KB to + // respond to stack overflow. This means that for very small stacks (e.g. 128KB) + // we'll fail a lot of stack checks incorrectly. + private const long STACK_RESERVED_SPACE = 4096 * 16; + +#endif // !FEATURE_CORECLR + + /// <summary> + /// This method needs to be called before attempting inline execution on the current thread. + /// If false is returned, it means we are too close to the end of the stack and should give up inlining. + /// Each call to TryBeginInliningScope() that returns true must be matched with a + /// call to EndInliningScope() regardless of whether inlining actually took place. + /// </summary> + [SecuritySafeCritical] + internal bool TryBeginInliningScope() + { + // If we're still under the 'safe' limit we'll just skip the stack probe to save p/invoke calls + if (m_inliningDepth < MAX_UNCHECKED_INLINING_DEPTH || CheckForSufficientStack()) + { + m_inliningDepth++; + return true; + } + else + return false; + } + + /// <summary> + /// This needs to be called once for each previous successful TryBeginInliningScope() call after + /// inlining related logic runs. + /// </summary> + internal void EndInliningScope() + { + m_inliningDepth--; + Contract.Assert(m_inliningDepth >= 0, "Inlining depth count should never go negative."); + + // do the right thing just in case... + if (m_inliningDepth < 0) m_inliningDepth = 0; + } + + [SecurityCritical] + private unsafe bool CheckForSufficientStack() + { +#if FEATURE_CORECLR + return RuntimeHelpers.TryEnsureSufficientExecutionStack(); +#else + // see if we already have the system page size info recorded + int pageSize = s_pageSize; + if (pageSize == 0) + { + // If not we need to query it from GetSystemInfo() + // Note that this happens only once for the process lifetime + Win32Native.SYSTEM_INFO sysInfo = new Win32Native.SYSTEM_INFO(); + Win32Native.GetSystemInfo(ref sysInfo); + + s_pageSize = pageSize = sysInfo.dwPageSize; + } + + Win32Native.MEMORY_BASIC_INFORMATION stackInfo = new Win32Native.MEMORY_BASIC_INFORMATION(); + + // We subtract one page for our request. VirtualQuery rounds UP to the next page. + // Unfortunately, the stack grows down. If we're on the first page (last page in the + // VirtualAlloc), we'll be moved to the next page, which is off the stack! + + UIntPtr currentAddr = new UIntPtr(&stackInfo - pageSize); + UInt64 current64 = currentAddr.ToUInt64(); + + // Check whether we previously recorded a deeper stack than where we currently are, + // If so we don't need to do the P/Invoke to VirtualQuery + if (m_lastKnownWatermark != 0 && current64 > m_lastKnownWatermark) + return true; + + // Actual stack probe. P/Invoke to query for the current stack allocation information. + Win32Native.VirtualQuery(currentAddr.ToPointer(), ref stackInfo, (UIntPtr)(sizeof(Win32Native.MEMORY_BASIC_INFORMATION))); + + // If the current address minus the base (remember: the stack grows downward in the + // address space) is greater than the number of bytes requested plus the reserved + // space at the end, the request has succeeded. + + if ((current64 - ((UIntPtr)stackInfo.AllocationBase).ToUInt64()) > STACK_RESERVED_SPACE) + { + m_lastKnownWatermark = current64; + return true; + } + + return false; +#endif + } + } + + // Special internal struct that we use to signify that we are not interested in + // a Task<VoidTaskResult>'s result. + internal struct VoidTaskResult { } + + // Interface to which all completion actions must conform. + // This interface allows us to combine functionality and reduce allocations. + // For example, see Task.SetOnInvokeMres, and its use in Task.SpinThenBlockingWait(). + // This code: + // ManualResetEvent mres = new ManualResetEventSlim(false, 0); + // Action<Task> completionAction = delegate { mres.Set() ; }; + // AddCompletionAction(completionAction); + // gets replaced with this: + // SetOnInvokeMres mres = new SetOnInvokeMres(); + // AddCompletionAction(mres); + // For additional examples of where this is used, see internal classes Task.SignalOnInvokeCDE, + // Task.WhenAllPromise, Task.WhenAllPromise<T>, TaskFactory.CompleteOnCountdownPromise, + // TaskFactory.CompleteOnCountdownPromise<T>, and TaskFactory.CompleteOnInvokePromise. + internal interface ITaskCompletionAction + { + /// <summary>Invoked to run the completion action.</summary> + void Invoke(Task completingTask); + + /// <summary> + /// Some completion actions are considered internal implementation details of tasks, + /// using the continuation mechanism only for performance reasons. Such actions perform + /// known quantities and types of work, and can be invoked safely as a continuation even + /// if the system wants to prevent arbitrary continuations from running synchronously. + /// This should only return false for a limited set of implementations where a small amount + /// of work is guaranteed to be performed, e.g. setting a ManualResetEventSlim. + /// </summary> + bool InvokeMayRunArbitraryCode { get; } + } + + // This class encapsulates all "unwrap" logic, and also implements ITaskCompletionAction, + // which minimizes the allocations needed for queuing it to its antecedent. This + // logic is used by both the Unwrap extension methods and the unwrap-style Task.Run methods. + internal sealed class UnwrapPromise<TResult> : Task<TResult>, ITaskCompletionAction + { + // The possible states for our UnwrapPromise, used by Invoke() to determine which logic to execute + private const byte STATE_WAITING_ON_OUTER_TASK = 0; // Invoke() means "process completed outer task" + private const byte STATE_WAITING_ON_INNER_TASK = 1; // Invoke() means "process completed inner task" + private const byte STATE_DONE = 2; // Invoke() means "something went wrong and we are hosed!" + + // Keep track of our state; initialized to STATE_WAITING_ON_OUTER_TASK in the constructor + private byte _state; + + // "Should we check for OperationCanceledExceptions on the outer task and interpret them as proxy cancellation?" + // Unwrap() sets this to false, Run() sets it to true. + private readonly bool _lookForOce; + + public UnwrapPromise(Task outerTask, bool lookForOce) + : base((object)null, outerTask.CreationOptions & TaskCreationOptions.AttachedToParent) + { + Contract.Requires(outerTask != null, "Expected non-null outerTask"); + _lookForOce = lookForOce; + _state = STATE_WAITING_ON_OUTER_TASK; + + if (AsyncCausalityTracer.LoggingOn) + AsyncCausalityTracer.TraceOperationCreation(CausalityTraceLevel.Required, this.Id, "Task.Unwrap", 0); + + if (Task.s_asyncDebuggingEnabled) + { + AddToActiveTasks(this); + } + + // Link ourselves to the outer task. + // If the outer task has already completed, take the fast path + // of immediately transferring its results or processing the inner task. + if (outerTask.IsCompleted) + { + ProcessCompletedOuterTask(outerTask); + } + else // Otherwise, process its completion asynchronously. + { + outerTask.AddCompletionAction(this); + } + } + + // For ITaskCompletionAction + public void Invoke(Task completingTask) + { + // Check the current stack guard. If we're ok to inline, + // process the task, and reset the guard when we're done. + var sg = Task.CurrentStackGuard; + if (sg.TryBeginInliningScope()) + { + try { InvokeCore(completingTask); } + finally { sg.EndInliningScope(); } + } + // Otherwise, we're too deep on the stack, and + // we shouldn't run the continuation chain here, so queue a work + // item to call back here to Invoke asynchronously. + else InvokeCoreAsync(completingTask); + } + + /// <summary> + /// Processes the completed task. InvokeCore could be called twice: + /// once for the outer task, once for the inner task. + /// </summary> + /// <param name="completingTask">The completing outer or inner task.</param> + private void InvokeCore(Task completingTask) + { + switch (_state) + { + case STATE_WAITING_ON_OUTER_TASK: + ProcessCompletedOuterTask(completingTask); + // We bump the state inside of ProcessCompletedOuterTask because it can also be called from the constructor. + break; + case STATE_WAITING_ON_INNER_TASK: + bool result = TrySetFromTask(completingTask, lookForOce: false); + _state = STATE_DONE; // bump the state + Contract.Assert(result, "Expected TrySetFromTask from inner task to succeed"); + break; + default: + Contract.Assert(false, "UnwrapPromise in illegal state"); + break; + } + } + + // Calls InvokeCore asynchronously. + [SecuritySafeCritical] + private void InvokeCoreAsync(Task completingTask) + { + // Queue a call to Invoke. If we're so deep on the stack that we're at risk of overflowing, + // there's a high liklihood this thread is going to be doing lots more work before + // returning to the thread pool (at the very least unwinding through thousands of + // stack frames). So we queue to the global queue. + ThreadPool.UnsafeQueueUserWorkItem(state => + { + // InvokeCore(completingTask); + var tuple = (Tuple<UnwrapPromise<TResult>, Task>)state; + tuple.Item1.InvokeCore(tuple.Item2); + }, Tuple.Create<UnwrapPromise<TResult>, Task>(this, completingTask)); + } + + /// <summary>Processes the outer task once it's completed.</summary> + /// <param name="task">The now-completed outer task.</param> + private void ProcessCompletedOuterTask(Task task) + { + Contract.Requires(task != null && task.IsCompleted, "Expected non-null, completed outer task"); + Contract.Assert(_state == STATE_WAITING_ON_OUTER_TASK, "We're in the wrong state!"); + + // Bump our state before proceeding any further + _state = STATE_WAITING_ON_INNER_TASK; + + switch (task.Status) + { + // If the outer task did not complete successfully, then record the + // cancellation/fault information to tcs.Task. + case TaskStatus.Canceled: + case TaskStatus.Faulted: + bool result = TrySetFromTask(task, _lookForOce); + Contract.Assert(result, "Expected TrySetFromTask from outer task to succeed"); + break; + + // Otherwise, process the inner task it returned. + case TaskStatus.RanToCompletion: + var taskOfTaskOfTResult = task as Task<Task<TResult>>; // it's either a Task<Task> or Task<Task<TResult>> + ProcessInnerTask(taskOfTaskOfTResult != null ? + taskOfTaskOfTResult.Result : ((Task<Task>)task).Result); + break; + } + } + + /// <summary>Transfer the completion status from "task" to ourself.</summary> + /// <param name="task">The source task whose results should be transfered to <paramref name="promise"/>.</param> + /// <param name="lookForOce">Whether or not to look for OperationCanceledExceptions in task's exceptions if it faults.</param> + /// <returns>true if the transfer was successful; otherwise, false.</returns> + private bool TrySetFromTask(Task task, bool lookForOce) + { + Contract.Requires(task != null && task.IsCompleted, "TrySetFromTask: Expected task to have completed."); + + if (AsyncCausalityTracer.LoggingOn) + AsyncCausalityTracer.TraceOperationRelation(CausalityTraceLevel.Important, this.Id, CausalityRelation.Join); + + bool result = false; + switch (task.Status) + { + case TaskStatus.Canceled: + result = TrySetCanceled(task.CancellationToken, task.GetCancellationExceptionDispatchInfo()); + break; + + case TaskStatus.Faulted: + var edis = task.GetExceptionDispatchInfos(); + ExceptionDispatchInfo oceEdi; + OperationCanceledException oce; + if (lookForOce && edis.Count > 0 && + (oceEdi = edis[0]) != null && + (oce = oceEdi.SourceException as OperationCanceledException) != null) + { + result = TrySetCanceled(oce.CancellationToken, oceEdi); + } + else + { + result = TrySetException(edis); + } + break; + + case TaskStatus.RanToCompletion: + var taskTResult = task as Task<TResult>; + + if (AsyncCausalityTracer.LoggingOn) + AsyncCausalityTracer.TraceOperationCompletion(CausalityTraceLevel.Required, this.Id, AsyncCausalityStatus.Completed); + + if (Task.s_asyncDebuggingEnabled) + { + RemoveFromActiveTasks(this.Id); + } + + result = TrySetResult(taskTResult != null ? taskTResult.Result : default(TResult)); + break; + } + return result; + } + + /// <summary> + /// Processes the inner task of a Task{Task} or Task{Task{TResult}}, + /// transferring the appropriate results to ourself. + /// </summary> + /// <param name="task">The inner task returned by the task provided by the user.</param> + private void ProcessInnerTask(Task task) + { + // If the inner task is null, the proxy should be canceled. + if (task == null) + { + TrySetCanceled(default(CancellationToken)); + _state = STATE_DONE; // ... and record that we are done + } + + // Fast path for if the inner task is already completed + else if (task.IsCompleted) + { + TrySetFromTask(task, lookForOce: false); + _state = STATE_DONE; // ... and record that we are done + } + + // The inner task exists but is not yet complete, so when it does complete, + // take some action to set our completion state. + else + { + task.AddCompletionAction(this); + // We'll record that we are done when Invoke() is called. + } + } + + public bool InvokeMayRunArbitraryCode { get { return true; } } + } + +} diff --git a/src/mscorlib/src/System/Threading/Tasks/TaskCanceledException.cs b/src/mscorlib/src/System/Threading/Tasks/TaskCanceledException.cs new file mode 100644 index 0000000000..f15e3e783a --- /dev/null +++ b/src/mscorlib/src/System/Threading/Tasks/TaskCanceledException.cs @@ -0,0 +1,93 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +// =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ +// +// +// +// An exception for task cancellations. +// +// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + +using System; +using System.Runtime.InteropServices; +using System.Runtime.Serialization; + +namespace System.Threading.Tasks +{ + + /// <summary> + /// Represents an exception used to communicate task cancellation. + /// </summary> + [Serializable] + public class TaskCanceledException : OperationCanceledException + { + + [NonSerialized] + private Task m_canceledTask; // The task which has been canceled. + + /// <summary> + /// Initializes a new instance of the <see cref="T:System.Threading.Tasks.TaskCanceledException"/> class. + /// </summary> + public TaskCanceledException() : base(Environment.GetResourceString("TaskCanceledException_ctor_DefaultMessage")) + { + } + + /// <summary> + /// Initializes a new instance of the <see cref="T:System.Threading.Tasks.TaskCanceledException"/> + /// class with a specified error message. + /// </summary> + /// <param name="message">The error message that explains the reason for the exception.</param> + public TaskCanceledException(string message) : base(message) + { + } + + /// <summary> + /// Initializes a new instance of the <see cref="T:System.Threading.Tasks.TaskCanceledException"/> + /// class with a specified error message and a reference to the inner exception that is the cause of + /// this exception. + /// </summary> + /// <param name="message">The error message that explains the reason for the exception.</param> + /// <param name="innerException">The exception that is the cause of the current exception.</param> + public TaskCanceledException(string message, Exception innerException) : base(message, innerException) + { + } + + /// <summary> + /// Initializes a new instance of the <see cref="T:System.Threading.Tasks.TaskCanceledException"/> class + /// with a reference to the <see cref="T:System.Threading.Tasks.Task"/> that has been canceled. + /// </summary> + /// <param name="task">A task that has been canceled.</param> + public TaskCanceledException(Task task) : + base(Environment.GetResourceString("TaskCanceledException_ctor_DefaultMessage"), task!=null ? task.CancellationToken:new CancellationToken()) + { + m_canceledTask = task; + } + + /// <summary> + /// Initializes a new instance of the <see cref="T:System.Threading.Tasks.TaskCanceledException"/> + /// class with serialized data. + /// </summary> + /// <param name="info">The <see cref="T:System.Runtime.Serialization.SerializationInfo"/> that holds the serialized object data about the exception being thrown.</param> + /// <param name="context">The <see cref="T:System.Runtime.Serialization.StreamingContext"/> that contains contextual information about the source or destination. </param> + protected TaskCanceledException(SerializationInfo info, StreamingContext context) : base(info, context) + { + } + + /// <summary> + /// Gets the task associated with this exception. + /// </summary> + /// <remarks> + /// It is permissible for no Task to be associated with a + /// <see cref="T:System.Threading.Tasks.TaskCanceledException"/>, in which case + /// this property will return null. + /// </remarks> + public Task Task + { + get { return m_canceledTask; } + } + + } + +} diff --git a/src/mscorlib/src/System/Threading/Tasks/TaskCompletionSource.cs b/src/mscorlib/src/System/Threading/Tasks/TaskCompletionSource.cs new file mode 100644 index 0000000000..8b1dd2a62f --- /dev/null +++ b/src/mscorlib/src/System/Threading/Tasks/TaskCompletionSource.cs @@ -0,0 +1,370 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +// =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ +// +// +// +// TaskCompletionSource<TResult> is the producer end of an unbound future. Its +// Task member may be distributed as the consumer end of the future. +// +// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + +using System; +using System.Diagnostics.Contracts; +using System.Collections.Generic; +using System.Runtime.CompilerServices; +using System.Runtime.ExceptionServices; +using System.Security.Permissions; +using System.Threading; + +// Disable the "reference to volatile field not treated as volatile" error. +#pragma warning disable 0420 + +namespace System.Threading.Tasks +{ + /// <summary> + /// Represents the producer side of a <see cref="T:System.Threading.Tasks.Task{TResult}"/> unbound to a + /// delegate, providing access to the consumer side through the <see cref="Task"/> property. + /// </summary> + /// <remarks> + /// <para> + /// It is often the case that a <see cref="T:System.Threading.Tasks.Task{TResult}"/> is desired to + /// represent another asynchronous operation. + /// <see cref="TaskCompletionSource{TResult}">TaskCompletionSource</see> is provided for this purpose. It enables + /// the creation of a task that can be handed out to consumers, and those consumers can use the members + /// of the task as they would any other. However, unlike most tasks, the state of a task created by a + /// TaskCompletionSource is controlled explicitly by the methods on TaskCompletionSource. This enables the + /// completion of the external asynchronous operation to be propagated to the underlying Task. The + /// separation also ensures that consumers are not able to transition the state without access to the + /// corresponding TaskCompletionSource. + /// </para> + /// <para> + /// All members of <see cref="TaskCompletionSource{TResult}"/> are thread-safe + /// and may be used from multiple threads concurrently. + /// </para> + /// </remarks> + /// <typeparam name="TResult">The type of the result value assocatied with this <see + /// cref="TaskCompletionSource{TResult}"/>.</typeparam> + [HostProtection(Synchronization = true, ExternalThreading = true)] + public class TaskCompletionSource<TResult> + { + private readonly Task<TResult> m_task; + + /// <summary> + /// Creates a <see cref="TaskCompletionSource{TResult}"/>. + /// </summary> + public TaskCompletionSource() + { + m_task = new Task<TResult>(); + } + + /// <summary> + /// Creates a <see cref="TaskCompletionSource{TResult}"/> + /// with the specified options. + /// </summary> + /// <remarks> + /// The <see cref="T:System.Threading.Tasks.Task{TResult}"/> created + /// by this instance and accessible through its <see cref="Task"/> property + /// will be instantiated using the specified <paramref name="creationOptions"/>. + /// </remarks> + /// <param name="creationOptions">The options to use when creating the underlying + /// <see cref="T:System.Threading.Tasks.Task{TResult}"/>.</param> + /// <exception cref="T:System.ArgumentOutOfRangeException"> + /// The <paramref name="creationOptions"/> represent options invalid for use + /// with a <see cref="TaskCompletionSource{TResult}"/>. + /// </exception> + public TaskCompletionSource(TaskCreationOptions creationOptions) + : this(null, creationOptions) + { + } + + /// <summary> + /// Creates a <see cref="TaskCompletionSource{TResult}"/> + /// with the specified state. + /// </summary> + /// <param name="state">The state to use as the underlying + /// <see cref="T:System.Threading.Tasks.Task{TResult}"/>'s AsyncState.</param> + public TaskCompletionSource(object state) + : this(state, TaskCreationOptions.None) + { + } + + /// <summary> + /// Creates a <see cref="TaskCompletionSource{TResult}"/> with + /// the specified state and options. + /// </summary> + /// <param name="creationOptions">The options to use when creating the underlying + /// <see cref="T:System.Threading.Tasks.Task{TResult}"/>.</param> + /// <param name="state">The state to use as the underlying + /// <see cref="T:System.Threading.Tasks.Task{TResult}"/>'s AsyncState.</param> + /// <exception cref="T:System.ArgumentOutOfRangeException"> + /// The <paramref name="creationOptions"/> represent options invalid for use + /// with a <see cref="TaskCompletionSource{TResult}"/>. + /// </exception> + public TaskCompletionSource(object state, TaskCreationOptions creationOptions) + { + m_task = new Task<TResult>(state, creationOptions); + } + + + /// <summary> + /// Gets the <see cref="T:System.Threading.Tasks.Task{TResult}"/> created + /// by this <see cref="TaskCompletionSource{TResult}"/>. + /// </summary> + /// <remarks> + /// This property enables a consumer access to the <see + /// cref="T:System.Threading.Tasks.Task{TResult}"/> that is controlled by this instance. + /// The <see cref="SetResult"/>, <see cref="SetException(System.Exception)"/>, + /// <see cref="SetException(System.Collections.Generic.IEnumerable{System.Exception})"/>, and <see cref="SetCanceled"/> + /// methods (and their "Try" variants) on this instance all result in the relevant state + /// transitions on this underlying Task. + /// </remarks> + public Task<TResult> Task + { + get { return m_task; } + } + + /// <summary>Spins until the underlying task is completed.</summary> + /// <remarks>This should only be called if the task is in the process of being completed by another thread.</remarks> + private void SpinUntilCompleted() + { + // Spin wait until the completion is finalized by another thread. + var sw = new SpinWait(); + while (!m_task.IsCompleted) + sw.SpinOnce(); + } + + /// <summary> + /// Attempts to transition the underlying + /// <see cref="T:System.Threading.Tasks.Task{TResult}"/> into the + /// <see cref="System.Threading.Tasks.TaskStatus.Faulted">Faulted</see> + /// state. + /// </summary> + /// <param name="exception">The exception to bind to this <see + /// cref="T:System.Threading.Tasks.Task{TResult}"/>.</param> + /// <returns>True if the operation was successful; otherwise, false.</returns> + /// <remarks>This operation will return false if the + /// <see cref="T:System.Threading.Tasks.Task{TResult}"/> is already in one + /// of the three final states: + /// <see cref="System.Threading.Tasks.TaskStatus.RanToCompletion">RanToCompletion</see>, + /// <see cref="System.Threading.Tasks.TaskStatus.Faulted">Faulted</see>, or + /// <see cref="System.Threading.Tasks.TaskStatus.Canceled">Canceled</see>. + /// </remarks> + /// <exception cref="T:System.ArgumentNullException">The <paramref name="exception"/> argument is null.</exception> + /// <exception cref="T:System.ObjectDisposedException">The <see cref="Task"/> was disposed.</exception> + public bool TrySetException(Exception exception) + { + if (exception == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.exception); + + bool rval = m_task.TrySetException(exception); + if (!rval && !m_task.IsCompleted) SpinUntilCompleted(); + return rval; + } + + /// <summary> + /// Attempts to transition the underlying + /// <see cref="T:System.Threading.Tasks.Task{TResult}"/> into the + /// <see cref="System.Threading.Tasks.TaskStatus.Faulted">Faulted</see> + /// state. + /// </summary> + /// <param name="exceptions">The collection of exceptions to bind to this <see + /// cref="T:System.Threading.Tasks.Task{TResult}"/>.</param> + /// <returns>True if the operation was successful; otherwise, false.</returns> + /// <remarks>This operation will return false if the + /// <see cref="T:System.Threading.Tasks.Task{TResult}"/> is already in one + /// of the three final states: + /// <see cref="System.Threading.Tasks.TaskStatus.RanToCompletion">RanToCompletion</see>, + /// <see cref="System.Threading.Tasks.TaskStatus.Faulted">Faulted</see>, or + /// <see cref="System.Threading.Tasks.TaskStatus.Canceled">Canceled</see>. + /// </remarks> + /// <exception cref="T:System.ArgumentNullException">The <paramref name="exceptions"/> argument is null.</exception> + /// <exception cref="T:System.ArgumentException">There are one or more null elements in <paramref name="exceptions"/>.</exception> + /// <exception cref="T:System.ArgumentException">The <paramref name="exceptions"/> collection is empty.</exception> + /// <exception cref="T:System.ObjectDisposedException">The <see cref="Task"/> was disposed.</exception> + public bool TrySetException(IEnumerable<Exception> exceptions) + { + if (exceptions == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.exceptions); + + List<Exception> defensiveCopy = new List<Exception>(); + foreach (Exception e in exceptions) + { + if (e == null) + ThrowHelper.ThrowArgumentException(ExceptionResource.TaskCompletionSourceT_TrySetException_NullException, ExceptionArgument.exceptions); + defensiveCopy.Add(e); + } + + if (defensiveCopy.Count == 0) + ThrowHelper.ThrowArgumentException(ExceptionResource.TaskCompletionSourceT_TrySetException_NoExceptions, ExceptionArgument.exceptions); + + bool rval = m_task.TrySetException(defensiveCopy); + if (!rval && !m_task.IsCompleted) SpinUntilCompleted(); + return rval; + } + + /// <summary>Attempts to transition the underlying task to the faulted state.</summary> + /// <param name="exceptions">The collection of exception dispatch infos to bind to this task.</param> + /// <returns>True if the operation was successful; otherwise, false.</returns> + /// <remarks>Unlike the public methods, this method doesn't currently validate that its arguments are correct.</remarks> + internal bool TrySetException(IEnumerable<ExceptionDispatchInfo> exceptions) + { + Contract.Assert(exceptions != null); +#if DEBUG + foreach(var edi in exceptions) Contract.Assert(edi != null, "Contents must be non-null"); +#endif + + bool rval = m_task.TrySetException(exceptions); + if (!rval && !m_task.IsCompleted) SpinUntilCompleted(); + return rval; + } + + /// <summary> + /// Transitions the underlying + /// <see cref="T:System.Threading.Tasks.Task{TResult}"/> into the + /// <see cref="System.Threading.Tasks.TaskStatus.Faulted">Faulted</see> + /// state. + /// </summary> + /// <param name="exception">The exception to bind to this <see + /// cref="T:System.Threading.Tasks.Task{TResult}"/>.</param> + /// <exception cref="T:System.ArgumentNullException">The <paramref name="exception"/> argument is null.</exception> + /// <exception cref="T:System.InvalidOperationException"> + /// The underlying <see cref="T:System.Threading.Tasks.Task{TResult}"/> is already in one + /// of the three final states: + /// <see cref="System.Threading.Tasks.TaskStatus.RanToCompletion">RanToCompletion</see>, + /// <see cref="System.Threading.Tasks.TaskStatus.Faulted">Faulted</see>, or + /// <see cref="System.Threading.Tasks.TaskStatus.Canceled">Canceled</see>. + /// </exception> + /// <exception cref="T:System.ObjectDisposedException">The <see cref="Task"/> was disposed.</exception> + public void SetException(Exception exception) + { + if (exception == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.exception); + + if (!TrySetException(exception)) + { + ThrowHelper.ThrowInvalidOperationException(ExceptionResource.TaskT_TransitionToFinal_AlreadyCompleted); + } + } + + /// <summary> + /// Transitions the underlying + /// <see cref="T:System.Threading.Tasks.Task{TResult}"/> into the + /// <see cref="System.Threading.Tasks.TaskStatus.Faulted">Faulted</see> + /// state. + /// </summary> + /// <param name="exceptions">The collection of exceptions to bind to this <see + /// cref="T:System.Threading.Tasks.Task{TResult}"/>.</param> + /// <exception cref="T:System.ArgumentNullException">The <paramref name="exceptions"/> argument is null.</exception> + /// <exception cref="T:System.ArgumentException">There are one or more null elements in <paramref name="exceptions"/>.</exception> + /// <exception cref="T:System.InvalidOperationException"> + /// The underlying <see cref="T:System.Threading.Tasks.Task{TResult}"/> is already in one + /// of the three final states: + /// <see cref="System.Threading.Tasks.TaskStatus.RanToCompletion">RanToCompletion</see>, + /// <see cref="System.Threading.Tasks.TaskStatus.Faulted">Faulted</see>, or + /// <see cref="System.Threading.Tasks.TaskStatus.Canceled">Canceled</see>. + /// </exception> + /// <exception cref="T:System.ObjectDisposedException">The <see cref="Task"/> was disposed.</exception> + public void SetException(IEnumerable<Exception> exceptions) + { + if (!TrySetException(exceptions)) + { + ThrowHelper.ThrowInvalidOperationException(ExceptionResource.TaskT_TransitionToFinal_AlreadyCompleted); + } + } + + + /// <summary> + /// Attempts to transition the underlying + /// <see cref="T:System.Threading.Tasks.Task{TResult}"/> into the + /// <see cref="System.Threading.Tasks.TaskStatus.RanToCompletion">RanToCompletion</see> + /// state. + /// </summary> + /// <param name="result">The result value to bind to this <see + /// cref="T:System.Threading.Tasks.Task{TResult}"/>.</param> + /// <returns>True if the operation was successful; otherwise, false.</returns> + /// <remarks>This operation will return false if the + /// <see cref="T:System.Threading.Tasks.Task{TResult}"/> is already in one + /// of the three final states: + /// <see cref="System.Threading.Tasks.TaskStatus.RanToCompletion">RanToCompletion</see>, + /// <see cref="System.Threading.Tasks.TaskStatus.Faulted">Faulted</see>, or + /// <see cref="System.Threading.Tasks.TaskStatus.Canceled">Canceled</see>. + /// </remarks> + /// <exception cref="T:System.ObjectDisposedException">The <see cref="Task"/> was disposed.</exception> + public bool TrySetResult(TResult result) + { + bool rval = m_task.TrySetResult(result); + if (!rval && !m_task.IsCompleted) SpinUntilCompleted(); + return rval; + } + + /// <summary> + /// Transitions the underlying + /// <see cref="T:System.Threading.Tasks.Task{TResult}"/> into the + /// <see cref="System.Threading.Tasks.TaskStatus.RanToCompletion">RanToCompletion</see> + /// state. + /// </summary> + /// <param name="result">The result value to bind to this <see + /// cref="T:System.Threading.Tasks.Task{TResult}"/>.</param> + /// <exception cref="T:System.InvalidOperationException"> + /// The underlying <see cref="T:System.Threading.Tasks.Task{TResult}"/> is already in one + /// of the three final states: + /// <see cref="System.Threading.Tasks.TaskStatus.RanToCompletion">RanToCompletion</see>, + /// <see cref="System.Threading.Tasks.TaskStatus.Faulted">Faulted</see>, or + /// <see cref="System.Threading.Tasks.TaskStatus.Canceled">Canceled</see>. + /// </exception> + /// <exception cref="T:System.ObjectDisposedException">The <see cref="Task"/> was disposed.</exception> + public void SetResult(TResult result) + { + if (!TrySetResult(result)) + ThrowHelper.ThrowInvalidOperationException(ExceptionResource.TaskT_TransitionToFinal_AlreadyCompleted); + } + + /// <summary> + /// Attempts to transition the underlying + /// <see cref="T:System.Threading.Tasks.Task{TResult}"/> into the + /// <see cref="System.Threading.Tasks.TaskStatus.Canceled">Canceled</see> + /// state. + /// </summary> + /// <returns>True if the operation was successful; otherwise, false.</returns> + /// <remarks>This operation will return false if the + /// <see cref="T:System.Threading.Tasks.Task{TResult}"/> is already in one + /// of the three final states: + /// <see cref="System.Threading.Tasks.TaskStatus.RanToCompletion">RanToCompletion</see>, + /// <see cref="System.Threading.Tasks.TaskStatus.Faulted">Faulted</see>, or + /// <see cref="System.Threading.Tasks.TaskStatus.Canceled">Canceled</see>. + /// </remarks> + /// <exception cref="T:System.ObjectDisposedException">The <see cref="Task"/> was disposed.</exception> + public bool TrySetCanceled() + { + return TrySetCanceled(default(CancellationToken)); + } + + // Enables a token to be stored into the canceled task + public bool TrySetCanceled(CancellationToken cancellationToken) + { + bool rval = m_task.TrySetCanceled(cancellationToken); + if (!rval && !m_task.IsCompleted) SpinUntilCompleted(); + return rval; + } + + /// <summary> + /// Transitions the underlying + /// <see cref="T:System.Threading.Tasks.Task{TResult}"/> into the + /// <see cref="System.Threading.Tasks.TaskStatus.Canceled">Canceled</see> + /// state. + /// </summary> + /// <exception cref="T:System.InvalidOperationException"> + /// The underlying <see cref="T:System.Threading.Tasks.Task{TResult}"/> is already in one + /// of the three final states: + /// <see cref="System.Threading.Tasks.TaskStatus.RanToCompletion">RanToCompletion</see>, + /// <see cref="System.Threading.Tasks.TaskStatus.Faulted">Faulted</see>, or + /// <see cref="System.Threading.Tasks.TaskStatus.Canceled">Canceled</see>. + /// </exception> + /// <exception cref="T:System.ObjectDisposedException">The <see cref="Task"/> was disposed.</exception> + public void SetCanceled() + { + if(!TrySetCanceled()) + ThrowHelper.ThrowInvalidOperationException(ExceptionResource.TaskT_TransitionToFinal_AlreadyCompleted); + } + } +} diff --git a/src/mscorlib/src/System/Threading/Tasks/TaskContinuation.cs b/src/mscorlib/src/System/Threading/Tasks/TaskContinuation.cs new file mode 100644 index 0000000000..4c035dfddb --- /dev/null +++ b/src/mscorlib/src/System/Threading/Tasks/TaskContinuation.cs @@ -0,0 +1,867 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +// =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ +// +// +// +// Implementation of task continuations, TaskContinuation, and its descendants. +// +// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + +using System.Security; +using System.Diagnostics.Contracts; +using System.Runtime.ExceptionServices; +using System.Runtime.CompilerServices; +using System.Threading; + +#if FEATURE_COMINTEROP +using System.Runtime.InteropServices.WindowsRuntime; +#endif // FEATURE_COMINTEROP + +namespace System.Threading.Tasks +{ + // Task type used to implement: Task ContinueWith(Action<Task,...>) + internal sealed class ContinuationTaskFromTask : Task + { + private Task m_antecedent; + + public ContinuationTaskFromTask( + Task antecedent, Delegate action, object state, TaskCreationOptions creationOptions, InternalTaskOptions internalOptions, ref StackCrawlMark stackMark) : + base(action, state, Task.InternalCurrentIfAttached(creationOptions), default(CancellationToken), creationOptions, internalOptions, null) + { + Contract.Requires(action is Action<Task> || action is Action<Task, object>, + "Invalid delegate type in ContinuationTaskFromTask"); + m_antecedent = antecedent; + PossiblyCaptureContext(ref stackMark); + } + + /// <summary> + /// Evaluates the value selector of the Task which is passed in as an object and stores the result. + /// </summary> + internal override void InnerInvoke() + { + // Get and null out the antecedent. This is crucial to avoid a memory + // leak with long chains of continuations. + var antecedent = m_antecedent; + Contract.Assert(antecedent != null, + "No antecedent was set for the ContinuationTaskFromTask."); + m_antecedent = null; + + // Notify the debugger we're completing an asynchronous wait on a task + antecedent.NotifyDebuggerOfWaitCompletionIfNecessary(); + + // Invoke the delegate + Contract.Assert(m_action != null); + var action = m_action as Action<Task>; + if (action != null) + { + action(antecedent); + return; + } + var actionWithState = m_action as Action<Task, object>; + if (actionWithState != null) + { + actionWithState(antecedent, m_stateObject); + return; + } + Contract.Assert(false, "Invalid m_action in ContinuationTaskFromTask"); + } + } + + // Task type used to implement: Task<TResult> ContinueWith(Func<Task,...>) + internal sealed class ContinuationResultTaskFromTask<TResult> : Task<TResult> + { + private Task m_antecedent; + + public ContinuationResultTaskFromTask( + Task antecedent, Delegate function, object state, TaskCreationOptions creationOptions, InternalTaskOptions internalOptions, ref StackCrawlMark stackMark) : + base(function, state, Task.InternalCurrentIfAttached(creationOptions), default(CancellationToken), creationOptions, internalOptions, null) + { + Contract.Requires(function is Func<Task, TResult> || function is Func<Task, object, TResult>, + "Invalid delegate type in ContinuationResultTaskFromTask"); + m_antecedent = antecedent; + PossiblyCaptureContext(ref stackMark); + } + + /// <summary> + /// Evaluates the value selector of the Task which is passed in as an object and stores the result. + /// </summary> + internal override void InnerInvoke() + { + // Get and null out the antecedent. This is crucial to avoid a memory + // leak with long chains of continuations. + var antecedent = m_antecedent; + Contract.Assert(antecedent != null, + "No antecedent was set for the ContinuationResultTaskFromTask."); + m_antecedent = null; + + // Notify the debugger we're completing an asynchronous wait on a task + antecedent.NotifyDebuggerOfWaitCompletionIfNecessary(); + + // Invoke the delegate + Contract.Assert(m_action != null); + var func = m_action as Func<Task, TResult>; + if (func != null) + { + m_result = func(antecedent); + return; + } + var funcWithState = m_action as Func<Task, object, TResult>; + if (funcWithState != null) + { + m_result = funcWithState(antecedent, m_stateObject); + return; + } + Contract.Assert(false, "Invalid m_action in ContinuationResultTaskFromTask"); + } + } + + // Task type used to implement: Task ContinueWith(Action<Task<TAntecedentResult>,...>) + internal sealed class ContinuationTaskFromResultTask<TAntecedentResult> : Task + { + private Task<TAntecedentResult> m_antecedent; + + public ContinuationTaskFromResultTask( + Task<TAntecedentResult> antecedent, Delegate action, object state, TaskCreationOptions creationOptions, InternalTaskOptions internalOptions, ref StackCrawlMark stackMark) : + base(action, state, Task.InternalCurrentIfAttached(creationOptions), default(CancellationToken), creationOptions, internalOptions, null) + { + Contract.Requires(action is Action<Task<TAntecedentResult>> || action is Action<Task<TAntecedentResult>, object>, + "Invalid delegate type in ContinuationTaskFromResultTask"); + m_antecedent = antecedent; + PossiblyCaptureContext(ref stackMark); + } + + /// <summary> + /// Evaluates the value selector of the Task which is passed in as an object and stores the result. + /// </summary> + internal override void InnerInvoke() + { + // Get and null out the antecedent. This is crucial to avoid a memory + // leak with long chains of continuations. + var antecedent = m_antecedent; + Contract.Assert(antecedent != null, + "No antecedent was set for the ContinuationTaskFromResultTask."); + m_antecedent = null; + + // Notify the debugger we're completing an asynchronous wait on a task + antecedent.NotifyDebuggerOfWaitCompletionIfNecessary(); + + // Invoke the delegate + Contract.Assert(m_action != null); + var action = m_action as Action<Task<TAntecedentResult>>; + if (action != null) + { + action(antecedent); + return; + } + var actionWithState = m_action as Action<Task<TAntecedentResult>, object>; + if (actionWithState != null) + { + actionWithState(antecedent, m_stateObject); + return; + } + Contract.Assert(false, "Invalid m_action in ContinuationTaskFromResultTask"); + } + } + + // Task type used to implement: Task<TResult> ContinueWith(Func<Task<TAntecedentResult>,...>) + internal sealed class ContinuationResultTaskFromResultTask<TAntecedentResult, TResult> : Task<TResult> + { + private Task<TAntecedentResult> m_antecedent; + + public ContinuationResultTaskFromResultTask( + Task<TAntecedentResult> antecedent, Delegate function, object state, TaskCreationOptions creationOptions, InternalTaskOptions internalOptions, ref StackCrawlMark stackMark) : + base(function, state, Task.InternalCurrentIfAttached(creationOptions), default(CancellationToken), creationOptions, internalOptions, null) + { + Contract.Requires(function is Func<Task<TAntecedentResult>, TResult> || function is Func<Task<TAntecedentResult>, object, TResult>, + "Invalid delegate type in ContinuationResultTaskFromResultTask"); + m_antecedent = antecedent; + PossiblyCaptureContext(ref stackMark); + } + + /// <summary> + /// Evaluates the value selector of the Task which is passed in as an object and stores the result. + /// </summary> + internal override void InnerInvoke() + { + // Get and null out the antecedent. This is crucial to avoid a memory + // leak with long chains of continuations. + var antecedent = m_antecedent; + Contract.Assert(antecedent != null, + "No antecedent was set for the ContinuationResultTaskFromResultTask."); + m_antecedent = null; + + // Notify the debugger we're completing an asynchronous wait on a task + antecedent.NotifyDebuggerOfWaitCompletionIfNecessary(); + + // Invoke the delegate + Contract.Assert(m_action != null); + var func = m_action as Func<Task<TAntecedentResult>, TResult>; + if (func != null) + { + m_result = func(antecedent); + return; + } + var funcWithState = m_action as Func<Task<TAntecedentResult>, object, TResult>; + if (funcWithState != null) + { + m_result = funcWithState(antecedent, m_stateObject); + return; + } + Contract.Assert(false, "Invalid m_action in ContinuationResultTaskFromResultTask"); + } + } + + // For performance reasons, we don't just have a single way of representing + // a continuation object. Rather, we have a hierarchy of types: + // - TaskContinuation: abstract base that provides a virtual Run method + // - StandardTaskContinuation: wraps a task,options,and scheduler, and overrides Run to process the task with that configuration + // - AwaitTaskContinuation: base for continuations created through TaskAwaiter; targets default scheduler by default + // - TaskSchedulerAwaitTaskContinuation: awaiting with a non-default TaskScheduler + // - SynchronizationContextAwaitTaskContinuation: awaiting with a "current" sync ctx + + /// <summary>Represents a continuation.</summary> + internal abstract class TaskContinuation + { + /// <summary>Inlines or schedules the continuation.</summary> + /// <param name="completedTask">The antecedent task that has completed.</param> + /// <param name="canInlineContinuationTask">true if inlining is permitted; otherwise, false.</param> + internal abstract void Run(Task completedTask, bool bCanInlineContinuationTask); + + /// <summary>Tries to run the task on the current thread, if possible; otherwise, schedules it.</summary> + /// <param name="task">The task to run</param> + /// <param name="needsProtection"> + /// true if we need to protect against multiple threads racing to start/cancel the task; otherwise, false. + /// </param> + [SecuritySafeCritical] + protected static void InlineIfPossibleOrElseQueue(Task task, bool needsProtection) + { + Contract.Requires(task != null); + Contract.Assert(task.m_taskScheduler != null); + + // Set the TASK_STATE_STARTED flag. This only needs to be done + // if the task may be canceled or if someone else has a reference to it + // that may try to execute it. + if (needsProtection) + { + if (!task.MarkStarted()) + return; // task has been previously started or canceled. Stop processing. + } + else + { + task.m_stateFlags |= Task.TASK_STATE_STARTED; + } + + // Try to inline it but queue if we can't + try + { + if (!task.m_taskScheduler.TryRunInline(task, taskWasPreviouslyQueued: false)) + { + task.m_taskScheduler.InternalQueueTask(task); + } + } + catch (Exception e) + { + // Either TryRunInline() or QueueTask() threw an exception. Record the exception, marking the task as Faulted. + // However if it was a ThreadAbortException coming from TryRunInline we need to skip here, + // because it would already have been handled in Task.Execute() + if (!(e is ThreadAbortException && + (task.m_stateFlags & Task.TASK_STATE_THREAD_WAS_ABORTED) != 0)) // this ensures TAEs from QueueTask will be wrapped in TSE + { + TaskSchedulerException tse = new TaskSchedulerException(e); + task.AddException(tse); + task.Finish(false); + } + + // Don't re-throw. + } + } + + internal abstract Delegate[] GetDelegateContinuationsForDebugger(); + + } + + /// <summary>Provides the standard implementation of a task continuation.</summary> + internal class StandardTaskContinuation : TaskContinuation + { + /// <summary>The unstarted continuation task.</summary> + internal readonly Task m_task; + /// <summary>The options to use with the continuation task.</summary> + internal readonly TaskContinuationOptions m_options; + /// <summary>The task scheduler with which to run the continuation task.</summary> + private readonly TaskScheduler m_taskScheduler; + + /// <summary>Initializes a new continuation.</summary> + /// <param name="task">The task to be activated.</param> + /// <param name="options">The continuation options.</param> + /// <param name="scheduler">The scheduler to use for the continuation.</param> + internal StandardTaskContinuation(Task task, TaskContinuationOptions options, TaskScheduler scheduler) + { + Contract.Requires(task != null, "TaskContinuation ctor: task is null"); + Contract.Requires(scheduler != null, "TaskContinuation ctor: scheduler is null"); + m_task = task; + m_options = options; + m_taskScheduler = scheduler; + if (AsyncCausalityTracer.LoggingOn) + AsyncCausalityTracer.TraceOperationCreation(CausalityTraceLevel.Required, m_task.Id, "Task.ContinueWith: " + ((Delegate)task.m_action).Method.Name, 0); + + if (Task.s_asyncDebuggingEnabled) + { + Task.AddToActiveTasks(m_task); + } + } + + /// <summary>Invokes the continuation for the target completion task.</summary> + /// <param name="completedTask">The completed task.</param> + /// <param name="bCanInlineContinuationTask">Whether the continuation can be inlined.</param> + internal override void Run(Task completedTask, bool bCanInlineContinuationTask) + { + Contract.Assert(completedTask != null); + Contract.Assert(completedTask.IsCompleted, "ContinuationTask.Run(): completedTask not completed"); + + // Check if the completion status of the task works with the desired + // activation criteria of the TaskContinuationOptions. + TaskContinuationOptions options = m_options; + bool isRightKind = + completedTask.IsRanToCompletion ? + (options & TaskContinuationOptions.NotOnRanToCompletion) == 0 : + (completedTask.IsCanceled ? + (options & TaskContinuationOptions.NotOnCanceled) == 0 : + (options & TaskContinuationOptions.NotOnFaulted) == 0); + + // If the completion status is allowed, run the continuation. + Task continuationTask = m_task; + if (isRightKind) + { + //If the task was cancel before running (e.g a ContinueWhenAll with a cancelled caancelation token) + //we will still flow it to ScheduleAndStart() were it will check the status before running + //We check here to avoid faulty logs that contain a join event to an operation that was already set as completed. + if (!continuationTask.IsCanceled && AsyncCausalityTracer.LoggingOn) + { + // Log now that we are sure that this continuation is being ran + AsyncCausalityTracer.TraceOperationRelation(CausalityTraceLevel.Important, continuationTask.Id, CausalityRelation.AssignDelegate); + } + continuationTask.m_taskScheduler = m_taskScheduler; + + // Either run directly or just queue it up for execution, depending + // on whether synchronous or asynchronous execution is wanted. + if (bCanInlineContinuationTask && // inlining is allowed by the caller + (options & TaskContinuationOptions.ExecuteSynchronously) != 0) // synchronous execution was requested by the continuation's creator + { + InlineIfPossibleOrElseQueue(continuationTask, needsProtection: true); + } + else + { + try { continuationTask.ScheduleAndStart(needsProtection: true); } + catch (TaskSchedulerException) + { + // No further action is necessary -- ScheduleAndStart() already transitioned the + // task to faulted. But we want to make sure that no exception is thrown from here. + } + } + } + // Otherwise, the final state of this task does not match the desired + // continuation activation criteria; cancel it to denote this. + else continuationTask.InternalCancel(false); + } + + internal override Delegate[] GetDelegateContinuationsForDebugger() + { + if (m_task.m_action == null) + { + return m_task.GetDelegateContinuationsForDebugger(); + } + + return new Delegate[] { m_task.m_action as Delegate }; + } + } + + /// <summary>Task continuation for awaiting with a current synchronization context.</summary> + internal sealed class SynchronizationContextAwaitTaskContinuation : AwaitTaskContinuation + { + /// <summary>SendOrPostCallback delegate to invoke the action.</summary> + private readonly static SendOrPostCallback s_postCallback = state => ((Action)state)(); // can't use InvokeAction as it's SecurityCritical + /// <summary>Cached delegate for PostAction</summary> + [SecurityCritical] + private static ContextCallback s_postActionCallback; + /// <summary>The context with which to run the action.</summary> + private readonly SynchronizationContext m_syncContext; + + /// <summary>Initializes the SynchronizationContextAwaitTaskContinuation.</summary> + /// <param name="context">The synchronization context with which to invoke the action. Must not be null.</param> + /// <param name="action">The action to invoke. Must not be null.</param> + /// <param name="flowExecutionContext">Whether to capture and restore ExecutionContext.</param> + /// <param name="stackMark">The captured stack mark.</param> + [SecurityCritical] + internal SynchronizationContextAwaitTaskContinuation( + SynchronizationContext context, Action action, bool flowExecutionContext, ref StackCrawlMark stackMark) : + base(action, flowExecutionContext, ref stackMark) + { + Contract.Assert(context != null); + m_syncContext = context; + } + + /// <summary>Inlines or schedules the continuation.</summary> + /// <param name="ignored">The antecedent task, which is ignored.</param> + /// <param name="canInlineContinuationTask">true if inlining is permitted; otherwise, false.</param> + [SecuritySafeCritical] + internal sealed override void Run(Task task, bool canInlineContinuationTask) + { + // If we're allowed to inline, run the action on this thread. + if (canInlineContinuationTask && + m_syncContext == SynchronizationContext.CurrentNoFlow) + { + RunCallback(GetInvokeActionCallback(), m_action, ref Task.t_currentTask); + } + // Otherwise, Post the action back to the SynchronizationContext. + else + { + TplEtwProvider etwLog = TplEtwProvider.Log; + if (etwLog.IsEnabled()) + { + m_continuationId = Task.NewId(); + etwLog.AwaitTaskContinuationScheduled((task.ExecutingTaskScheduler ?? TaskScheduler.Default).Id, task.Id, m_continuationId); + } + RunCallback(GetPostActionCallback(), this, ref Task.t_currentTask); + } + // Any exceptions will be handled by RunCallback. + } + + /// <summary>Calls InvokeOrPostAction(false) on the supplied SynchronizationContextAwaitTaskContinuation.</summary> + /// <param name="state">The SynchronizationContextAwaitTaskContinuation.</param> + [SecurityCritical] + private static void PostAction(object state) + { + var c = (SynchronizationContextAwaitTaskContinuation)state; + + TplEtwProvider etwLog = TplEtwProvider.Log; + if (etwLog.TasksSetActivityIds && c.m_continuationId != 0) + { + c.m_syncContext.Post(s_postCallback, GetActionLogDelegate(c.m_continuationId, c.m_action)); + } + else + { + c.m_syncContext.Post(s_postCallback, c.m_action); // s_postCallback is manually cached, as the compiler won't in a SecurityCritical method + } + } + + private static Action GetActionLogDelegate(int continuationId, Action action) + { + return () => + { + Guid savedActivityId; + Guid activityId = TplEtwProvider.CreateGuidForTaskID(continuationId); + System.Diagnostics.Tracing.EventSource.SetCurrentThreadActivityId(activityId, out savedActivityId); + try { action(); } + finally { System.Diagnostics.Tracing.EventSource.SetCurrentThreadActivityId(savedActivityId); } + }; + } + + /// <summary>Gets a cached delegate for the PostAction method.</summary> + /// <returns> + /// A delegate for PostAction, which expects a SynchronizationContextAwaitTaskContinuation + /// to be passed as state. + /// </returns> + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [SecurityCritical] + private static ContextCallback GetPostActionCallback() + { + ContextCallback callback = s_postActionCallback; + if (callback == null) { s_postActionCallback = callback = PostAction; } // lazily initialize SecurityCritical delegate + return callback; + } + } + + /// <summary>Task continuation for awaiting with a task scheduler.</summary> + internal sealed class TaskSchedulerAwaitTaskContinuation : AwaitTaskContinuation + { + /// <summary>The scheduler on which to run the action.</summary> + private readonly TaskScheduler m_scheduler; + + /// <summary>Initializes the TaskSchedulerAwaitTaskContinuation.</summary> + /// <param name="scheduler">The task scheduler with which to invoke the action. Must not be null.</param> + /// <param name="action">The action to invoke. Must not be null.</param> + /// <param name="flowExecutionContext">Whether to capture and restore ExecutionContext.</param> + /// <param name="stackMark">The captured stack mark.</param> + [SecurityCritical] + internal TaskSchedulerAwaitTaskContinuation( + TaskScheduler scheduler, Action action, bool flowExecutionContext, ref StackCrawlMark stackMark) : + base(action, flowExecutionContext, ref stackMark) + { + Contract.Assert(scheduler != null); + m_scheduler = scheduler; + } + + /// <summary>Inlines or schedules the continuation.</summary> + /// <param name="ignored">The antecedent task, which is ignored.</param> + /// <param name="canInlineContinuationTask">true if inlining is permitted; otherwise, false.</param> + internal sealed override void Run(Task ignored, bool canInlineContinuationTask) + { + // If we're targeting the default scheduler, we can use the faster path provided by the base class. + if (m_scheduler == TaskScheduler.Default) + { + base.Run(ignored, canInlineContinuationTask); + } + else + { + // We permit inlining if the caller allows us to, and + // either we're on a thread pool thread (in which case we're fine running arbitrary code) + // or we're already on the target scheduler (in which case we'll just ask the scheduler + // whether it's ok to run here). We include the IsThreadPoolThread check here, whereas + // we don't in AwaitTaskContinuation.Run, since here it expands what's allowed as opposed + // to in AwaitTaskContinuation.Run where it restricts what's allowed. + bool inlineIfPossible = canInlineContinuationTask && + (TaskScheduler.InternalCurrent == m_scheduler || Thread.CurrentThread.IsThreadPoolThread); + + // Create the continuation task task. If we're allowed to inline, try to do so. + // The target scheduler may still deny us from executing on this thread, in which case this'll be queued. + var task = CreateTask(state => { + try { ((Action)state)(); } + catch (Exception exc) { ThrowAsyncIfNecessary(exc); } + }, m_action, m_scheduler); + + if (inlineIfPossible) + { + InlineIfPossibleOrElseQueue(task, needsProtection: false); + } + else + { + // We need to run asynchronously, so just schedule the task. + try { task.ScheduleAndStart(needsProtection: false); } + catch (TaskSchedulerException) { } // No further action is necessary, as ScheduleAndStart already transitioned task to faulted + } + } + } + } + + /// <summary>Base task continuation class used for await continuations.</summary> + internal class AwaitTaskContinuation : TaskContinuation, IThreadPoolWorkItem + { + /// <summary>The ExecutionContext with which to run the continuation.</summary> + private readonly ExecutionContext m_capturedContext; + /// <summary>The action to invoke.</summary> + protected readonly Action m_action; + + protected int m_continuationId; + + /// <summary>Initializes the continuation.</summary> + /// <param name="action">The action to invoke. Must not be null.</param> + /// <param name="flowExecutionContext">Whether to capture and restore ExecutionContext.</param> + /// <param name="stackMark">The captured stack mark with which to construct an ExecutionContext.</param> + [SecurityCritical] + internal AwaitTaskContinuation(Action action, bool flowExecutionContext, ref StackCrawlMark stackMark) + { + Contract.Requires(action != null); + m_action = action; + if (flowExecutionContext) + { + m_capturedContext = ExecutionContext.Capture( + ref stackMark, + ExecutionContext.CaptureOptions.IgnoreSyncCtx | ExecutionContext.CaptureOptions.OptimizeDefaultCase); + } + } + + /// <summary>Initializes the continuation.</summary> + /// <param name="action">The action to invoke. Must not be null.</param> + /// <param name="flowExecutionContext">Whether to capture and restore ExecutionContext.</param> + [SecurityCritical] + internal AwaitTaskContinuation(Action action, bool flowExecutionContext) + { + Contract.Requires(action != null); + m_action = action; + if (flowExecutionContext) + { + m_capturedContext = ExecutionContext.FastCapture(); + } + } + + /// <summary>Creates a task to run the action with the specified state on the specified scheduler.</summary> + /// <param name="action">The action to run. Must not be null.</param> + /// <param name="state">The state to pass to the action. Must not be null.</param> + /// <param name="scheduler">The scheduler to target.</param> + /// <returns>The created task.</returns> + protected Task CreateTask(Action<object> action, object state, TaskScheduler scheduler) + { + Contract.Requires(action != null); + Contract.Requires(scheduler != null); + + return new Task( + action, state, null, default(CancellationToken), + TaskCreationOptions.None, InternalTaskOptions.QueuedByRuntime, scheduler) + { + CapturedContext = m_capturedContext + }; + } + + /// <summary>Inlines or schedules the continuation onto the default scheduler.</summary> + /// <param name="ignored">The antecedent task, which is ignored.</param> + /// <param name="canInlineContinuationTask">true if inlining is permitted; otherwise, false.</param> + [SecuritySafeCritical] + internal override void Run(Task task, bool canInlineContinuationTask) + { + // For the base AwaitTaskContinuation, we allow inlining if our caller allows it + // and if we're in a "valid location" for it. See the comments on + // IsValidLocationForInlining for more about what's valid. For performance + // reasons we would like to always inline, but we don't in some cases to avoid + // running arbitrary amounts of work in suspected "bad locations", like UI threads. + if (canInlineContinuationTask && IsValidLocationForInlining) + { + RunCallback(GetInvokeActionCallback(), m_action, ref Task.t_currentTask); // any exceptions from m_action will be handled by s_callbackRunAction + } + else + { + TplEtwProvider etwLog = TplEtwProvider.Log; + if (etwLog.IsEnabled()) + { + m_continuationId = Task.NewId(); + etwLog.AwaitTaskContinuationScheduled((task.ExecutingTaskScheduler ?? TaskScheduler.Default).Id, task.Id, m_continuationId); + } + + // We couldn't inline, so now we need to schedule it + ThreadPool.UnsafeQueueCustomWorkItem(this, forceGlobal: false); + } + } + + /// <summary> + /// Gets whether the current thread is an appropriate location to inline a continuation's execution. + /// </summary> + /// <remarks> + /// Returns whether SynchronizationContext is null and we're in the default scheduler. + /// If the await had a SynchronizationContext/TaskScheduler where it began and the + /// default/ConfigureAwait(true) was used, then we won't be on this path. If, however, + /// ConfigureAwait(false) was used, or the SynchronizationContext and TaskScheduler were + /// naturally null/Default, then we might end up here. If we do, we need to make sure + /// that we don't execute continuations in a place that isn't set up to handle them, e.g. + /// running arbitrary amounts of code on the UI thread. It would be "correct", but very + /// expensive, to always run the continuations asynchronously, incurring lots of context + /// switches and allocations and locks and the like. As such, we employ the heuristic + /// that if the current thread has a non-null SynchronizationContext or a non-default + /// scheduler, then we better not run arbitrary continuations here. + /// </remarks> + internal static bool IsValidLocationForInlining + { + get + { + // If there's a SynchronizationContext, we'll be conservative and say + // this is a bad location to inline. + var ctx = SynchronizationContext.CurrentNoFlow; + if (ctx != null && ctx.GetType() != typeof(SynchronizationContext)) return false; + + // Similarly, if there's a non-default TaskScheduler, we'll be conservative + // and say this is a bad location to inline. + var sched = TaskScheduler.InternalCurrent; + return sched == null || sched == TaskScheduler.Default; + } + } + + /// <summary>IThreadPoolWorkItem override, which is the entry function for this when the ThreadPool scheduler decides to run it.</summary> + [SecurityCritical] + void ExecuteWorkItemHelper() + { + var etwLog = TplEtwProvider.Log; + Guid savedActivityId = Guid.Empty; + if (etwLog.TasksSetActivityIds && m_continuationId != 0) + { + Guid activityId = TplEtwProvider.CreateGuidForTaskID(m_continuationId); + System.Diagnostics.Tracing.EventSource.SetCurrentThreadActivityId(activityId, out savedActivityId); + } + try + { + // We're not inside of a task, so t_currentTask doesn't need to be specially maintained. + // We're on a thread pool thread with no higher-level callers, so exceptions can just propagate. + + // If there's no execution context, just invoke the delegate. + if (m_capturedContext == null) + { + m_action(); + } + // If there is an execution context, get the cached delegate and run the action under the context. + else + { + try + { + ExecutionContext.Run(m_capturedContext, GetInvokeActionCallback(), m_action, true); + } + finally { m_capturedContext.Dispose(); } + } + } + finally + { + if (etwLog.TasksSetActivityIds && m_continuationId != 0) + { + System.Diagnostics.Tracing.EventSource.SetCurrentThreadActivityId(savedActivityId); + } + } + } + + [SecurityCritical] + void IThreadPoolWorkItem.ExecuteWorkItem() + { + // inline the fast path + if (m_capturedContext == null && !TplEtwProvider.Log.IsEnabled() + ) + { + m_action(); + } + else + { + ExecuteWorkItemHelper(); + } + } + + /// <summary> + /// The ThreadPool calls this if a ThreadAbortException is thrown while trying to execute this workitem. + /// </summary> + [SecurityCritical] + void IThreadPoolWorkItem.MarkAborted(ThreadAbortException tae) { /* nop */ } + + /// <summary>Cached delegate that invokes an Action passed as an object parameter.</summary> + [SecurityCritical] + private static ContextCallback s_invokeActionCallback; + + /// <summary>Runs an action provided as an object parameter.</summary> + /// <param name="state">The Action to invoke.</param> + [SecurityCritical] + private static void InvokeAction(object state) { ((Action)state)(); } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [SecurityCritical] + protected static ContextCallback GetInvokeActionCallback() + { + ContextCallback callback = s_invokeActionCallback; + if (callback == null) { s_invokeActionCallback = callback = InvokeAction; } // lazily initialize SecurityCritical delegate + return callback; + } + + /// <summary>Runs the callback synchronously with the provided state.</summary> + /// <param name="callback">The callback to run.</param> + /// <param name="state">The state to pass to the callback.</param> + /// <param name="currentTask">A reference to Task.t_currentTask.</param> + [SecurityCritical] + protected void RunCallback(ContextCallback callback, object state, ref Task currentTask) + { + Contract.Requires(callback != null); + Contract.Assert(currentTask == Task.t_currentTask); + + // Pretend there's no current task, so that no task is seen as a parent + // and TaskScheduler.Current does not reflect false information + var prevCurrentTask = currentTask; + try + { + if (prevCurrentTask != null) currentTask = null; + + // If there's no captured context, just run the callback directly. + if (m_capturedContext == null) callback(state); + // Otherwise, use the captured context to do so. + else ExecutionContext.Run(m_capturedContext, callback, state, true); + } + catch (Exception exc) // we explicitly do not request handling of dangerous exceptions like AVs + { + ThrowAsyncIfNecessary(exc); + } + finally + { + // Restore the current task information + if (prevCurrentTask != null) currentTask = prevCurrentTask; + + // Clean up after the execution context, which is only usable once. + if (m_capturedContext != null) m_capturedContext.Dispose(); + } + } + + /// <summary>Invokes or schedules the action to be executed.</summary> + /// <param name="action">The action to invoke or queue.</param> + /// <param name="allowInlining"> + /// true to allow inlining, or false to force the action to run asynchronously. + /// </param> + /// <param name="currentTask"> + /// A reference to the t_currentTask thread static value. + /// This is passed by-ref rather than accessed in the method in order to avoid + /// unnecessary thread-static writes. + /// </param> + /// <remarks> + /// No ExecutionContext work is performed used. This method is only used in the + /// case where a raw Action continuation delegate was stored into the Task, which + /// only happens in Task.SetContinuationForAwait if execution context flow was disabled + /// via using TaskAwaiter.UnsafeOnCompleted or a similar path. + /// </remarks> + [SecurityCritical] + internal static void RunOrScheduleAction(Action action, bool allowInlining, ref Task currentTask) + { + Contract.Assert(currentTask == Task.t_currentTask); + + // If we're not allowed to run here, schedule the action + if (!allowInlining || !IsValidLocationForInlining) + { + UnsafeScheduleAction(action, currentTask); + return; + } + + // Otherwise, run it, making sure that t_currentTask is null'd out appropriately during the execution + Task prevCurrentTask = currentTask; + try + { + if (prevCurrentTask != null) currentTask = null; + action(); + } + catch (Exception exception) + { + ThrowAsyncIfNecessary(exception); + } + finally + { + if (prevCurrentTask != null) currentTask = prevCurrentTask; + } + } + + /// <summary>Schedules the action to be executed. No ExecutionContext work is performed used.</summary> + /// <param name="action">The action to invoke or queue.</param> + [SecurityCritical] + internal static void UnsafeScheduleAction(Action action, Task task) + { + AwaitTaskContinuation atc = new AwaitTaskContinuation(action, flowExecutionContext: false); + + var etwLog = TplEtwProvider.Log; + if (etwLog.IsEnabled() && task != null) + { + atc.m_continuationId = Task.NewId(); + etwLog.AwaitTaskContinuationScheduled((task.ExecutingTaskScheduler ?? TaskScheduler.Default).Id, task.Id, atc.m_continuationId); + } + + ThreadPool.UnsafeQueueCustomWorkItem(atc, forceGlobal: false); + } + + /// <summary>Throws the exception asynchronously on the ThreadPool.</summary> + /// <param name="exc">The exception to throw.</param> + protected static void ThrowAsyncIfNecessary(Exception exc) + { + // Awaits should never experience an exception (other than an TAE or ADUE), + // unless a malicious user is explicitly passing a throwing action into the TaskAwaiter. + // We don't want to allow the exception to propagate on this stack, as it'll emerge in random places, + // and we can't fail fast, as that would allow for elevation of privilege. + // + // If unhandled error reporting APIs are available use those, otherwise since this + // would have executed on the thread pool otherwise, let it propagate there. + + if (!(exc is ThreadAbortException || exc is AppDomainUnloadedException)) + { +#if FEATURE_COMINTEROP + if (!WindowsRuntimeMarshal.ReportUnhandledError(exc)) +#endif // FEATURE_COMINTEROP + { + var edi = ExceptionDispatchInfo.Capture(exc); + ThreadPool.QueueUserWorkItem(s => ((ExceptionDispatchInfo)s).Throw(), edi); + } + } + } + + internal override Delegate[] GetDelegateContinuationsForDebugger() + { + Contract.Assert(m_action != null); + return new Delegate[] { AsyncMethodBuilderCore.TryGetStateMachineForDebugger(m_action) }; + } + } + +} diff --git a/src/mscorlib/src/System/Threading/Tasks/TaskExceptionHolder.cs b/src/mscorlib/src/System/Threading/Tasks/TaskExceptionHolder.cs new file mode 100644 index 0000000000..198db8e15c --- /dev/null +++ b/src/mscorlib/src/System/Threading/Tasks/TaskExceptionHolder.cs @@ -0,0 +1,424 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +// =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ +// +// +// +// An abstraction for holding and aggregating exceptions. +// +// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + +// Disable the "reference to volatile field not treated as volatile" error. +#pragma warning disable 0420 + +namespace System.Threading.Tasks +{ + using System; + using System.Collections.Generic; + using System.Collections.ObjectModel; + using System.Diagnostics.Contracts; + using System.Runtime.ExceptionServices; + using System.Security; + + /// <summary> + /// An exception holder manages a list of exceptions for one particular task. + /// It offers the ability to aggregate, but more importantly, also offers intrinsic + /// support for propagating unhandled exceptions that are never observed. It does + /// this by aggregating and throwing if the holder is ever GC'd without the holder's + /// contents ever having been requested (e.g. by a Task.Wait, Task.get_Exception, etc). + /// This behavior is prominent in .NET 4 but is suppressed by default beyond that release. + /// </summary> + internal class TaskExceptionHolder + { + /// <summary>Whether we should propagate exceptions on the finalizer.</summary> + private readonly static bool s_failFastOnUnobservedException = ShouldFailFastOnUnobservedException(); + /// <summary>Whether the AppDomain has started to unload.</summary> + private static volatile bool s_domainUnloadStarted; + /// <summary>An event handler used to notify of domain unload.</summary> + private static volatile EventHandler s_adUnloadEventHandler; + + /// <summary>The task with which this holder is associated.</summary> + private readonly Task m_task; + /// <summary> + /// The lazily-initialized list of faulting exceptions. Volatile + /// so that it may be read to determine whether any exceptions were stored. + /// </summary> + private volatile List<ExceptionDispatchInfo> m_faultExceptions; + /// <summary>An exception that triggered the task to cancel.</summary> + private ExceptionDispatchInfo m_cancellationException; + /// <summary>Whether the holder was "observed" and thus doesn't cause finalization behavior.</summary> + private volatile bool m_isHandled; + + /// <summary> + /// Creates a new holder; it will be registered for finalization. + /// </summary> + /// <param name="task">The task this holder belongs to.</param> + internal TaskExceptionHolder(Task task) + { + Contract.Requires(task != null, "Expected a non-null task."); + m_task = task; + EnsureADUnloadCallbackRegistered(); + } + + [SecuritySafeCritical] + private static bool ShouldFailFastOnUnobservedException() + { + bool shouldFailFast = false; + #if !FEATURE_CORECLR + shouldFailFast = System.CLRConfig.CheckThrowUnobservedTaskExceptions(); + #endif + return shouldFailFast; + } + + private static void EnsureADUnloadCallbackRegistered() + { + if (s_adUnloadEventHandler == null && + Interlocked.CompareExchange( ref s_adUnloadEventHandler, + AppDomainUnloadCallback, + null) == null) + { + AppDomain.CurrentDomain.DomainUnload += s_adUnloadEventHandler; + } + } + + private static void AppDomainUnloadCallback(object sender, EventArgs e) + { + s_domainUnloadStarted = true; + } + + /// <summary> + /// A finalizer that repropagates unhandled exceptions. + /// </summary> + ~TaskExceptionHolder() + { + // Raise unhandled exceptions only when we know that neither the process or nor the appdomain is being torn down. + // We need to do this filtering because all TaskExceptionHolders will be finalized during shutdown or unload + // regardles of reachability of the task (i.e. even if the user code was about to observe the task's exception), + // which can otherwise lead to spurious crashes during shutdown. + if (m_faultExceptions != null && !m_isHandled && + !Environment.HasShutdownStarted && !AppDomain.CurrentDomain.IsFinalizingForUnload() && !s_domainUnloadStarted) + { + // We don't want to crash the finalizer thread if any ThreadAbortExceptions + // occur in the list or in any nested AggregateExceptions. + // (Don't rethrow ThreadAbortExceptions.) + foreach (ExceptionDispatchInfo edi in m_faultExceptions) + { + var exp = edi.SourceException; + AggregateException aggExp = exp as AggregateException; + if (aggExp != null) + { + AggregateException flattenedAggExp = aggExp.Flatten(); + foreach (Exception innerExp in flattenedAggExp.InnerExceptions) + { + if (innerExp is ThreadAbortException) + return; + } + } + else if (exp is ThreadAbortException) + { + return; + } + } + + // We will only propagate if this is truly unhandled. The reason this could + // ever occur is somewhat subtle: if a Task's exceptions are observed in some + // other finalizer, and the Task was finalized before the holder, the holder + // will have been marked as handled before even getting here. + + // Give users a chance to keep this exception from crashing the process + + // First, publish the unobserved exception and allow users to observe it + AggregateException exceptionToThrow = new AggregateException( + Environment.GetResourceString("TaskExceptionHolder_UnhandledException"), + m_faultExceptions); + UnobservedTaskExceptionEventArgs ueea = new UnobservedTaskExceptionEventArgs(exceptionToThrow); + TaskScheduler.PublishUnobservedTaskException(m_task, ueea); + + // Now, if we are still unobserved and we're configured to crash on unobserved, throw the exception. + // We need to publish the event above even if we're not going to crash, hence + // why this check doesn't come at the beginning of the method. + if (s_failFastOnUnobservedException && !ueea.m_observed) + { + throw exceptionToThrow; + } + } + } + + /// <summary>Gets whether the exception holder is currently storing any exceptions for faults.</summary> + internal bool ContainsFaultList { get { return m_faultExceptions != null; } } + + /// <summary> + /// Add an exception to the holder. This will ensure the holder is + /// in the proper state (handled/unhandled) depending on the list's contents. + /// </summary> + /// <param name="exceptionObject"> + /// An exception object (either an Exception, an ExceptionDispatchInfo, + /// an IEnumerable{Exception}, or an IEnumerable{ExceptionDispatchInfo}) + /// to add to the list. + /// </param> + /// <remarks> + /// Must be called under lock. + /// </remarks> + internal void Add(object exceptionObject) + { + Add(exceptionObject, representsCancellation: false); + } + + /// <summary> + /// Add an exception to the holder. This will ensure the holder is + /// in the proper state (handled/unhandled) depending on the list's contents. + /// </summary> + /// <param name="representsCancellation"> + /// Whether the exception represents a cancellation request (true) or a fault (false). + /// </param> + /// <param name="exceptionObject"> + /// An exception object (either an Exception, an ExceptionDispatchInfo, + /// an IEnumerable{Exception}, or an IEnumerable{ExceptionDispatchInfo}) + /// to add to the list. + /// </param> + /// <remarks> + /// Must be called under lock. + /// </remarks> + internal void Add(object exceptionObject, bool representsCancellation) + { + Contract.Requires(exceptionObject != null, "TaskExceptionHolder.Add(): Expected a non-null exceptionObject"); + Contract.Requires( + exceptionObject is Exception || exceptionObject is IEnumerable<Exception> || + exceptionObject is ExceptionDispatchInfo || exceptionObject is IEnumerable<ExceptionDispatchInfo>, + "TaskExceptionHolder.Add(): Expected Exception, IEnumerable<Exception>, ExceptionDispatchInfo, or IEnumerable<ExceptionDispatchInfo>"); + + if (representsCancellation) SetCancellationException(exceptionObject); + else AddFaultException(exceptionObject); + } + + /// <summary>Sets the cancellation exception.</summary> + /// <param name="exceptionObject">The cancellation exception.</param> + /// <remarks> + /// Must be called under lock. + /// </remarks> + private void SetCancellationException(object exceptionObject) + { + Contract.Requires(exceptionObject != null, "Expected exceptionObject to be non-null."); + + Contract.Assert(m_cancellationException == null, + "Expected SetCancellationException to be called only once."); + // Breaking this assumption will overwrite a previously OCE, + // and implies something may be wrong elsewhere, since there should only ever be one. + + Contract.Assert(m_faultExceptions == null, + "Expected SetCancellationException to be called before any faults were added."); + // Breaking this assumption shouldn't hurt anything here, but it implies something may be wrong elsewhere. + // If this changes, make sure to only conditionally mark as handled below. + + // Store the cancellation exception + var oce = exceptionObject as OperationCanceledException; + if (oce != null) + { + m_cancellationException = ExceptionDispatchInfo.Capture(oce); + } + else + { + var edi = exceptionObject as ExceptionDispatchInfo; + Contract.Assert(edi != null && edi.SourceException is OperationCanceledException, + "Expected an OCE or an EDI that contained an OCE"); + m_cancellationException = edi; + } + + // This is just cancellation, and there are no faults, so mark the holder as handled. + MarkAsHandled(false); + } + + /// <summary>Adds the exception to the fault list.</summary> + /// <param name="exceptionObject">The exception to store.</param> + /// <remarks> + /// Must be called under lock. + /// </remarks> + private void AddFaultException(object exceptionObject) + { + Contract.Requires(exceptionObject != null, "AddFaultException(): Expected a non-null exceptionObject"); + + // Initialize the exceptions list if necessary. The list should be non-null iff it contains exceptions. + var exceptions = m_faultExceptions; + if (exceptions == null) m_faultExceptions = exceptions = new List<ExceptionDispatchInfo>(1); + else Contract.Assert(exceptions.Count > 0, "Expected existing exceptions list to have > 0 exceptions."); + + // Handle Exception by capturing it into an ExceptionDispatchInfo and storing that + var exception = exceptionObject as Exception; + if (exception != null) + { + exceptions.Add(ExceptionDispatchInfo.Capture(exception)); + } + else + { + // Handle ExceptionDispatchInfo by storing it into the list + var edi = exceptionObject as ExceptionDispatchInfo; + if (edi != null) + { + exceptions.Add(edi); + } + else + { + // Handle enumerables of exceptions by capturing each of the contained exceptions into an EDI and storing it + var exColl = exceptionObject as IEnumerable<Exception>; + if (exColl != null) + { +#if DEBUG + int numExceptions = 0; +#endif + foreach (var exc in exColl) + { +#if DEBUG + Contract.Assert(exc != null, "No exceptions should be null"); + numExceptions++; +#endif + exceptions.Add(ExceptionDispatchInfo.Capture(exc)); + } +#if DEBUG + Contract.Assert(numExceptions > 0, "Collection should contain at least one exception."); +#endif + } + else + { + // Handle enumerables of EDIs by storing them directly + var ediColl = exceptionObject as IEnumerable<ExceptionDispatchInfo>; + if (ediColl != null) + { + exceptions.AddRange(ediColl); +#if DEBUG + Contract.Assert(exceptions.Count > 0, "There should be at least one dispatch info."); + foreach(var tmp in exceptions) + { + Contract.Assert(tmp != null, "No dispatch infos should be null"); + } +#endif + } + // Anything else is a programming error + else + { + throw new ArgumentException(Environment.GetResourceString("TaskExceptionHolder_UnknownExceptionType"), "exceptionObject"); + } + } + } + } + + + // If all of the exceptions are ThreadAbortExceptions and/or + // AppDomainUnloadExceptions, we do not want the finalization + // probe to propagate them, so we consider the holder to be + // handled. If a subsequent exception comes in of a different + // kind, we will reactivate the holder. + for (int i = 0; i < exceptions.Count; i++) + { + var t = exceptions[i].SourceException.GetType(); + if (t != typeof(ThreadAbortException) && t != typeof(AppDomainUnloadedException)) + { + MarkAsUnhandled(); + break; + } + else if (i == exceptions.Count - 1) + { + MarkAsHandled(false); + } + } + } + + /// <summary> + /// A private helper method that ensures the holder is considered + /// unhandled, i.e. it is registered for finalization. + /// </summary> + private void MarkAsUnhandled() + { + // If a thread partially observed this thread's exceptions, we + // should revert back to "not handled" so that subsequent exceptions + // must also be seen. Otherwise, some could go missing. We also need + // to reregister for finalization. + if (m_isHandled) + { + GC.ReRegisterForFinalize(this); + m_isHandled = false; + } + } + + /// <summary> + /// A private helper method that ensures the holder is considered + /// handled, i.e. it is not registered for finalization. + /// </summary> + /// <param name="calledFromFinalizer">Whether this is called from the finalizer thread.</param> + internal void MarkAsHandled(bool calledFromFinalizer) + { + if (!m_isHandled) + { + if (!calledFromFinalizer) + { + GC.SuppressFinalize(this); + } + + m_isHandled = true; + } + } + + /// <summary> + /// Allocates a new aggregate exception and adds the contents of the list to + /// it. By calling this method, the holder assumes exceptions to have been + /// "observed", such that the finalization check will be subsequently skipped. + /// </summary> + /// <param name="calledFromFinalizer">Whether this is being called from a finalizer.</param> + /// <param name="includeThisException">An extra exception to be included (optionally).</param> + /// <returns>The aggregate exception to throw.</returns> + internal AggregateException CreateExceptionObject(bool calledFromFinalizer, Exception includeThisException) + { + var exceptions = m_faultExceptions; + Contract.Assert(exceptions != null, "Expected an initialized list."); + Contract.Assert(exceptions.Count > 0, "Expected at least one exception."); + + // Mark as handled and aggregate the exceptions. + MarkAsHandled(calledFromFinalizer); + + // If we're only including the previously captured exceptions, + // return them immediately in an aggregate. + if (includeThisException == null) + return new AggregateException(exceptions); + + // Otherwise, the caller wants a specific exception to be included, + // so return an aggregate containing that exception and the rest. + Exception[] combinedExceptions = new Exception[exceptions.Count + 1]; + for (int i = 0; i < combinedExceptions.Length - 1; i++) + { + combinedExceptions[i] = exceptions[i].SourceException; + } + combinedExceptions[combinedExceptions.Length - 1] = includeThisException; + return new AggregateException(combinedExceptions); + } + + /// <summary> + /// Wraps the exception dispatch infos into a new read-only collection. By calling this method, + /// the holder assumes exceptions to have been "observed", such that the finalization + /// check will be subsequently skipped. + /// </summary> + internal ReadOnlyCollection<ExceptionDispatchInfo> GetExceptionDispatchInfos() + { + var exceptions = m_faultExceptions; + Contract.Assert(exceptions != null, "Expected an initialized list."); + Contract.Assert(exceptions.Count > 0, "Expected at least one exception."); + MarkAsHandled(false); + return new ReadOnlyCollection<ExceptionDispatchInfo>(exceptions); + } + + /// <summary> + /// Gets the ExceptionDispatchInfo representing the singular exception + /// that was the cause of the task's cancellation. + /// </summary> + /// <returns> + /// The ExceptionDispatchInfo for the cancellation exception. May be null. + /// </returns> + internal ExceptionDispatchInfo GetCancellationExceptionDispatchInfo() + { + var edi = m_cancellationException; + Contract.Assert(edi == null || edi.SourceException is OperationCanceledException, + "Expected the EDI to be for an OperationCanceledException"); + return edi; + } + } +} diff --git a/src/mscorlib/src/System/Threading/Tasks/TaskFactory.cs b/src/mscorlib/src/System/Threading/Tasks/TaskFactory.cs new file mode 100644 index 0000000000..52b471628a --- /dev/null +++ b/src/mscorlib/src/System/Threading/Tasks/TaskFactory.cs @@ -0,0 +1,3206 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +// =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ +// +// +// +// There are a plethora of common patterns for which Tasks are created. TaskFactory encodes +// these patterns into helper methods. These helpers also pick up default configuration settings +// applicable to the entire factory and configurable through its constructors. +// +// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + +using System; +using System.Collections.Generic; +using System.Security; +using System.Security.Permissions; +using System.Runtime.CompilerServices; +using System.Threading; +using System.Diagnostics.Contracts; + +namespace System.Threading.Tasks +{ + /// <summary> + /// Provides support for creating and scheduling + /// <see cref="T:System.Threading.Tasks.Task">Tasks</see>. + /// </summary> + /// <remarks> + /// <para> + /// There are many common patterns for which tasks are relevant. The <see cref="TaskFactory"/> + /// class encodes some of these patterns into methods that pick up default settings, which are + /// configurable through its constructors. + /// </para> + /// <para> + /// A default instance of <see cref="TaskFactory"/> is available through the + /// <see cref="System.Threading.Tasks.Task.Factory">Task.Factory</see> property. + /// </para> + /// </remarks> + [HostProtection(Synchronization = true, ExternalThreading = true)] + public class TaskFactory + { + // member variables + private CancellationToken m_defaultCancellationToken; + private TaskScheduler m_defaultScheduler; + private TaskCreationOptions m_defaultCreationOptions; + private TaskContinuationOptions m_defaultContinuationOptions; + + + private TaskScheduler DefaultScheduler + { + get + { + if (m_defaultScheduler == null) return TaskScheduler.Current; + else return m_defaultScheduler; + } + } + + // sister method to above property -- avoids a TLS lookup + private TaskScheduler GetDefaultScheduler(Task currTask) + { + if (m_defaultScheduler != null) return m_defaultScheduler; + else if ((currTask != null) + && ((currTask.CreationOptions & TaskCreationOptions.HideScheduler) == 0) + ) + return currTask.ExecutingTaskScheduler; + else return TaskScheduler.Default; + } + + /* Constructors */ + + // ctor parameters provide defaults for the factory, which can be overridden by options provided to + // specific calls on the factory + + + /// <summary> + /// Initializes a <see cref="TaskFactory"/> instance with the default configuration. + /// </summary> + /// <remarks> + /// This constructor creates a <see cref="TaskFactory"/> instance with a default configuration. The + /// <see cref="TaskCreationOptions"/> property is initialized to + /// <see cref="System.Threading.Tasks.TaskCreationOptions.None">TaskCreationOptions.None</see>, the + /// <see cref="TaskContinuationOptions"/> property is initialized to <see + /// cref="System.Threading.Tasks.TaskContinuationOptions.None">TaskContinuationOptions.None</see>, + /// and the <see cref="System.Threading.Tasks.TaskScheduler">TaskScheduler</see> property is + /// initialized to the current scheduler (see <see + /// cref="System.Threading.Tasks.TaskScheduler.Current">TaskScheduler.Current</see>). + /// </remarks> + public TaskFactory() + : this(default(CancellationToken), TaskCreationOptions.None, TaskContinuationOptions.None, null) + { + } + + /// <summary> + /// Initializes a <see cref="TaskFactory"/> instance with the specified configuration. + /// </summary> + /// <param name="cancellationToken">The default <see cref="CancellationToken"/> that will be assigned + /// to tasks created by this <see cref="TaskFactory"/> unless another CancellationToken is explicitly specified + /// while calling the factory methods.</param> + /// <remarks> + /// This constructor creates a <see cref="TaskFactory"/> instance with a default configuration. The + /// <see cref="TaskCreationOptions"/> property is initialized to + /// <see cref="System.Threading.Tasks.TaskCreationOptions.None">TaskCreationOptions.None</see>, the + /// <see cref="TaskContinuationOptions"/> property is initialized to <see + /// cref="System.Threading.Tasks.TaskContinuationOptions.None">TaskContinuationOptions.None</see>, + /// and the <see cref="System.Threading.Tasks.TaskScheduler">TaskScheduler</see> property is + /// initialized to the current scheduler (see <see + /// cref="System.Threading.Tasks.TaskScheduler.Current">TaskScheduler.Current</see>). + /// </remarks> + public TaskFactory(CancellationToken cancellationToken) + : this(cancellationToken, TaskCreationOptions.None, TaskContinuationOptions.None, null) + { + } + + /// <summary> + /// Initializes a <see cref="TaskFactory"/> instance with the specified configuration. + /// </summary> + /// <param name="scheduler"> + /// The <see cref="System.Threading.Tasks.TaskScheduler"> + /// TaskScheduler</see> to use to schedule any tasks created with this TaskFactory. A null value + /// indicates that the current TaskScheduler should be used. + /// </param> + /// <remarks> + /// With this constructor, the + /// <see cref="TaskCreationOptions"/> property is initialized to + /// <see cref="System.Threading.Tasks.TaskCreationOptions.None">TaskCreationOptions.None</see>, the + /// <see cref="TaskContinuationOptions"/> property is initialized to <see + /// cref="System.Threading.Tasks.TaskContinuationOptions.None">TaskContinuationOptions.None</see>, + /// and the <see cref="System.Threading.Tasks.TaskScheduler">TaskScheduler</see> property is + /// initialized to <paramref name="scheduler"/>, unless it's null, in which case the property is + /// initialized to the current scheduler (see <see + /// cref="System.Threading.Tasks.TaskScheduler.Current">TaskScheduler.Current</see>). + /// </remarks> + public TaskFactory(TaskScheduler scheduler) // null means to use TaskScheduler.Current + : this(default(CancellationToken), TaskCreationOptions.None, TaskContinuationOptions.None, scheduler) + { + } + + /// <summary> + /// Initializes a <see cref="TaskFactory"/> instance with the specified configuration. + /// </summary> + /// <param name="creationOptions"> + /// The default <see cref="System.Threading.Tasks.TaskCreationOptions"> + /// TaskCreationOptions</see> to use when creating tasks with this TaskFactory. + /// </param> + /// <param name="continuationOptions"> + /// The default <see cref="System.Threading.Tasks.TaskContinuationOptions"> + /// TaskContinuationOptions</see> to use when creating continuation tasks with this TaskFactory. + /// </param> + /// <exception cref="T:System.ArgumentOutOfRangeException"> + /// The exception that is thrown when the + /// <paramref name="creationOptions"/> argument or the <paramref name="continuationOptions"/> + /// argument specifies an invalid value. + /// </exception> + /// <remarks> + /// With this constructor, the + /// <see cref="TaskCreationOptions"/> property is initialized to <paramref name="creationOptions"/>, + /// the + /// <see cref="TaskContinuationOptions"/> property is initialized to <paramref + /// name="continuationOptions"/>, and the <see + /// cref="System.Threading.Tasks.TaskScheduler">TaskScheduler</see> property is initialized to the + /// current scheduler (see <see + /// cref="System.Threading.Tasks.TaskScheduler.Current">TaskScheduler.Current</see>). + /// </remarks> + public TaskFactory(TaskCreationOptions creationOptions, TaskContinuationOptions continuationOptions) + : this(default(CancellationToken), creationOptions, continuationOptions, null) + { + } + + /// <summary> + /// Initializes a <see cref="TaskFactory"/> instance with the specified configuration. + /// </summary> + /// <param name="cancellationToken">The default <see cref="CancellationToken"/> that will be assigned + /// to tasks created by this <see cref="TaskFactory"/> unless another CancellationToken is explicitly specified + /// while calling the factory methods.</param> + /// <param name="creationOptions"> + /// The default <see cref="System.Threading.Tasks.TaskCreationOptions"> + /// TaskCreationOptions</see> to use when creating tasks with this TaskFactory. + /// </param> + /// <param name="continuationOptions"> + /// The default <see cref="System.Threading.Tasks.TaskContinuationOptions"> + /// TaskContinuationOptions</see> to use when creating continuation tasks with this TaskFactory. + /// </param> + /// <param name="scheduler"> + /// The default <see cref="System.Threading.Tasks.TaskScheduler"> + /// TaskScheduler</see> to use to schedule any Tasks created with this TaskFactory. A null value + /// indicates that TaskScheduler.Current should be used. + /// </param> + /// <exception cref="T:System.ArgumentOutOfRangeException"> + /// The exception that is thrown when the + /// <paramref name="creationOptions"/> argument or the <paramref name="continuationOptions"/> + /// argumentspecifies an invalid value. + /// </exception> + /// <remarks> + /// With this constructor, the + /// <see cref="TaskCreationOptions"/> property is initialized to <paramref name="creationOptions"/>, + /// the + /// <see cref="TaskContinuationOptions"/> property is initialized to <paramref + /// name="continuationOptions"/>, and the <see + /// cref="System.Threading.Tasks.TaskScheduler">TaskScheduler</see> property is initialized to + /// <paramref name="scheduler"/>, unless it's null, in which case the property is initialized to the + /// current scheduler (see <see + /// cref="System.Threading.Tasks.TaskScheduler.Current">TaskScheduler.Current</see>). + /// </remarks> + public TaskFactory(CancellationToken cancellationToken, TaskCreationOptions creationOptions, TaskContinuationOptions continuationOptions, TaskScheduler scheduler) + { + CheckMultiTaskContinuationOptions(continuationOptions); + CheckCreationOptions(creationOptions); + + m_defaultCancellationToken = cancellationToken; + m_defaultScheduler = scheduler; + m_defaultCreationOptions = creationOptions; + m_defaultContinuationOptions = continuationOptions; + } + + [ContractArgumentValidatorAttribute] + internal static void CheckCreationOptions(TaskCreationOptions creationOptions) + { + // Check for validity of options + if ((creationOptions & + ~(TaskCreationOptions.AttachedToParent | + TaskCreationOptions.DenyChildAttach | + TaskCreationOptions.HideScheduler | + TaskCreationOptions.LongRunning | + TaskCreationOptions.PreferFairness | + TaskCreationOptions.RunContinuationsAsynchronously)) != 0) + { + throw new ArgumentOutOfRangeException("creationOptions"); + } + Contract.EndContractBlock(); + } + + /* Properties */ + + /// <summary> + /// Gets the default <see cref="System.Threading.CancellationToken">CancellationToken</see> of this + /// TaskFactory. + /// </summary> + /// <remarks> + /// This property returns the default <see cref="CancellationToken"/> that will be assigned to all + /// tasks created by this factory unless another CancellationToken value is explicitly specified + /// during the call to the factory methods. + /// </remarks> + public CancellationToken CancellationToken { get { return m_defaultCancellationToken; } } + + /// <summary> + /// Gets the <see cref="System.Threading.Tasks.TaskScheduler">TaskScheduler</see> of this + /// TaskFactory. + /// </summary> + /// <remarks> + /// This property returns the default scheduler for this factory. It will be used to schedule all + /// tasks unless another scheduler is explicitly specified during calls to this factory's methods. + /// If null, <see cref="System.Threading.Tasks.TaskScheduler.Current">TaskScheduler.Current</see> + /// will be used. + /// </remarks> + public TaskScheduler Scheduler { get { return m_defaultScheduler; } } + + /// <summary> + /// Gets the <see cref="System.Threading.Tasks.TaskCreationOptions">TaskCreationOptions + /// </see> value of this TaskFactory. + /// </summary> + /// <remarks> + /// This property returns the default creation options for this factory. They will be used to create all + /// tasks unless other options are explicitly specified during calls to this factory's methods. + /// </remarks> + public TaskCreationOptions CreationOptions { get { return m_defaultCreationOptions; } } + + /// <summary> + /// Gets the <see cref="System.Threading.Tasks.TaskCreationOptions">TaskContinuationOptions + /// </see> value of this TaskFactory. + /// </summary> + /// <remarks> + /// This property returns the default continuation options for this factory. They will be used to create + /// all continuation tasks unless other options are explicitly specified during calls to this factory's methods. + /// </remarks> + public TaskContinuationOptions ContinuationOptions { get { return m_defaultContinuationOptions; } } + + // + // StartNew methods + // + + /// <summary> + /// Creates and starts a <see cref="T:System.Threading.Tasks.Task">Task</see>. + /// </summary> + /// <param name="action">The action delegate to execute asynchronously.</param> + /// <returns>The started <see cref="T:System.Threading.Tasks.Task">Task</see>.</returns> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the <paramref name="action"/> + /// argument is null.</exception> + /// <remarks> + /// Calling StartNew is functionally equivalent to creating a Task using one of its constructors + /// and then calling + /// <see cref="System.Threading.Tasks.Task.Start()">Start</see> to schedule it for execution. However, + /// unless creation and scheduling must be separated, StartNew is the recommended + /// approach for both simplicity and performance. + /// </remarks> + [MethodImplAttribute(MethodImplOptions.NoInlining)] // Methods containing StackCrawlMark local var have to be marked non-inlineable + public Task StartNew(Action action) + { + StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller; + Task currTask = Task.InternalCurrent; + return Task.InternalStartNew(currTask, action, null, m_defaultCancellationToken, GetDefaultScheduler(currTask), + m_defaultCreationOptions, InternalTaskOptions.None, ref stackMark); + } + + /// <summary> + /// Creates and starts a <see cref="T:System.Threading.Tasks.Task">Task</see>. + /// </summary> + /// <param name="action">The action delegate to execute asynchronously.</param> + /// <param name="cancellationToken">The <see cref="CancellationToken"/> that will be assigned to the new task.</param> + /// <returns>The started <see cref="T:System.Threading.Tasks.Task">Task</see>.</returns> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the <paramref name="action"/> + /// argument is null.</exception> + /// <exception cref="T:System.ObjectDisposedException">The provided <see cref="System.Threading.CancellationToken">CancellationToken</see> + /// has already been disposed. + /// </exception> + /// <remarks> + /// Calling StartNew is functionally equivalent to creating a Task using one of its constructors + /// and then calling + /// <see cref="System.Threading.Tasks.Task.Start()">Start</see> to schedule it for execution. However, + /// unless creation and scheduling must be separated, StartNew is the recommended + /// approach for both simplicity and performance. + /// </remarks> + [MethodImplAttribute(MethodImplOptions.NoInlining)] // Methods containing StackCrawlMark local var have to be marked non-inlineable + public Task StartNew(Action action, CancellationToken cancellationToken) + { + StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller; + Task currTask = Task.InternalCurrent; + return Task.InternalStartNew(currTask, action, null, cancellationToken, GetDefaultScheduler(currTask), + m_defaultCreationOptions, InternalTaskOptions.None, ref stackMark); + } + + /// <summary> + /// Creates and starts a <see cref="T:System.Threading.Tasks.Task">Task</see>. + /// </summary> + /// <param name="action">The action delegate to execute asynchronously.</param> + /// <param name="creationOptions">A TaskCreationOptions value that controls the behavior of the + /// created + /// <see cref="T:System.Threading.Tasks.Task">Task.</see></param> + /// <returns>The started <see cref="T:System.Threading.Tasks.Task">Task</see>.</returns> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the <paramref + /// name="action"/> + /// argument is null.</exception> + /// <exception cref="T:System.ArgumentOutOfRangeException">The exception that is thrown when the + /// <paramref name="creationOptions"/> argument specifies an invalid TaskCreationOptions + /// value.</exception> + /// <remarks> + /// Calling StartNew is functionally equivalent to creating a Task using one of its constructors and + /// then calling + /// <see cref="System.Threading.Tasks.Task.Start()">Start</see> to schedule it for execution. + /// However, unless creation and scheduling must be separated, StartNew is the recommended approach + /// for both simplicity and performance. + /// </remarks> + [MethodImplAttribute(MethodImplOptions.NoInlining)] // Methods containing StackCrawlMark local var have to be marked non-inlineable + public Task StartNew(Action action, TaskCreationOptions creationOptions) + { + StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller; + Task currTask = Task.InternalCurrent; + return Task.InternalStartNew(currTask, action, null, m_defaultCancellationToken, GetDefaultScheduler(currTask), creationOptions, + InternalTaskOptions.None, ref stackMark); + } + + /// <summary> + /// Creates and starts a <see cref="T:System.Threading.Tasks.Task">Task</see>. + /// </summary> + /// <param name="action">The action delegate to execute asynchronously.</param> + /// <param name="cancellationToken">The <see cref="CancellationToken"/> that will be assigned to the new <see cref="Task"/></param> + /// <param name="creationOptions">A TaskCreationOptions value that controls the behavior of the + /// created + /// <see cref="T:System.Threading.Tasks.Task">Task.</see></param> + /// <param name="scheduler">The <see + /// cref="T:System.Threading.Tasks.TaskScheduler">TaskScheduler</see> + /// that is used to schedule the created <see + /// cref="T:System.Threading.Tasks.Task">Task</see>.</param> + /// <returns>The started <see cref="T:System.Threading.Tasks.Task">Task</see>.</returns> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the <paramref + /// name="action"/> + /// argument is null.</exception> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the <paramref + /// name="scheduler"/> + /// argument is null.</exception> + /// <exception cref="T:System.ArgumentOutOfRangeException">The exception that is thrown when the + /// <paramref name="creationOptions"/> argument specifies an invalid TaskCreationOptions + /// value.</exception> + /// <exception cref="T:System.ObjectDisposedException">The provided <see cref="System.Threading.CancellationToken">CancellationToken</see> + /// has already been disposed. + /// </exception> + /// <remarks> + /// Calling StartNew is functionally equivalent to creating a Task using one of its constructors and + /// then calling + /// <see cref="System.Threading.Tasks.Task.Start()">Start</see> to schedule it for execution. + /// However, unless creation and scheduling must be separated, StartNew is the recommended approach + /// for both simplicity and performance. + /// </remarks> + [MethodImplAttribute(MethodImplOptions.NoInlining)] // Methods containing StackCrawlMark local var have to be marked non-inlineable + public Task StartNew(Action action, CancellationToken cancellationToken, TaskCreationOptions creationOptions, TaskScheduler scheduler) + { + StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller; + return Task.InternalStartNew( + Task.InternalCurrentIfAttached(creationOptions), action, null, cancellationToken, scheduler, creationOptions, + InternalTaskOptions.None, ref stackMark); + } + + // Internal version includes InternalTaskOptions for Parallel.Invoke() support. + [MethodImplAttribute(MethodImplOptions.NoInlining)] // Methods containing StackCrawlMark local var have to be marked non-inlineable + internal Task StartNew(Action action, CancellationToken cancellationToken, TaskCreationOptions creationOptions, InternalTaskOptions internalOptions, TaskScheduler scheduler) + { + StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller; + return Task.InternalStartNew( + Task.InternalCurrentIfAttached(creationOptions), action, null, cancellationToken, scheduler, creationOptions, internalOptions, ref stackMark); + } + + + /// <summary> + /// Creates and starts a <see cref="T:System.Threading.Tasks.Task">Task</see>. + /// </summary> + /// <param name="action">The action delegate to execute asynchronously.</param> + /// <param name="state">An object containing data to be used by the <paramref name="action"/> + /// delegate.</param> + /// <returns>The started <see cref="T:System.Threading.Tasks.Task">Task</see>.</returns> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the <paramref + /// name="action"/> + /// argument is null.</exception> + /// <remarks> + /// Calling StartNew is functionally equivalent to creating a Task using one of its constructors and + /// then calling + /// <see cref="System.Threading.Tasks.Task.Start()">Start</see> to schedule it for execution. + /// However, unless creation and scheduling must be separated, StartNew is the recommended approach + /// for both simplicity and performance. + /// </remarks> + [MethodImplAttribute(MethodImplOptions.NoInlining)] // Methods containing StackCrawlMark local var have to be marked non-inlineable + public Task StartNew(Action<Object> action, Object state) + { + StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller; + Task currTask = Task.InternalCurrent; + return Task.InternalStartNew(currTask, action, state, m_defaultCancellationToken, GetDefaultScheduler(currTask), + m_defaultCreationOptions, InternalTaskOptions.None, ref stackMark); + } + + + /// <summary> + /// Creates and starts a <see cref="T:System.Threading.Tasks.Task">Task</see>. + /// </summary> + /// <param name="action">The action delegate to execute asynchronously.</param> + /// <param name="state">An object containing data to be used by the <paramref name="action"/> + /// delegate.</param> + /// <param name="cancellationToken">The <see cref="CancellationToken"/> that will be assigned to the new <see cref="Task"/></param> + /// <returns>The started <see cref="T:System.Threading.Tasks.Task">Task</see>.</returns> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the <paramref + /// name="action"/> + /// argument is null.</exception> + /// <exception cref="T:System.ObjectDisposedException">The provided <see cref="System.Threading.CancellationToken">CancellationToken</see> + /// has already been disposed. + /// </exception> + /// <remarks> + /// Calling StartNew is functionally equivalent to creating a Task using one of its constructors and + /// then calling + /// <see cref="System.Threading.Tasks.Task.Start()">Start</see> to schedule it for execution. + /// However, unless creation and scheduling must be separated, StartNew is the recommended approach + /// for both simplicity and performance. + /// </remarks> + [MethodImplAttribute(MethodImplOptions.NoInlining)] // Methods containing StackCrawlMark local var have to be marked non-inlineable + public Task StartNew(Action<Object> action, Object state, CancellationToken cancellationToken) + { + StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller; + Task currTask = Task.InternalCurrent; + return Task.InternalStartNew(currTask, action, state, cancellationToken, GetDefaultScheduler(currTask), + m_defaultCreationOptions, InternalTaskOptions.None, ref stackMark); + } + + /// <summary> + /// Creates and starts a <see cref="T:System.Threading.Tasks.Task">Task</see>. + /// </summary> + /// <param name="action">The action delegate to execute asynchronously.</param> + /// <param name="state">An object containing data to be used by the <paramref name="action"/> + /// delegate.</param> + /// <param name="creationOptions">A TaskCreationOptions value that controls the behavior of the + /// created + /// <see cref="T:System.Threading.Tasks.Task">Task.</see></param> + /// <returns>The started <see cref="T:System.Threading.Tasks.Task">Task</see>.</returns> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the <paramref + /// name="action"/> + /// argument is null.</exception> + /// <exception cref="T:System.ArgumentOutOfRangeException">The exception that is thrown when the + /// <paramref name="creationOptions"/> argument specifies an invalid TaskCreationOptions + /// value.</exception> + /// <remarks> + /// Calling StartNew is functionally equivalent to creating a Task using one of its constructors and + /// then calling + /// <see cref="System.Threading.Tasks.Task.Start()">Start</see> to schedule it for execution. + /// However, unless creation and scheduling must be separated, StartNew is the recommended approach + /// for both simplicity and performance. + /// </remarks> + [MethodImplAttribute(MethodImplOptions.NoInlining)] // Methods containing StackCrawlMark local var have to be marked non-inlineable + public Task StartNew(Action<Object> action, Object state, TaskCreationOptions creationOptions) + { + StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller; + Task currTask = Task.InternalCurrent; + return Task.InternalStartNew(currTask, action, state, m_defaultCancellationToken, GetDefaultScheduler(currTask), + creationOptions, InternalTaskOptions.None, ref stackMark); + } + + /// <summary> + /// Creates and starts a <see cref="T:System.Threading.Tasks.Task">Task</see>. + /// </summary> + /// <param name="action">The action delegate to execute asynchronously.</param> + /// <param name="state">An object containing data to be used by the <paramref name="action"/> + /// delegate.</param> + /// <param name="cancellationToken">The <see cref="CancellationToken"/> that will be assigned to the new task.</param> + /// <param name="creationOptions">A TaskCreationOptions value that controls the behavior of the + /// created + /// <see cref="T:System.Threading.Tasks.Task">Task.</see></param> + /// <param name="scheduler">The <see + /// cref="T:System.Threading.Tasks.TaskScheduler">TaskScheduler</see> + /// that is used to schedule the created <see + /// cref="T:System.Threading.Tasks.Task">Task</see>.</param> + /// <returns>The started <see cref="T:System.Threading.Tasks.Task">Task</see>.</returns> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the <paramref + /// name="action"/> + /// argument is null.</exception> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the <paramref + /// name="scheduler"/> + /// argument is null.</exception> + /// <exception cref="T:System.ArgumentOutOfRangeException">The exception that is thrown when the + /// <paramref name="creationOptions"/> argument specifies an invalid TaskCreationOptions + /// value.</exception> + /// <exception cref="T:System.ObjectDisposedException">The provided <see cref="System.Threading.CancellationToken">CancellationToken</see> + /// has already been disposed. + /// </exception> + /// <remarks> + /// Calling StartNew is functionally equivalent to creating a Task using one of its constructors and + /// then calling + /// <see cref="System.Threading.Tasks.Task.Start()">Start</see> to schedule it for execution. + /// However, unless creation and scheduling must be separated, StartNew is the recommended approach + /// for both simplicity and performance. + /// </remarks> + [MethodImplAttribute(MethodImplOptions.NoInlining)] // Methods containing StackCrawlMark local var have to be marked non-inlineable + public Task StartNew(Action<Object> action, Object state, CancellationToken cancellationToken, + TaskCreationOptions creationOptions, TaskScheduler scheduler) + { + StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller; + return Task.InternalStartNew( + Task.InternalCurrentIfAttached(creationOptions), action, state, cancellationToken, scheduler, + creationOptions, InternalTaskOptions.None, ref stackMark); + } + + /// <summary> + /// Creates and starts a <see cref="T:System.Threading.Tasks.Task{TResult}"/>. + /// </summary> + /// <typeparam name="TResult">The type of the result available through the + /// <see cref="T:System.Threading.Tasks.Task{TResult}">Task</see>. + /// </typeparam> + /// <param name="function">A function delegate that returns the future result to be available through + /// the <see cref="T:System.Threading.Tasks.Task{TResult}"/>.</param> + /// <returns>The started <see cref="T:System.Threading.Tasks.Task{TResult}"/>.</returns> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the <paramref + /// name="function"/> + /// argument is null.</exception> + /// <remarks> + /// Calling StartNew is functionally equivalent to creating a <see cref="Task{TResult}"/> using one + /// of its constructors and then calling + /// <see cref="System.Threading.Tasks.Task.Start()">Start</see> to schedule it for execution. + /// However, unless creation and scheduling must be separated, StartNew is the recommended approach + /// for both simplicity and performance. + /// </remarks> + [MethodImplAttribute(MethodImplOptions.NoInlining)] // Methods containing StackCrawlMark local var have to be marked non-inlineable + public Task<TResult> StartNew<TResult>(Func<TResult> function) + { + StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller; + Task currTask = Task.InternalCurrent; + return Task<TResult>.StartNew(currTask, function, m_defaultCancellationToken, + m_defaultCreationOptions, InternalTaskOptions.None, GetDefaultScheduler(currTask), ref stackMark); + } + + + /// <summary> + /// Creates and starts a <see cref="T:System.Threading.Tasks.Task{TResult}"/>. + /// </summary> + /// <typeparam name="TResult">The type of the result available through the + /// <see cref="T:System.Threading.Tasks.Task{TResult}">Task</see>. + /// </typeparam> + /// <param name="function">A function delegate that returns the future result to be available through + /// the <see cref="T:System.Threading.Tasks.Task{TResult}"/>.</param> + /// <param name="cancellationToken">The <see cref="CancellationToken"/> that will be assigned to the new <see cref="Task"/></param> + /// <returns>The started <see cref="T:System.Threading.Tasks.Task{TResult}"/>.</returns> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the <paramref + /// name="function"/> + /// argument is null.</exception> + /// <exception cref="T:System.ObjectDisposedException">The provided <see cref="System.Threading.CancellationToken">CancellationToken</see> + /// has already been disposed. + /// </exception> + /// <remarks> + /// Calling StartNew is functionally equivalent to creating a <see cref="Task{TResult}"/> using one + /// of its constructors and then calling + /// <see cref="System.Threading.Tasks.Task.Start()">Start</see> to schedule it for execution. + /// However, unless creation and scheduling must be separated, StartNew is the recommended approach + /// for both simplicity and performance. + /// </remarks> + [MethodImplAttribute(MethodImplOptions.NoInlining)] // Methods containing StackCrawlMark local var have to be marked non-inlineable + public Task<TResult> StartNew<TResult>(Func<TResult> function, CancellationToken cancellationToken) + { + StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller; + Task currTask = Task.InternalCurrent; + return Task<TResult>.StartNew(currTask, function, cancellationToken, + m_defaultCreationOptions, InternalTaskOptions.None, GetDefaultScheduler(currTask), ref stackMark); + } + + /// <summary> + /// Creates and starts a <see cref="T:System.Threading.Tasks.Task{TResult}"/>. + /// </summary> + /// <typeparam name="TResult">The type of the result available through the + /// <see cref="T:System.Threading.Tasks.Task{TResult}">Task</see>. + /// </typeparam> + /// <param name="function">A function delegate that returns the future result to be available through + /// the <see cref="T:System.Threading.Tasks.Task{TResult}"/>.</param> + /// <param name="creationOptions">A TaskCreationOptions value that controls the behavior of the + /// created + /// <see cref="T:System.Threading.Tasks.Task{TResult}"/>.</param> + /// <returns>The started <see cref="T:System.Threading.Tasks.Task{TResult}"/>.</returns> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the <paramref + /// name="function"/> + /// argument is null.</exception> + /// <exception cref="T:System.ArgumentOutOfRangeException">The exception that is thrown when the + /// <paramref name="creationOptions"/> argument specifies an invalid TaskCreationOptions + /// value.</exception> + /// <remarks> + /// Calling StartNew is functionally equivalent to creating a <see cref="Task{TResult}"/> using one + /// of its constructors and then calling + /// <see cref="System.Threading.Tasks.Task.Start()">Start</see> to schedule it for execution. + /// However, unless creation and scheduling must be separated, StartNew is the recommended approach + /// for both simplicity and performance. + /// </remarks> + [MethodImplAttribute(MethodImplOptions.NoInlining)] // Methods containing StackCrawlMark local var have to be marked non-inlineable + public Task<TResult> StartNew<TResult>(Func<TResult> function, TaskCreationOptions creationOptions) + { + StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller; + Task currTask = Task.InternalCurrent; + return Task<TResult>.StartNew(currTask, function, m_defaultCancellationToken, + creationOptions, InternalTaskOptions.None, GetDefaultScheduler(currTask), ref stackMark); + } + + /// <summary> + /// Creates and starts a <see cref="T:System.Threading.Tasks.Task{TResult}"/>. + /// </summary> + /// <typeparam name="TResult">The type of the result available through the + /// <see cref="T:System.Threading.Tasks.Task{TResult}">Task</see>. + /// </typeparam> + /// <param name="function">A function delegate that returns the future result to be available through + /// the <see cref="T:System.Threading.Tasks.Task{TResult}"/>.</param> + /// <param name="cancellationToken">The <see cref="CancellationToken"/> that will be assigned to the new task.</param> + /// <param name="creationOptions">A TaskCreationOptions value that controls the behavior of the + /// created + /// <see cref="T:System.Threading.Tasks.Task{TResult}"/>.</param> + /// <param name="scheduler">The <see + /// cref="T:System.Threading.Tasks.TaskScheduler">TaskScheduler</see> + /// that is used to schedule the created <see cref="T:System.Threading.Tasks.Task{TResult}"> + /// Task{TResult}</see>.</param> + /// <returns>The started <see cref="T:System.Threading.Tasks.Task{TResult}"/>.</returns> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the <paramref + /// name="function"/> + /// argument is null.</exception> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the <paramref + /// name="scheduler"/> + /// argument is null.</exception> + /// <exception cref="T:System.ArgumentOutOfRangeException">The exception that is thrown when the + /// <paramref name="creationOptions"/> argument specifies an invalid TaskCreationOptions + /// value.</exception> + /// <exception cref="T:System.ObjectDisposedException">The provided <see cref="System.Threading.CancellationToken">CancellationToken</see> + /// has already been disposed. + /// </exception> + /// <remarks> + /// Calling StartNew is functionally equivalent to creating a <see cref="Task{TResult}"/> using one + /// of its constructors and then calling + /// <see cref="System.Threading.Tasks.Task.Start()">Start</see> to schedule it for execution. + /// However, unless creation and scheduling must be separated, StartNew is the recommended approach + /// for both simplicity and performance. + /// </remarks> + [MethodImplAttribute(MethodImplOptions.NoInlining)] // Methods containing StackCrawlMark local var have to be marked non-inlineable + public Task<TResult> StartNew<TResult>(Func<TResult> function, CancellationToken cancellationToken, TaskCreationOptions creationOptions, TaskScheduler scheduler) + { + StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller; + return Task<TResult>.StartNew( + Task.InternalCurrentIfAttached(creationOptions), function, cancellationToken, + creationOptions, InternalTaskOptions.None, scheduler, ref stackMark); + } + + /// <summary> + /// Creates and starts a <see cref="T:System.Threading.Tasks.Task{TResult}"/>. + /// </summary> + /// <typeparam name="TResult">The type of the result available through the + /// <see cref="T:System.Threading.Tasks.Task{TResult}">Task</see>. + /// </typeparam> + /// <param name="function">A function delegate that returns the future result to be available through + /// the <see cref="T:System.Threading.Tasks.Task{TResult}"/>.</param> + /// <param name="state">An object containing data to be used by the <paramref name="function"/> + /// delegate.</param> + /// <returns>The started <see cref="T:System.Threading.Tasks.Task{TResult}"/>.</returns> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the <paramref + /// name="function"/> + /// argument is null.</exception> + /// <remarks> + /// Calling StartNew is functionally equivalent to creating a <see cref="Task{TResult}"/> using one + /// of its constructors and then calling + /// <see cref="System.Threading.Tasks.Task.Start()">Start</see> to schedule it for execution. + /// However, unless creation and scheduling must be separated, StartNew is the recommended approach + /// for both simplicity and performance. + /// </remarks> + [MethodImplAttribute(MethodImplOptions.NoInlining)] // Methods containing StackCrawlMark local var have to be marked non-inlineable + public Task<TResult> StartNew<TResult>(Func<Object, TResult> function, Object state) + { + StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller; + Task currTask = Task.InternalCurrent; + return Task<TResult>.StartNew(currTask, function, state, m_defaultCancellationToken, + m_defaultCreationOptions, InternalTaskOptions.None, GetDefaultScheduler(currTask), ref stackMark); + } + + + /// <summary> + /// Creates and starts a <see cref="T:System.Threading.Tasks.Task{TResult}"/>. + /// </summary> + /// <typeparam name="TResult">The type of the result available through the + /// <see cref="T:System.Threading.Tasks.Task{TResult}">Task</see>. + /// </typeparam> + /// <param name="function">A function delegate that returns the future result to be available through + /// the <see cref="T:System.Threading.Tasks.Task{TResult}"/>.</param> + /// <param name="state">An object containing data to be used by the <paramref name="function"/> + /// delegate.</param> + /// <param name="cancellationToken">The <see cref="CancellationToken"/> that will be assigned to the new <see cref="Task"/></param> + /// <returns>The started <see cref="T:System.Threading.Tasks.Task{TResult}"/>.</returns> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the <paramref + /// name="function"/> + /// argument is null.</exception> + /// <exception cref="T:System.ObjectDisposedException">The provided <see cref="System.Threading.CancellationToken">CancellationToken</see> + /// has already been disposed. + /// </exception> + /// <remarks> + /// Calling StartNew is functionally equivalent to creating a <see cref="Task{TResult}"/> using one + /// of its constructors and then calling + /// <see cref="System.Threading.Tasks.Task.Start()">Start</see> to schedule it for execution. + /// However, unless creation and scheduling must be separated, StartNew is the recommended approach + /// for both simplicity and performance. + /// </remarks> + [MethodImplAttribute(MethodImplOptions.NoInlining)] // Methods containing StackCrawlMark local var have to be marked non-inlineable + public Task<TResult> StartNew<TResult>(Func<Object, TResult> function, Object state, CancellationToken cancellationToken) + { + StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller; + Task currTask = Task.InternalCurrent; + return Task<TResult>.StartNew(currTask, function, state, cancellationToken, + m_defaultCreationOptions, InternalTaskOptions.None, GetDefaultScheduler(currTask), ref stackMark); + } + + /// <summary> + /// Creates and starts a <see cref="T:System.Threading.Tasks.Task{TResult}"/>. + /// </summary> + /// <typeparam name="TResult">The type of the result available through the + /// <see cref="T:System.Threading.Tasks.Task{TResult}">Task</see>. + /// </typeparam> + /// <param name="function">A function delegate that returns the future result to be available through + /// the <see cref="T:System.Threading.Tasks.Task{TResult}"/>.</param> + /// <param name="state">An object containing data to be used by the <paramref name="function"/> + /// delegate.</param> + /// <param name="creationOptions">A TaskCreationOptions value that controls the behavior of the + /// created + /// <see cref="T:System.Threading.Tasks.Task{TResult}"/>.</param> + /// <returns>The started <see cref="T:System.Threading.Tasks.Task{TResult}"/>.</returns> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the <paramref + /// name="function"/> + /// argument is null.</exception> + /// <exception cref="T:System.ArgumentOutOfRangeException">The exception that is thrown when the + /// <paramref name="creationOptions"/> argument specifies an invalid TaskCreationOptions + /// value.</exception> + /// <remarks> + /// Calling StartNew is functionally equivalent to creating a <see cref="Task{TResult}"/> using one + /// of its constructors and then calling + /// <see cref="System.Threading.Tasks.Task.Start()">Start</see> to schedule it for execution. + /// However, unless creation and scheduling must be separated, StartNew is the recommended approach + /// for both simplicity and performance. + /// </remarks> + [MethodImplAttribute(MethodImplOptions.NoInlining)] // Methods containing StackCrawlMark local var have to be marked non-inlineable + public Task<TResult> StartNew<TResult>(Func<Object, TResult> function, Object state, TaskCreationOptions creationOptions) + { + StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller; + Task currTask = Task.InternalCurrent; + return Task<TResult>.StartNew(currTask, function, state, m_defaultCancellationToken, + creationOptions, InternalTaskOptions.None, GetDefaultScheduler(currTask), ref stackMark); + } + + /// <summary> + /// Creates and starts a <see cref="T:System.Threading.Tasks.Task{TResult}"/>. + /// </summary> + /// <typeparam name="TResult">The type of the result available through the + /// <see cref="T:System.Threading.Tasks.Task{TResult}">Task</see>. + /// </typeparam> + /// <param name="function">A function delegate that returns the future result to be available through + /// the <see cref="T:System.Threading.Tasks.Task{TResult}"/>.</param> + /// <param name="state">An object containing data to be used by the <paramref name="function"/> + /// delegate.</param> + /// <param name="cancellationToken">The <see cref="CancellationToken"/> that will be assigned to the new task.</param> + /// <param name="creationOptions">A TaskCreationOptions value that controls the behavior of the + /// created + /// <see cref="T:System.Threading.Tasks.Task{TResult}"/>.</param> + /// <param name="scheduler">The <see + /// cref="T:System.Threading.Tasks.TaskScheduler">TaskScheduler</see> + /// that is used to schedule the created <see cref="T:System.Threading.Tasks.Task{TResult}"> + /// Task{TResult}</see>.</param> + /// <returns>The started <see cref="T:System.Threading.Tasks.Task{TResult}"/>.</returns> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the <paramref + /// name="function"/> + /// argument is null.</exception> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the <paramref + /// name="scheduler"/> + /// argument is null.</exception> + /// <exception cref="T:System.ArgumentOutOfRangeException">The exception that is thrown when the + /// <paramref name="creationOptions"/> argument specifies an invalid TaskCreationOptions + /// value.</exception> + /// <exception cref="T:System.ObjectDisposedException">The provided <see cref="System.Threading.CancellationToken">CancellationToken</see> + /// has already been disposed. + /// </exception> + /// <remarks> + /// Calling StartNew is functionally equivalent to creating a <see cref="Task{TResult}"/> using one + /// of its constructors and then calling + /// <see cref="System.Threading.Tasks.Task.Start()">Start</see> to schedule it for execution. + /// However, unless creation and scheduling must be separated, StartNew is the recommended approach + /// for both simplicity and performance. + /// </remarks> + [MethodImplAttribute(MethodImplOptions.NoInlining)] // Methods containing StackCrawlMark local var have to be marked non-inlineable + public Task<TResult> StartNew<TResult>(Func<Object, TResult> function, Object state, CancellationToken cancellationToken, + TaskCreationOptions creationOptions, TaskScheduler scheduler) + { + StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller; + return Task<TResult>.StartNew( + Task.InternalCurrentIfAttached(creationOptions), function, state, cancellationToken, + creationOptions, InternalTaskOptions.None, scheduler, ref stackMark); + } + + // + // FromAsync methods + // + + /// <summary> + /// Creates a <see cref="T:System.Threading.Tasks.Task">Task</see> that executes an end method action + /// when a specified <see cref="T:System.IAsyncResult">IAsyncResult</see> completes. + /// </summary> + /// <param name="asyncResult">The IAsyncResult whose completion should trigger the processing of the + /// <paramref name="endMethod"/>.</param> + /// <param name="endMethod">The action delegate that processes the completed <paramref + /// name="asyncResult"/>.</param> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the + /// <paramref name="asyncResult"/> argument is null.</exception> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the + /// <paramref name="endMethod"/> argument is null.</exception> + /// <returns>A <see cref="T:System.Threading.Tasks.Task">Task</see> that represents the asynchronous + /// operation.</returns> + [MethodImplAttribute(MethodImplOptions.NoInlining)] // Methods containing StackCrawlMark local var have to be marked non-inlineable + public Task FromAsync( + IAsyncResult asyncResult, + Action<IAsyncResult> endMethod) + { + StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller; + return FromAsync(asyncResult, endMethod, m_defaultCreationOptions, DefaultScheduler, ref stackMark); + } + + /// <summary> + /// Creates a <see cref="T:System.Threading.Tasks.Task">Task</see> that executes an end method action + /// when a specified <see cref="T:System.IAsyncResult">IAsyncResult</see> completes. + /// </summary> + /// <param name="asyncResult">The IAsyncResult whose completion should trigger the processing of the + /// <paramref name="endMethod"/>.</param> + /// <param name="endMethod">The action delegate that processes the completed <paramref + /// name="asyncResult"/>.</param> + /// <param name="creationOptions">The TaskCreationOptions value that controls the behavior of the + /// created <see cref="T:System.Threading.Tasks.Task">Task</see>.</param> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the + /// <paramref name="asyncResult"/> argument is null.</exception> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the + /// <paramref name="endMethod"/> argument is null.</exception> + /// <exception cref="T:System.ArgumentOutOfRangeException">The exception that is thrown when the + /// <paramref name="creationOptions"/> argument specifies an invalid TaskCreationOptions + /// value.</exception> + /// <returns>A <see cref="T:System.Threading.Tasks.Task">Task</see> that represents the asynchronous + /// operation.</returns> + [MethodImplAttribute(MethodImplOptions.NoInlining)] // Methods containing StackCrawlMark local var have to be marked non-inlineable + public Task FromAsync( + IAsyncResult asyncResult, + Action<IAsyncResult> endMethod, + TaskCreationOptions creationOptions) + { + StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller; + return FromAsync(asyncResult, endMethod, creationOptions, DefaultScheduler, ref stackMark); + } + + /// <summary> + /// Creates a <see cref="T:System.Threading.Tasks.Task">Task</see> that executes an end method action + /// when a specified <see cref="T:System.IAsyncResult">IAsyncResult</see> completes. + /// </summary> + /// <param name="asyncResult">The IAsyncResult whose completion should trigger the processing of the + /// <paramref name="endMethod"/>.</param> + /// <param name="endMethod">The action delegate that processes the completed <paramref + /// name="asyncResult"/>.</param> + /// <param name="scheduler">The <see cref="System.Threading.Tasks.TaskScheduler">TaskScheduler</see> + /// that is used to schedule the task that executes the end method.</param> + /// <param name="creationOptions">The TaskCreationOptions value that controls the behavior of the + /// created <see cref="T:System.Threading.Tasks.Task">Task</see>.</param> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the + /// <paramref name="asyncResult"/> argument is null.</exception> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the + /// <paramref name="endMethod"/> argument is null.</exception> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the + /// <paramref name="scheduler"/> argument is null.</exception> + /// <exception cref="T:System.ArgumentOutOfRangeException">The exception that is thrown when the + /// <paramref name="creationOptions"/> argument specifies an invalid TaskCreationOptions + /// value.</exception> + /// <returns>A <see cref="T:System.Threading.Tasks.Task">Task</see> that represents the asynchronous + /// operation.</returns> + [MethodImplAttribute(MethodImplOptions.NoInlining)] // Methods containing StackCrawlMark local var have to be marked non-inlineable + public Task FromAsync( + IAsyncResult asyncResult, + Action<IAsyncResult> endMethod, + TaskCreationOptions creationOptions, + TaskScheduler scheduler) + { + StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller; + return FromAsync(asyncResult, endMethod, creationOptions, scheduler, ref stackMark); + } + + // private version that supports StackCrawlMark. + private Task FromAsync( + IAsyncResult asyncResult, + Action<IAsyncResult> endMethod, + TaskCreationOptions creationOptions, + TaskScheduler scheduler, + ref StackCrawlMark stackMark) + { + return TaskFactory<VoidTaskResult>.FromAsyncImpl(asyncResult, null, endMethod, creationOptions, scheduler, ref stackMark); + } + + /// <summary> + /// Creates a <see cref="T:System.Threading.Tasks.Task">Task</see> that represents a pair of begin + /// and end methods that conform to the Asynchronous Programming Model pattern. + /// </summary> + /// <param name="beginMethod">The delegate that begins the asynchronous operation.</param> + /// <param name="endMethod">The delegate that ends the asynchronous operation.</param> + /// <param name="state">An object containing data to be used by the <paramref name="beginMethod"/> + /// delegate.</param> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the + /// <paramref name="beginMethod"/> argument is null.</exception> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the + /// <paramref name="endMethod"/> argument is null.</exception> + /// <returns>The created <see cref="T:System.Threading.Tasks.Task">Task</see> that represents the + /// asynchronous operation.</returns> + /// <remarks> + /// This method throws any exceptions thrown by the <paramref name="beginMethod"/>. + /// </remarks> + public Task FromAsync( + Func<AsyncCallback, object, IAsyncResult> beginMethod, + Action<IAsyncResult> endMethod, + object state) + { + return FromAsync(beginMethod, endMethod, state, m_defaultCreationOptions); + } + + /// <summary> + /// Creates a <see cref="T:System.Threading.Tasks.Task">Task</see> that represents a pair of begin + /// and end methods that conform to the Asynchronous Programming Model pattern. + /// </summary> + /// <param name="beginMethod">The delegate that begins the asynchronous operation.</param> + /// <param name="endMethod">The delegate that ends the asynchronous operation.</param> + /// <param name="creationOptions">The TaskCreationOptions value that controls the behavior of the + /// created <see cref="T:System.Threading.Tasks.Task">Task</see>.</param> + /// <param name="state">An object containing data to be used by the <paramref name="beginMethod"/> + /// delegate.</param> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the + /// <paramref name="beginMethod"/> argument is null.</exception> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the + /// <paramref name="endMethod"/> argument is null.</exception> + /// <exception cref="T:System.ArgumentOutOfRangeException">The exception that is thrown when the + /// <paramref name="creationOptions"/> argument specifies an invalid TaskCreationOptions + /// value.</exception> + /// <returns>The created <see cref="T:System.Threading.Tasks.Task">Task</see> that represents the + /// asynchronous operation.</returns> + /// <remarks> + /// This method throws any exceptions thrown by the <paramref name="beginMethod"/>. + /// </remarks> + public Task FromAsync( + Func<AsyncCallback, object, IAsyncResult> beginMethod, + Action<IAsyncResult> endMethod, object state, TaskCreationOptions creationOptions) + { + return TaskFactory<VoidTaskResult>.FromAsyncImpl(beginMethod, null, endMethod, state, creationOptions); + } + + /// <summary> + /// Creates a <see cref="T:System.Threading.Tasks.Task">Task</see> that represents a pair of begin + /// and end methods that conform to the Asynchronous Programming Model pattern. + /// </summary> + /// <typeparam name="TArg1">The type of the first argument passed to the <paramref + /// name="beginMethod"/> + /// delegate.</typeparam> + /// <param name="beginMethod">The delegate that begins the asynchronous operation.</param> + /// <param name="endMethod">The delegate that ends the asynchronous operation.</param> + /// <param name="arg1">The first argument passed to the <paramref name="beginMethod"/> + /// delegate.</param> + /// <param name="state">An object containing data to be used by the <paramref name="beginMethod"/> + /// delegate.</param> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the + /// <paramref name="beginMethod"/> argument is null.</exception> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the + /// <paramref name="endMethod"/> argument is null.</exception> + /// <returns>The created <see cref="T:System.Threading.Tasks.Task">Task</see> that represents the + /// asynchronous operation.</returns> + /// <remarks> + /// This method throws any exceptions thrown by the <paramref name="beginMethod"/>. + /// </remarks> + public Task FromAsync<TArg1>( + Func<TArg1, AsyncCallback, object, IAsyncResult> beginMethod, + Action<IAsyncResult> endMethod, + TArg1 arg1, + object state) + { + return FromAsync(beginMethod, endMethod, arg1, state, m_defaultCreationOptions); + } + + /// <summary> + /// Creates a <see cref="T:System.Threading.Tasks.Task">Task</see> that represents a pair of begin + /// and end methods that conform to the Asynchronous Programming Model pattern. + /// </summary> + /// <typeparam name="TArg1">The type of the first argument passed to the <paramref + /// name="beginMethod"/> + /// delegate.</typeparam> + /// <param name="beginMethod">The delegate that begins the asynchronous operation.</param> + /// <param name="endMethod">The delegate that ends the asynchronous operation.</param> + /// <param name="arg1">The first argument passed to the <paramref name="beginMethod"/> + /// delegate.</param> + /// <param name="creationOptions">The TaskCreationOptions value that controls the behavior of the + /// created <see cref="T:System.Threading.Tasks.Task">Task</see>.</param> + /// <param name="state">An object containing data to be used by the <paramref name="beginMethod"/> + /// delegate.</param> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the + /// <paramref name="beginMethod"/> argument is null.</exception> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the + /// <paramref name="endMethod"/> argument is null.</exception> + /// <exception cref="T:System.ArgumentOutOfRangeException">The exception that is thrown when the + /// <paramref name="creationOptions"/> argument specifies an invalid TaskCreationOptions + /// value.</exception> + /// <returns>The created <see cref="T:System.Threading.Tasks.Task">Task</see> that represents the + /// asynchronous operation.</returns> + /// <remarks> + /// This method throws any exceptions thrown by the <paramref name="beginMethod"/>. + /// </remarks> + public Task FromAsync<TArg1>( + Func<TArg1, AsyncCallback, object, IAsyncResult> beginMethod, + Action<IAsyncResult> endMethod, + TArg1 arg1, object state, TaskCreationOptions creationOptions) + { + return TaskFactory<VoidTaskResult>.FromAsyncImpl(beginMethod, null, endMethod, arg1, state, creationOptions); + } + + /// <summary> + /// Creates a <see cref="T:System.Threading.Tasks.Task">Task</see> that represents a pair of begin + /// and end methods that conform to the Asynchronous Programming Model pattern. + /// </summary> + /// <typeparam name="TArg1">The type of the first argument passed to the <paramref + /// name="beginMethod"/> + /// delegate.</typeparam> + /// <typeparam name="TArg2">The type of the second argument passed to <paramref name="beginMethod"/> + /// delegate.</typeparam> + /// <param name="beginMethod">The delegate that begins the asynchronous operation.</param> + /// <param name="endMethod">The delegate that ends the asynchronous operation.</param> + /// <param name="arg1">The first argument passed to the <paramref name="beginMethod"/> + /// delegate.</param> + /// <param name="arg2">The second argument passed to the <paramref name="beginMethod"/> + /// delegate.</param> + /// <param name="state">An object containing data to be used by the <paramref name="beginMethod"/> + /// delegate.</param> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the + /// <paramref name="beginMethod"/> argument is null.</exception> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the + /// <paramref name="endMethod"/> argument is null.</exception> + /// <returns>The created <see cref="T:System.Threading.Tasks.Task">Task</see> that represents the + /// asynchronous operation.</returns> + /// <remarks> + /// This method throws any exceptions thrown by the <paramref name="beginMethod"/>. + /// </remarks> + public Task FromAsync<TArg1, TArg2>( + Func<TArg1, TArg2, AsyncCallback, object, IAsyncResult> beginMethod, + Action<IAsyncResult> endMethod, + TArg1 arg1, TArg2 arg2, object state) + { + return FromAsync(beginMethod, endMethod, arg1, arg2, state, m_defaultCreationOptions); + } + + /// <summary> + /// Creates a <see cref="T:System.Threading.Tasks.Task">Task</see> that represents a pair of begin + /// and end methods that conform to the Asynchronous Programming Model pattern. + /// </summary> + /// <typeparam name="TArg1">The type of the first argument passed to the <paramref + /// name="beginMethod"/> + /// delegate.</typeparam> + /// <typeparam name="TArg2">The type of the second argument passed to <paramref name="beginMethod"/> + /// delegate.</typeparam> + /// <param name="beginMethod">The delegate that begins the asynchronous operation.</param> + /// <param name="endMethod">The delegate that ends the asynchronous operation.</param> + /// <param name="arg1">The first argument passed to the <paramref name="beginMethod"/> + /// delegate.</param> + /// <param name="arg2">The second argument passed to the <paramref name="beginMethod"/> + /// delegate.</param> + /// <param name="creationOptions">The TaskCreationOptions value that controls the behavior of the + /// created <see cref="T:System.Threading.Tasks.Task">Task</see>.</param> + /// <param name="state">An object containing data to be used by the <paramref name="beginMethod"/> + /// delegate.</param> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the + /// <paramref name="beginMethod"/> argument is null.</exception> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the + /// <paramref name="endMethod"/> argument is null.</exception> + /// <exception cref="T:System.ArgumentOutOfRangeException">The exception that is thrown when the + /// <paramref name="creationOptions"/> argument specifies an invalid TaskCreationOptions + /// value.</exception> + /// <returns>The created <see cref="T:System.Threading.Tasks.Task">Task</see> that represents the + /// asynchronous operation.</returns> + /// <remarks> + /// This method throws any exceptions thrown by the <paramref name="beginMethod"/>. + /// </remarks> + public Task FromAsync<TArg1, TArg2>( + Func<TArg1, TArg2, AsyncCallback, object, IAsyncResult> beginMethod, + Action<IAsyncResult> endMethod, + TArg1 arg1, TArg2 arg2, object state, TaskCreationOptions creationOptions) + { + return TaskFactory<VoidTaskResult>.FromAsyncImpl(beginMethod, null, endMethod, arg1, arg2, state, creationOptions); + } + + /// <summary> + /// Creates a <see cref="T:System.Threading.Tasks.Task">Task</see> that represents a pair of begin + /// and end methods that conform to the Asynchronous Programming Model pattern. + /// </summary> + /// <typeparam name="TArg1">The type of the first argument passed to the <paramref + /// name="beginMethod"/> + /// delegate.</typeparam> + /// <typeparam name="TArg2">The type of the second argument passed to <paramref name="beginMethod"/> + /// delegate.</typeparam> + /// <typeparam name="TArg3">The type of the third argument passed to <paramref name="beginMethod"/> + /// delegate.</typeparam> + /// <param name="beginMethod">The delegate that begins the asynchronous operation.</param> + /// <param name="endMethod">The delegate that ends the asynchronous operation.</param> + /// <param name="arg1">The first argument passed to the <paramref name="beginMethod"/> + /// delegate.</param> + /// <param name="arg2">The second argument passed to the <paramref name="beginMethod"/> + /// delegate.</param> + /// <param name="arg3">The third argument passed to the <paramref name="beginMethod"/> + /// delegate.</param> + /// <param name="state">An object containing data to be used by the <paramref name="beginMethod"/> + /// delegate.</param> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the + /// <paramref name="beginMethod"/> argument is null.</exception> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the + /// <paramref name="endMethod"/> argument is null.</exception> + /// <returns>The created <see cref="T:System.Threading.Tasks.Task">Task</see> that represents the + /// asynchronous operation.</returns> + /// <remarks> + /// This method throws any exceptions thrown by the <paramref name="beginMethod"/>. + /// </remarks> + public Task FromAsync<TArg1, TArg2, TArg3>( + Func<TArg1, TArg2, TArg3, AsyncCallback, object, IAsyncResult> beginMethod, + Action<IAsyncResult> endMethod, + TArg1 arg1, TArg2 arg2, TArg3 arg3, object state) + { + return FromAsync(beginMethod, endMethod, arg1, arg2, arg3, state, m_defaultCreationOptions); + } + + /// <summary> + /// Creates a <see cref="T:System.Threading.Tasks.Task">Task</see> that represents a pair of begin + /// and end methods that conform to the Asynchronous Programming Model pattern. + /// </summary> + /// <typeparam name="TArg1">The type of the first argument passed to the <paramref + /// name="beginMethod"/> + /// delegate.</typeparam> + /// <typeparam name="TArg2">The type of the second argument passed to <paramref name="beginMethod"/> + /// delegate.</typeparam> + /// <typeparam name="TArg3">The type of the third argument passed to <paramref name="beginMethod"/> + /// delegate.</typeparam> + /// <param name="beginMethod">The delegate that begins the asynchronous operation.</param> + /// <param name="endMethod">The delegate that ends the asynchronous operation.</param> + /// <param name="arg1">The first argument passed to the <paramref name="beginMethod"/> + /// delegate.</param> + /// <param name="arg2">The second argument passed to the <paramref name="beginMethod"/> + /// delegate.</param> + /// <param name="arg3">The third argument passed to the <paramref name="beginMethod"/> + /// delegate.</param> + /// <param name="creationOptions">The TaskCreationOptions value that controls the behavior of the + /// created <see cref="T:System.Threading.Tasks.Task">Task</see>.</param> + /// <param name="state">An object containing data to be used by the <paramref name="beginMethod"/> + /// delegate.</param> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the + /// <paramref name="beginMethod"/> argument is null.</exception> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the + /// <paramref name="endMethod"/> argument is null.</exception> + /// <exception cref="T:System.ArgumentOutOfRangeException">The exception that is thrown when the + /// <paramref name="creationOptions"/> argument specifies an invalid TaskCreationOptions + /// value.</exception> + /// <returns>The created <see cref="T:System.Threading.Tasks.Task">Task</see> that represents the + /// asynchronous operation.</returns> + /// <remarks> + /// This method throws any exceptions thrown by the <paramref name="beginMethod"/>. + /// </remarks> + public Task FromAsync<TArg1, TArg2, TArg3>( + Func<TArg1, TArg2, TArg3, AsyncCallback, object, IAsyncResult> beginMethod, + Action<IAsyncResult> endMethod, + TArg1 arg1, TArg2 arg2, TArg3 arg3, object state, TaskCreationOptions creationOptions) + { + return TaskFactory<VoidTaskResult>.FromAsyncImpl<TArg1, TArg2, TArg3>(beginMethod, null, endMethod, arg1, arg2, arg3, state, creationOptions); + } + + // + // Additional FromAsync() overloads used for inferencing convenience + // + + /// <summary> + /// Creates a <see cref="T:System.Threading.Tasks.Task{TResult}">Task</see> that executes an end + /// method function when a specified <see cref="T:System.IAsyncResult">IAsyncResult</see> completes. + /// </summary> + /// <typeparam name="TResult">The type of the result available through the + /// <see cref="T:System.Threading.Tasks.Task{TResult}">Task</see>. + /// </typeparam> + /// <param name="asyncResult">The IAsyncResult whose completion should trigger the processing of the + /// <paramref name="endMethod"/>.</param> + /// <param name="endMethod">The function delegate that processes the completed <paramref + /// name="asyncResult"/>.</param> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the + /// <paramref name="asyncResult"/> argument is null.</exception> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the + /// <paramref name="endMethod"/> argument is null.</exception> + /// <returns>A <see cref="T:System.Threading.Tasks.Task{TResult}">Task</see> that represents the + /// asynchronous operation.</returns> + [MethodImplAttribute(MethodImplOptions.NoInlining)] // Methods containing StackCrawlMark local var have to be marked non-inlineable + public Task<TResult> FromAsync<TResult>( + IAsyncResult asyncResult, Func<IAsyncResult, TResult> endMethod) + { + StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller; + return TaskFactory<TResult>.FromAsyncImpl(asyncResult, endMethod, null, m_defaultCreationOptions, DefaultScheduler, ref stackMark); + } + + /// <summary> + /// Creates a <see cref="T:System.Threading.Tasks.Task{TResult}">Task</see> that executes an end + /// method function when a specified <see cref="T:System.IAsyncResult">IAsyncResult</see> completes. + /// </summary> + /// <typeparam name="TResult">The type of the result available through the + /// <see cref="T:System.Threading.Tasks.Task{TResult}">Task</see>. + /// </typeparam> + /// <param name="asyncResult">The IAsyncResult whose completion should trigger the processing of the + /// <paramref name="endMethod"/>.</param> + /// <param name="endMethod">The function delegate that processes the completed <paramref + /// name="asyncResult"/>.</param> + /// <param name="creationOptions">The TaskCreationOptions value that controls the behavior of the + /// created <see cref="T:System.Threading.Tasks.Task{TResult}">Task</see>.</param> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the + /// <paramref name="asyncResult"/> argument is null.</exception> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the + /// <paramref name="endMethod"/> argument is null.</exception> + /// <exception cref="T:System.ArgumentOutOfRangeException">The exception that is thrown when the + /// <paramref name="creationOptions"/> argument specifies an invalid TaskCreationOptions + /// value.</exception> + /// <returns>A <see cref="T:System.Threading.Tasks.Task{TResult}">Task</see> that represents the + /// asynchronous operation.</returns> + [MethodImplAttribute(MethodImplOptions.NoInlining)] // Methods containing StackCrawlMark local var have to be marked non-inlineable + public Task<TResult> FromAsync<TResult>( + IAsyncResult asyncResult, Func<IAsyncResult, TResult> endMethod, TaskCreationOptions creationOptions) + { + StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller; + return TaskFactory<TResult>.FromAsyncImpl(asyncResult, endMethod, null, creationOptions, DefaultScheduler, ref stackMark); + } + + /// <summary> + /// Creates a <see cref="T:System.Threading.Tasks.Task{TResult}">Task</see> that executes an end + /// method function when a specified <see cref="T:System.IAsyncResult">IAsyncResult</see> completes. + /// </summary> + /// <typeparam name="TResult">The type of the result available through the + /// <see cref="T:System.Threading.Tasks.Task{TResult}">Task</see>. + /// </typeparam> + /// <param name="asyncResult">The IAsyncResult whose completion should trigger the processing of the + /// <paramref name="endMethod"/>.</param> + /// <param name="endMethod">The function delegate that processes the completed <paramref + /// name="asyncResult"/>.</param> + /// <param name="scheduler">The <see cref="System.Threading.Tasks.TaskScheduler">TaskScheduler</see> + /// that is used to schedule the task that executes the end method.</param> + /// <param name="creationOptions">The TaskCreationOptions value that controls the behavior of the + /// created <see cref="T:System.Threading.Tasks.Task{TResult}">Task</see>.</param> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the + /// <paramref name="asyncResult"/> argument is null.</exception> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the + /// <paramref name="endMethod"/> argument is null.</exception> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the + /// <paramref name="scheduler"/> argument is null.</exception> + /// <exception cref="T:System.ArgumentOutOfRangeException">The exception that is thrown when the + /// <paramref name="creationOptions"/> argument specifies an invalid TaskCreationOptions + /// value.</exception> + /// <returns>A <see cref="T:System.Threading.Tasks.Task{TResult}">Task</see> that represents the + /// asynchronous operation.</returns> + [MethodImplAttribute(MethodImplOptions.NoInlining)] // Methods containing StackCrawlMark local var have to be marked non-inlineable + public Task<TResult> FromAsync<TResult>( + IAsyncResult asyncResult, Func<IAsyncResult, TResult> endMethod, TaskCreationOptions creationOptions, TaskScheduler scheduler) + { + StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller; + return TaskFactory<TResult>.FromAsyncImpl(asyncResult, endMethod, null, creationOptions, scheduler, ref stackMark); + } + + /// <summary> + /// Creates a <see cref="T:System.Threading.Tasks.Task{TResult}">Task</see> that represents a pair of + /// begin and end methods that conform to the Asynchronous Programming Model pattern. + /// </summary> + /// <typeparam name="TResult">The type of the result available through the + /// <see cref="T:System.Threading.Tasks.Task{TResult}">Task</see>. + /// </typeparam> + /// <param name="beginMethod">The delegate that begins the asynchronous operation.</param> + /// <param name="endMethod">The delegate that ends the asynchronous operation.</param> + /// <param name="state">An object containing data to be used by the <paramref name="beginMethod"/> + /// delegate.</param> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the + /// <paramref name="beginMethod"/> argument is null.</exception> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the + /// <paramref name="endMethod"/> argument is null.</exception> + /// <returns>The created <see cref="T:System.Threading.Tasks.Task{TResult}">Task</see> that + /// represents the asynchronous operation.</returns> + /// <remarks> + /// This method throws any exceptions thrown by the <paramref name="beginMethod"/>. + /// </remarks> + public Task<TResult> FromAsync<TResult>( + Func<AsyncCallback, object, IAsyncResult> beginMethod, + Func<IAsyncResult, TResult> endMethod, object state) + { + return TaskFactory<TResult>.FromAsyncImpl(beginMethod, endMethod, null, state, m_defaultCreationOptions); + } + + /// <summary> + /// Creates a <see cref="T:System.Threading.Tasks.Task{TResult}">Task</see> that represents a pair of + /// begin and end methods that conform to the Asynchronous Programming Model pattern. + /// </summary> + /// <typeparam name="TResult">The type of the result available through the + /// <see cref="T:System.Threading.Tasks.Task{TResult}">Task</see>. + /// </typeparam> + /// <param name="beginMethod">The delegate that begins the asynchronous operation.</param> + /// <param name="endMethod">The delegate that ends the asynchronous operation.</param> + /// <param name="creationOptions">The TaskCreationOptions value that controls the behavior of the + /// created <see cref="T:System.Threading.Tasks.Task{TResult}">Task</see>.</param> + /// <param name="state">An object containing data to be used by the <paramref name="beginMethod"/> + /// delegate.</param> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the + /// <paramref name="beginMethod"/> argument is null.</exception> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the + /// <paramref name="endMethod"/> argument is null.</exception> + /// <exception cref="T:System.ArgumentOutOfRangeException">The exception that is thrown when the + /// <paramref name="creationOptions"/> argument specifies an invalid TaskCreationOptions + /// value.</exception> + /// <returns>The created <see cref="T:System.Threading.Tasks.Task{TResult}">Task</see> that + /// represents the asynchronous operation.</returns> + /// <remarks> + /// This method throws any exceptions thrown by the <paramref name="beginMethod"/>. + /// </remarks> + public Task<TResult> FromAsync<TResult>( + Func<AsyncCallback, object, IAsyncResult> beginMethod, + Func<IAsyncResult, TResult> endMethod, object state, TaskCreationOptions creationOptions) + { + return TaskFactory<TResult>.FromAsyncImpl(beginMethod, endMethod, null, state, creationOptions); + } + + /// <summary> + /// Creates a <see cref="T:System.Threading.Tasks.Task{TResult}">Task</see> that represents a pair of + /// begin and end methods that conform to the Asynchronous Programming Model pattern. + /// </summary> + /// <typeparam name="TArg1">The type of the first argument passed to the <paramref + /// name="beginMethod"/> delegate.</typeparam> + /// <typeparam name="TResult">The type of the result available through the + /// <see cref="T:System.Threading.Tasks.Task{TResult}">Task</see>. + /// </typeparam> + /// <param name="beginMethod">The delegate that begins the asynchronous operation.</param> + /// <param name="endMethod">The delegate that ends the asynchronous operation.</param> + /// <param name="arg1">The first argument passed to the <paramref name="beginMethod"/> + /// delegate.</param> + /// <param name="state">An object containing data to be used by the <paramref name="beginMethod"/> + /// delegate.</param> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the + /// <paramref name="beginMethod"/> argument is null.</exception> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the + /// <paramref name="endMethod"/> argument is null.</exception> + /// <returns>The created <see cref="T:System.Threading.Tasks.Task{TResult}">Task</see> that + /// represents the asynchronous operation.</returns> + /// <remarks> + /// This method throws any exceptions thrown by the <paramref name="beginMethod"/>. + /// </remarks> + public Task<TResult> FromAsync<TArg1, TResult>( + Func<TArg1, AsyncCallback, object, IAsyncResult> beginMethod, + Func<IAsyncResult, TResult> endMethod, TArg1 arg1, object state) + { + return TaskFactory<TResult>.FromAsyncImpl(beginMethod, endMethod, null, arg1, state, m_defaultCreationOptions); + } + + /// <summary> + /// Creates a <see cref="T:System.Threading.Tasks.Task{TResult}">Task</see> that represents a pair of + /// begin and end methods that conform to the Asynchronous Programming Model pattern. + /// </summary> + /// <typeparam name="TArg1">The type of the first argument passed to the <paramref + /// name="beginMethod"/> delegate.</typeparam> + /// <typeparam name="TResult">The type of the result available through the + /// <see cref="T:System.Threading.Tasks.Task{TResult}">Task</see>. + /// </typeparam> + /// <param name="beginMethod">The delegate that begins the asynchronous operation.</param> + /// <param name="endMethod">The delegate that ends the asynchronous operation.</param> + /// <param name="arg1">The first argument passed to the <paramref name="beginMethod"/> + /// delegate.</param> + /// <param name="creationOptions">The TaskCreationOptions value that controls the behavior of the + /// created <see cref="T:System.Threading.Tasks.Task{TResult}">Task</see>.</param> + /// <param name="state">An object containing data to be used by the <paramref name="beginMethod"/> + /// delegate.</param> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the + /// <paramref name="beginMethod"/> argument is null.</exception> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the + /// <paramref name="endMethod"/> argument is null.</exception> + /// <exception cref="T:System.ArgumentOutOfRangeException">The exception that is thrown when the + /// <paramref name="creationOptions"/> argument specifies an invalid TaskCreationOptions + /// value.</exception> + /// <returns>The created <see cref="T:System.Threading.Tasks.Task{TResult}">Task</see> that + /// represents the asynchronous operation.</returns> + /// <remarks> + /// This method throws any exceptions thrown by the <paramref name="beginMethod"/>. + /// </remarks> + public Task<TResult> FromAsync<TArg1, TResult>(Func<TArg1, AsyncCallback, object, IAsyncResult> beginMethod, + Func<IAsyncResult, TResult> endMethod, TArg1 arg1, object state, TaskCreationOptions creationOptions) + { + return TaskFactory<TResult>.FromAsyncImpl(beginMethod, endMethod, null, arg1, state, creationOptions); + } + + /// <summary> + /// Creates a <see cref="T:System.Threading.Tasks.Task{TResult}">Task</see> that represents a pair of + /// begin and end methods that conform to the Asynchronous Programming Model pattern. + /// </summary> + /// <typeparam name="TArg1">The type of the first argument passed to the <paramref + /// name="beginMethod"/> delegate.</typeparam> + /// <typeparam name="TArg2">The type of the second argument passed to <paramref name="beginMethod"/> + /// delegate.</typeparam> + /// <typeparam name="TResult">The type of the result available through the + /// <see cref="T:System.Threading.Tasks.Task{TResult}">Task</see>. + /// </typeparam> + /// <param name="beginMethod">The delegate that begins the asynchronous operation.</param> + /// <param name="endMethod">The delegate that ends the asynchronous operation.</param> + /// <param name="arg1">The first argument passed to the <paramref name="beginMethod"/> + /// delegate.</param> + /// <param name="arg2">The second argument passed to the <paramref name="beginMethod"/> + /// delegate.</param> + /// <param name="state">An object containing data to be used by the <paramref name="beginMethod"/> + /// delegate.</param> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the + /// <paramref name="beginMethod"/> argument is null.</exception> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the + /// <paramref name="endMethod"/> argument is null.</exception> + /// <returns>The created <see cref="T:System.Threading.Tasks.Task{TResult}">Task</see> that + /// represents the asynchronous operation.</returns> + /// <remarks> + /// This method throws any exceptions thrown by the <paramref name="beginMethod"/>. + /// </remarks> + public Task<TResult> FromAsync<TArg1, TArg2, TResult>(Func<TArg1, TArg2, AsyncCallback, object, IAsyncResult> beginMethod, + Func<IAsyncResult, TResult> endMethod, TArg1 arg1, TArg2 arg2, object state) + { + return TaskFactory<TResult>.FromAsyncImpl(beginMethod, endMethod, null, arg1, arg2, state, m_defaultCreationOptions); + } + + /// <summary> + /// Creates a <see cref="T:System.Threading.Tasks.Task{TResult}">Task</see> that represents a pair of + /// begin and end methods that conform to the Asynchronous Programming Model pattern. + /// </summary> + /// <typeparam name="TArg1">The type of the first argument passed to the <paramref + /// name="beginMethod"/> delegate.</typeparam> + /// <typeparam name="TArg2">The type of the second argument passed to <paramref name="beginMethod"/> + /// delegate.</typeparam> + /// <typeparam name="TResult">The type of the result available through the + /// <see cref="T:System.Threading.Tasks.Task{TResult}">Task</see>. + /// </typeparam> + /// <param name="beginMethod">The delegate that begins the asynchronous operation.</param> + /// <param name="endMethod">The delegate that ends the asynchronous operation.</param> + /// <param name="arg1">The first argument passed to the <paramref name="beginMethod"/> + /// delegate.</param> + /// <param name="arg2">The second argument passed to the <paramref name="beginMethod"/> + /// delegate.</param> + /// <param name="creationOptions">The TaskCreationOptions value that controls the behavior of the + /// created <see cref="T:System.Threading.Tasks.Task{TResult}">Task</see>.</param> + /// <param name="state">An object containing data to be used by the <paramref name="beginMethod"/> + /// delegate.</param> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the + /// <paramref name="beginMethod"/> argument is null.</exception> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the + /// <paramref name="endMethod"/> argument is null.</exception> + /// <exception cref="T:System.ArgumentOutOfRangeException">The exception that is thrown when the + /// <paramref name="creationOptions"/> argument specifies an invalid TaskCreationOptions + /// value.</exception> + /// <returns>The created <see cref="T:System.Threading.Tasks.Task{TResult}">Task</see> that + /// represents the asynchronous operation.</returns> + /// <remarks> + /// This method throws any exceptions thrown by the <paramref name="beginMethod"/>. + /// </remarks> + public Task<TResult> FromAsync<TArg1, TArg2, TResult>( + Func<TArg1, TArg2, AsyncCallback, object, IAsyncResult> beginMethod, + Func<IAsyncResult, TResult> endMethod, TArg1 arg1, TArg2 arg2, object state, TaskCreationOptions creationOptions) + { + return TaskFactory<TResult>.FromAsyncImpl(beginMethod, endMethod, null, arg1, arg2, state, creationOptions); + } + + /// <summary> + /// Creates a <see cref="T:System.Threading.Tasks.Task{TResult}">Task</see> that represents a pair of + /// begin and end methods that conform to the Asynchronous Programming Model pattern. + /// </summary> + /// <typeparam name="TArg1">The type of the first argument passed to the <paramref + /// name="beginMethod"/> delegate.</typeparam> + /// <typeparam name="TArg2">The type of the second argument passed to <paramref name="beginMethod"/> + /// delegate.</typeparam> + /// <typeparam name="TArg3">The type of the third argument passed to <paramref name="beginMethod"/> + /// delegate.</typeparam> + /// <typeparam name="TResult">The type of the result available through the + /// <see cref="T:System.Threading.Tasks.Task{TResult}">Task</see>. + /// </typeparam> + /// <param name="beginMethod">The delegate that begins the asynchronous operation.</param> + /// <param name="endMethod">The delegate that ends the asynchronous operation.</param> + /// <param name="arg1">The first argument passed to the <paramref name="beginMethod"/> + /// delegate.</param> + /// <param name="arg2">The second argument passed to the <paramref name="beginMethod"/> + /// delegate.</param> + /// <param name="arg3">The third argument passed to the <paramref name="beginMethod"/> + /// delegate.</param> + /// <param name="state">An object containing data to be used by the <paramref name="beginMethod"/> + /// delegate.</param> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the + /// <paramref name="beginMethod"/> argument is null.</exception> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the + /// <paramref name="endMethod"/> argument is null.</exception> + /// <returns>The created <see cref="T:System.Threading.Tasks.Task{TResult}">Task</see> that + /// represents the asynchronous operation.</returns> + /// <remarks> + /// This method throws any exceptions thrown by the <paramref name="beginMethod"/>. + /// </remarks> + public Task<TResult> FromAsync<TArg1, TArg2, TArg3, TResult>( + Func<TArg1, TArg2, TArg3, AsyncCallback, object, IAsyncResult> beginMethod, + Func<IAsyncResult, TResult> endMethod, TArg1 arg1, TArg2 arg2, TArg3 arg3, object state) + { + return TaskFactory<TResult>.FromAsyncImpl(beginMethod, endMethod, null, arg1, arg2, arg3, state, m_defaultCreationOptions); + } + + /// <summary> + /// Creates a <see cref="T:System.Threading.Tasks.Task{TResult}">Task</see> that represents a pair of + /// begin and end methods that conform to the Asynchronous Programming Model pattern. + /// </summary> + /// <typeparam name="TArg1">The type of the first argument passed to the <paramref + /// name="beginMethod"/> delegate.</typeparam> + /// <typeparam name="TArg2">The type of the second argument passed to <paramref name="beginMethod"/> + /// delegate.</typeparam> + /// <typeparam name="TArg3">The type of the third argument passed to <paramref name="beginMethod"/> + /// delegate.</typeparam> + /// <typeparam name="TResult">The type of the result available through the + /// <see cref="T:System.Threading.Tasks.Task{TResult}">Task</see>. + /// </typeparam> + /// <param name="beginMethod">The delegate that begins the asynchronous operation.</param> + /// <param name="endMethod">The delegate that ends the asynchronous operation.</param> + /// <param name="arg1">The first argument passed to the <paramref name="beginMethod"/> + /// delegate.</param> + /// <param name="arg2">The second argument passed to the <paramref name="beginMethod"/> + /// delegate.</param> + /// <param name="arg3">The third argument passed to the <paramref name="beginMethod"/> + /// delegate.</param> + /// <param name="creationOptions">The TaskCreationOptions value that controls the behavior of the + /// created <see cref="T:System.Threading.Tasks.Task{TResult}">Task</see>.</param> + /// <param name="state">An object containing data to be used by the <paramref name="beginMethod"/> + /// delegate.</param> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the + /// <paramref name="beginMethod"/> argument is null.</exception> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the + /// <paramref name="endMethod"/> argument is null.</exception> + /// <exception cref="T:System.ArgumentOutOfRangeException">The exception that is thrown when the + /// <paramref name="creationOptions"/> argument specifies an invalid TaskCreationOptions + /// value.</exception> + /// <returns>The created <see cref="T:System.Threading.Tasks.Task{TResult}">Task</see> that + /// represents the asynchronous operation.</returns> + /// <remarks> + /// This method throws any exceptions thrown by the <paramref name="beginMethod"/>. + /// </remarks> + public Task<TResult> FromAsync<TArg1, TArg2, TArg3, TResult>( + Func<TArg1, TArg2, TArg3, AsyncCallback, object, IAsyncResult> beginMethod, + Func<IAsyncResult, TResult> endMethod, TArg1 arg1, TArg2 arg2, TArg3 arg3, object state, TaskCreationOptions creationOptions) + { + return TaskFactory<TResult>.FromAsyncImpl(beginMethod, endMethod, null, arg1, arg2, arg3, state, creationOptions); + } + + /// <summary> + /// Check validity of options passed to FromAsync method + /// </summary> + /// <param name="creationOptions">The options to be validated.</param> + /// <param name="hasBeginMethod">determines type of FromAsync method that called this method</param> + internal static void CheckFromAsyncOptions(TaskCreationOptions creationOptions, bool hasBeginMethod) + { + if (hasBeginMethod) + { + // Options detected here cause exceptions in FromAsync methods that take beginMethod as a parameter + if ((creationOptions & TaskCreationOptions.LongRunning) != 0) + throw new ArgumentOutOfRangeException("creationOptions", Environment.GetResourceString("Task_FromAsync_LongRunning")); + if ((creationOptions & TaskCreationOptions.PreferFairness) != 0) + throw new ArgumentOutOfRangeException("creationOptions", Environment.GetResourceString("Task_FromAsync_PreferFairness")); + } + + // Check for general validity of options + if ((creationOptions & + ~(TaskCreationOptions.AttachedToParent | + TaskCreationOptions.DenyChildAttach | + TaskCreationOptions.HideScheduler | + TaskCreationOptions.PreferFairness | + TaskCreationOptions.LongRunning)) != 0) + { + throw new ArgumentOutOfRangeException("creationOptions"); + } + } + + + // + // ContinueWhenAll methods + // + + // A Task<Task[]> that, given an initial collection of N tasks, will complete when + // it has been invoked N times. This allows us to replace this logic: + // Task<Task[]> promise = new Task<Task[]>(...); + // int _count = tasksCopy.Length; + // Action<Task> completionAction = delegate {if(Interlocked.Decrement(ref _count) == 0) promise.TrySetResult(tasksCopy); + // for(int i=0; i<_count; i++) + // tasksCopy[i].AddCompletionAction(completionAction); + // with this logic: + // CompletionOnCountdownPromise promise = new CompletionOnCountdownPromise(tasksCopy); + // for(int i=0; i<tasksCopy.Length; i++) tasksCopy[i].AddCompletionAction(promise); + // which saves a few allocations. + // + // Used in TaskFactory.CommonCWAllLogic(Task[]), below. + private sealed class CompleteOnCountdownPromise : Task<Task[]>, ITaskCompletionAction + { + private readonly Task[] _tasks; + private int _count; + + internal CompleteOnCountdownPromise(Task[] tasksCopy) : base() + { + Contract.Requires((tasksCopy != null) && (tasksCopy.Length > 0), "Expected non-null task array with at least one element in it"); + _tasks = tasksCopy; + _count = tasksCopy.Length; + + if (AsyncCausalityTracer.LoggingOn) + AsyncCausalityTracer.TraceOperationCreation(CausalityTraceLevel.Required, this.Id, "TaskFactory.ContinueWhenAll", 0); + + if (Task.s_asyncDebuggingEnabled) + { + AddToActiveTasks(this); + } + } + + public void Invoke(Task completingTask) + { + if (AsyncCausalityTracer.LoggingOn) + AsyncCausalityTracer.TraceOperationRelation(CausalityTraceLevel.Important, this.Id, CausalityRelation.Join); + + if (completingTask.IsWaitNotificationEnabled) this.SetNotificationForWaitCompletion(enabled: true); + if (Interlocked.Decrement(ref _count) == 0) + { + if (AsyncCausalityTracer.LoggingOn) + AsyncCausalityTracer.TraceOperationCompletion(CausalityTraceLevel.Required, this.Id, AsyncCausalityStatus.Completed); + + if (Task.s_asyncDebuggingEnabled) + { + RemoveFromActiveTasks(this.Id); + } + + TrySetResult(_tasks); + } + Contract.Assert(_count >= 0, "Count should never go below 0"); + } + + public bool InvokeMayRunArbitraryCode { get { return true; } } + + /// <summary> + /// Returns whether we should notify the debugger of a wait completion. This returns + /// true iff at least one constituent task has its bit set. + /// </summary> + internal override bool ShouldNotifyDebuggerOfWaitCompletion + { + get + { + return + base.ShouldNotifyDebuggerOfWaitCompletion && + Task.AnyTaskRequiresNotifyDebuggerOfWaitCompletion(_tasks); + } + } + } + + // Performs some logic common to all ContinueWhenAll() overloads + internal static Task<Task[]> CommonCWAllLogic(Task[] tasksCopy) + { + Contract.Requires(tasksCopy != null); + + // Create a promise task to be returned to the user + CompleteOnCountdownPromise promise = new CompleteOnCountdownPromise(tasksCopy); + + for (int i = 0; i < tasksCopy.Length; i++) + { + if (tasksCopy[i].IsCompleted) promise.Invoke(tasksCopy[i]); // Short-circuit the completion action, if possible + else tasksCopy[i].AddCompletionAction(promise); // simple completion action + } + + return promise; + } + + + // A Task<Task<T>[]> that, given an initial collection of N tasks, will complete when + // it has been invoked N times. See comments for non-generic CompleteOnCountdownPromise class. + // + // Used in TaskFactory.CommonCWAllLogic<TResult>(Task<TResult>[]), below. + private sealed class CompleteOnCountdownPromise<T> : Task<Task<T>[]>, ITaskCompletionAction + { + private readonly Task<T>[] _tasks; + private int _count; + + internal CompleteOnCountdownPromise(Task<T>[] tasksCopy) : base() + { + Contract.Requires((tasksCopy != null) && (tasksCopy.Length > 0), "Expected non-null task array with at least one element in it"); + _tasks = tasksCopy; + _count = tasksCopy.Length; + + if (AsyncCausalityTracer.LoggingOn) + AsyncCausalityTracer.TraceOperationCreation(CausalityTraceLevel.Required, this.Id, "TaskFactory.ContinueWhenAll<>", 0); + + if (Task.s_asyncDebuggingEnabled) + { + AddToActiveTasks(this); + } + } + + public void Invoke(Task completingTask) + { + if (AsyncCausalityTracer.LoggingOn) + AsyncCausalityTracer.TraceOperationRelation(CausalityTraceLevel.Important, this.Id, CausalityRelation.Join); + + if (completingTask.IsWaitNotificationEnabled) this.SetNotificationForWaitCompletion(enabled: true); + if (Interlocked.Decrement(ref _count) == 0) + { + if (AsyncCausalityTracer.LoggingOn) + AsyncCausalityTracer.TraceOperationCompletion(CausalityTraceLevel.Required, this.Id, AsyncCausalityStatus.Completed); + + if (Task.s_asyncDebuggingEnabled) + { + RemoveFromActiveTasks(this.Id); + } + + TrySetResult(_tasks); + } + Contract.Assert(_count >= 0, "Count should never go below 0"); + } + + public bool InvokeMayRunArbitraryCode { get { return true; } } + + /// <summary> + /// Returns whether we should notify the debugger of a wait completion. This returns + /// true iff at least one constituent task has its bit set. + /// </summary> + internal override bool ShouldNotifyDebuggerOfWaitCompletion + { + get + { + return + base.ShouldNotifyDebuggerOfWaitCompletion && + Task.AnyTaskRequiresNotifyDebuggerOfWaitCompletion(_tasks); + } + } + } + + + internal static Task<Task<T>[]> CommonCWAllLogic<T>(Task<T>[] tasksCopy) + { + Contract.Requires(tasksCopy != null); + + // Create a promise task to be returned to the user + CompleteOnCountdownPromise<T> promise = new CompleteOnCountdownPromise<T>(tasksCopy); + + for (int i = 0; i < tasksCopy.Length; i++) + { + if (tasksCopy[i].IsCompleted) promise.Invoke(tasksCopy[i]); // Short-circuit the completion action, if possible + else tasksCopy[i].AddCompletionAction(promise); // simple completion action + } + + return promise; + } + /// <summary> + /// Creates a continuation <see cref="T:System.Threading.Tasks.Task">Task</see> + /// that will be started upon the completion of a set of provided Tasks. + /// </summary> + /// <param name="tasks">The array of tasks from which to continue.</param> + /// <param name="continuationAction">The action delegate to execute when all tasks in + /// the <paramref name="tasks"/> array have completed.</param> + /// <returns>The new continuation <see cref="T:System.Threading.Tasks.Task">Task</see>.</returns> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the + /// <paramref name="tasks"/> array is null.</exception> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the + /// <paramref name="continuationAction"/> argument is null.</exception> + /// <exception cref="T:System.ArgumentException">The exception that is thrown when the + /// <paramref name="tasks"/> array contains a null value.</exception> + /// <exception cref="T:System.ArgumentException">The exception that is thrown when the + /// <paramref name="tasks"/> array is empty.</exception> + [MethodImplAttribute(MethodImplOptions.NoInlining)] // Methods containing StackCrawlMark local var have to be marked non-inlineable + public Task ContinueWhenAll(Task[] tasks, Action<Task[]> continuationAction) + { + if (continuationAction == null) throw new ArgumentNullException("continuationAction"); + Contract.EndContractBlock(); + + StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller; + return TaskFactory<VoidTaskResult>.ContinueWhenAllImpl(tasks, null, continuationAction, m_defaultContinuationOptions, m_defaultCancellationToken, DefaultScheduler, ref stackMark); + } + + + /// <summary> + /// Creates a continuation <see cref="T:System.Threading.Tasks.Task">Task</see> + /// that will be started upon the completion of a set of provided Tasks. + /// </summary> + /// <param name="tasks">The array of tasks from which to continue.</param> + /// <param name="continuationAction">The action delegate to execute when all tasks in + /// the <paramref name="tasks"/> array have completed.</param> + /// <param name="cancellationToken">The <see cref="System.Threading.CancellationToken">CancellationToken</see> + /// that will be assigned to the new continuation task.</param> + /// <returns>The new continuation <see cref="T:System.Threading.Tasks.Task">Task</see>.</returns> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the + /// <paramref name="tasks"/> array is null.</exception> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the + /// <paramref name="continuationAction"/> argument is null.</exception> + /// <exception cref="T:System.ArgumentException">The exception that is thrown when the + /// <paramref name="tasks"/> array contains a null value.</exception> + /// <exception cref="T:System.ArgumentException">The exception that is thrown when the + /// <paramref name="tasks"/> array is empty.</exception> + /// <exception cref="T:System.ObjectDisposedException">The provided <see cref="System.Threading.CancellationToken">CancellationToken</see> + /// has already been disposed. + /// </exception> + [MethodImplAttribute(MethodImplOptions.NoInlining)] // Methods containing StackCrawlMark local var have to be marked non-inlineable + public Task ContinueWhenAll(Task[] tasks, Action<Task[]> continuationAction, CancellationToken cancellationToken) + { + if (continuationAction == null) throw new ArgumentNullException("continuationAction"); + Contract.EndContractBlock(); + + StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller; + return TaskFactory<VoidTaskResult>.ContinueWhenAllImpl(tasks, null, continuationAction, m_defaultContinuationOptions, cancellationToken, DefaultScheduler, ref stackMark); + } + + /// <summary> + /// Creates a continuation <see cref="T:System.Threading.Tasks.Task">Task</see> + /// that will be started upon the completion of a set of provided Tasks. + /// </summary> + /// <param name="tasks">The array of tasks from which to continue.</param> + /// <param name="continuationAction">The action delegate to execute when all tasks in the <paramref + /// name="tasks"/> array have completed.</param> + /// <param name="continuationOptions">The <see cref="System.Threading.Tasks.TaskContinuationOptions"> + /// TaskContinuationOptions</see> value that controls the behavior of + /// the created continuation <see cref="T:System.Threading.Tasks.Task">Task</see>.</param> + /// <returns>The new continuation <see cref="T:System.Threading.Tasks.Task">Task</see>.</returns> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the + /// <paramref name="tasks"/> array is null.</exception> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the + /// <paramref name="continuationAction"/> argument is null.</exception> + /// <exception cref="T:System.ArgumentException">The exception that is thrown when the + /// <paramref name="tasks"/> array contains a null value.</exception> + /// <exception cref="T:System.ArgumentException">The exception that is thrown when the + /// <paramref name="tasks"/> array is empty.</exception> + /// <exception cref="T:System.ArgumentOutOfRangeException">The exception that is thrown when the + /// <paramref name="continuationOptions"/> argument specifies an invalid TaskContinuationOptions + /// value.</exception> + /// <remarks> + /// The NotOn* and OnlyOn* <see cref="System.Threading.Tasks.TaskContinuationOptions">TaskContinuationOptions</see>, + /// which constrain for which <see cref="System.Threading.Tasks.TaskStatus">TaskStatus</see> states a continuation + /// will be executed, are illegal with ContinueWhenAll. + /// </remarks> + [MethodImplAttribute(MethodImplOptions.NoInlining)] // Methods containing StackCrawlMark local var have to be marked non-inlineable + public Task ContinueWhenAll(Task[] tasks, Action<Task[]> continuationAction, TaskContinuationOptions continuationOptions) + { + if (continuationAction == null) throw new ArgumentNullException("continuationAction"); + Contract.EndContractBlock(); + + StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller; + return TaskFactory<VoidTaskResult>.ContinueWhenAllImpl(tasks, null, continuationAction, continuationOptions, m_defaultCancellationToken, DefaultScheduler, ref stackMark); + } + + /// <summary> + /// Creates a continuation <see cref="T:System.Threading.Tasks.Task">Task</see> + /// that will be started upon the completion of a set of provided Tasks. + /// </summary> + /// <param name="tasks">The array of tasks from which to continue.</param> + /// <param name="continuationAction">The action delegate to execute when all tasks in the <paramref + /// name="tasks"/> array have completed.</param> + /// <param name="cancellationToken">The <see cref="System.Threading.CancellationToken">CancellationToken</see> + /// that will be assigned to the new continuation task.</param> + /// <param name="continuationOptions">The <see cref="System.Threading.Tasks.TaskContinuationOptions"> + /// TaskContinuationOptions</see> value that controls the behavior of + /// the created continuation <see cref="T:System.Threading.Tasks.Task">Task</see>.</param> + /// <param name="scheduler">The <see cref="System.Threading.Tasks.TaskScheduler">TaskScheduler</see> + /// that is used to schedule the created continuation <see + /// cref="T:System.Threading.Tasks.Task">Task</see>.</param> + /// <returns>The new continuation <see cref="T:System.Threading.Tasks.Task">Task</see>.</returns> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the + /// <paramref name="tasks"/> array is null.</exception> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the + /// <paramref name="continuationAction"/> argument is null.</exception> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the + /// <paramref name="scheduler"/> argument is null.</exception> + /// <exception cref="T:System.ArgumentException">The exception that is thrown when the + /// <paramref name="tasks"/> array contains a null value.</exception> + /// <exception cref="T:System.ArgumentException">The exception that is thrown when the + /// <paramref name="tasks"/> array is empty.</exception> + /// <exception cref="T:System.ArgumentOutOfRangeException">The exception that is thrown when the + /// <paramref name="continuationOptions"/> argument specifies an invalid TaskContinuationOptions + /// value.</exception> + /// <exception cref="T:System.ObjectDisposedException">The provided <see cref="System.Threading.CancellationToken">CancellationToken</see> + /// has already been disposed. + /// </exception> + /// <remarks> + /// The NotOn* and OnlyOn* <see cref="System.Threading.Tasks.TaskContinuationOptions">TaskContinuationOptions</see>, + /// which constrain for which <see cref="System.Threading.Tasks.TaskStatus">TaskStatus</see> states a continuation + /// will be executed, are illegal with ContinueWhenAll. + /// </remarks> + [MethodImplAttribute(MethodImplOptions.NoInlining)] // Methods containing StackCrawlMark local var have to be marked non-inlineable + public Task ContinueWhenAll(Task[] tasks, Action<Task[]> continuationAction, CancellationToken cancellationToken, + TaskContinuationOptions continuationOptions, TaskScheduler scheduler) + { + if (continuationAction == null) throw new ArgumentNullException("continuationAction"); + Contract.EndContractBlock(); + + StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller; + return TaskFactory<VoidTaskResult>.ContinueWhenAllImpl(tasks, null, continuationAction, continuationOptions, cancellationToken, scheduler, ref stackMark); + } + + /// <summary> + /// Creates a continuation <see cref="T:System.Threading.Tasks.Task">Task</see> + /// that will be started upon the completion of a set of provided Tasks. + /// </summary> + /// <typeparam name="TAntecedentResult">The type of the result of the antecedent <paramref name="tasks"/>.</typeparam> + /// <param name="tasks">The array of tasks from which to continue.</param> + /// <param name="continuationAction">The action delegate to execute when all tasks in + /// the <paramref name="tasks"/> array have completed.</param> + /// <returns>The new continuation <see cref="T:System.Threading.Tasks.Task">Task</see>.</returns> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the + /// <paramref name="tasks"/> array is null.</exception> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the + /// <paramref name="continuationAction"/> argument is null.</exception> + /// <exception cref="T:System.ArgumentException">The exception that is thrown when the + /// <paramref name="tasks"/> array contains a null value.</exception> + /// <exception cref="T:System.ArgumentException">The exception that is thrown when the + /// <paramref name="tasks"/> array is empty.</exception> + [MethodImplAttribute(MethodImplOptions.NoInlining)] // Methods containing StackCrawlMark local var have to be marked non-inlineable + public Task ContinueWhenAll<TAntecedentResult>(Task<TAntecedentResult>[] tasks, Action<Task<TAntecedentResult>[]> continuationAction) + { + if (continuationAction == null) throw new ArgumentNullException("continuationAction"); + Contract.EndContractBlock(); + + StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller; + return TaskFactory<VoidTaskResult>.ContinueWhenAllImpl<TAntecedentResult>(tasks, null, continuationAction, m_defaultContinuationOptions, m_defaultCancellationToken, DefaultScheduler, ref stackMark); + } + + + /// <summary> + /// Creates a continuation <see cref="T:System.Threading.Tasks.Task">Task</see> + /// that will be started upon the completion of a set of provided Tasks. + /// </summary> + /// <typeparam name="TAntecedentResult">The type of the result of the antecedent <paramref name="tasks"/>.</typeparam> + /// <param name="tasks">The array of tasks from which to continue.</param> + /// <param name="continuationAction">The action delegate to execute when all tasks in + /// the <paramref name="tasks"/> array have completed.</param> + /// <param name="cancellationToken">The <see cref="System.Threading.CancellationToken">CancellationToken</see> + /// that will be assigned to the new continuation task.</param> + /// <returns>The new continuation <see cref="T:System.Threading.Tasks.Task">Task</see>.</returns> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the + /// <paramref name="tasks"/> array is null.</exception> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the + /// <paramref name="continuationAction"/> argument is null.</exception> + /// <exception cref="T:System.ArgumentException">The exception that is thrown when the + /// <paramref name="tasks"/> array contains a null value.</exception> + /// <exception cref="T:System.ArgumentException">The exception that is thrown when the + /// <paramref name="tasks"/> array is empty.</exception> + /// <exception cref="T:System.ObjectDisposedException">The provided <see cref="System.Threading.CancellationToken">CancellationToken</see> + /// has already been disposed. + /// </exception> + [MethodImplAttribute(MethodImplOptions.NoInlining)] // Methods containing StackCrawlMark local var have to be marked non-inlineable + public Task ContinueWhenAll<TAntecedentResult>(Task<TAntecedentResult>[] tasks, Action<Task<TAntecedentResult>[]> continuationAction, + CancellationToken cancellationToken) + { + if (continuationAction == null) throw new ArgumentNullException("continuationAction"); + Contract.EndContractBlock(); + + StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller; + return TaskFactory<VoidTaskResult>.ContinueWhenAllImpl<TAntecedentResult>(tasks, null, continuationAction, m_defaultContinuationOptions, cancellationToken, DefaultScheduler, ref stackMark); + } + + /// <summary> + /// Creates a continuation <see cref="T:System.Threading.Tasks.Task">Task</see> + /// that will be started upon the completion of a set of provided Tasks. + /// </summary> + /// <typeparam name="TAntecedentResult">The type of the result of the antecedent <paramref name="tasks"/>.</typeparam> + /// <param name="tasks">The array of tasks from which to continue.</param> + /// <param name="continuationAction">The action delegate to execute when all tasks in the <paramref + /// name="tasks"/> array have completed.</param> + /// <param name="continuationOptions">The <see cref="System.Threading.Tasks.TaskContinuationOptions"> + /// TaskContinuationOptions</see> value that controls the behavior of + /// the created continuation <see cref="T:System.Threading.Tasks.Task">Task</see>.</param> + /// <returns>The new continuation <see cref="T:System.Threading.Tasks.Task">Task</see>.</returns> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the + /// <paramref name="tasks"/> array is null.</exception> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the + /// <paramref name="continuationAction"/> argument is null.</exception> + /// <exception cref="T:System.ArgumentException">The exception that is thrown when the + /// <paramref name="tasks"/> array contains a null value.</exception> + /// <exception cref="T:System.ArgumentException">The exception that is thrown when the + /// <paramref name="tasks"/> array is empty.</exception> + /// <exception cref="T:System.ArgumentOutOfRangeException">The exception that is thrown when the + /// <paramref name="continuationOptions"/> argument specifies an invalid TaskContinuationOptions + /// value.</exception> + /// <remarks> + /// The NotOn* and OnlyOn* <see cref="System.Threading.Tasks.TaskContinuationOptions">TaskContinuationOptions</see>, + /// which constrain for which <see cref="System.Threading.Tasks.TaskStatus">TaskStatus</see> states a continuation + /// will be executed, are illegal with ContinueWhenAll. + /// </remarks> + [MethodImplAttribute(MethodImplOptions.NoInlining)] // Methods containing StackCrawlMark local var have to be marked non-inlineable + public Task ContinueWhenAll<TAntecedentResult>(Task<TAntecedentResult>[] tasks, Action<Task<TAntecedentResult>[]> continuationAction, + TaskContinuationOptions continuationOptions) + { + if (continuationAction == null) throw new ArgumentNullException("continuationAction"); + Contract.EndContractBlock(); + + StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller; + return TaskFactory<VoidTaskResult>.ContinueWhenAllImpl<TAntecedentResult>(tasks, null, continuationAction, continuationOptions, m_defaultCancellationToken, DefaultScheduler, ref stackMark); + } + + /// <summary> + /// Creates a continuation <see cref="T:System.Threading.Tasks.Task">Task</see> + /// that will be started upon the completion of a set of provided Tasks. + /// </summary> + /// <typeparam name="TAntecedentResult">The type of the result of the antecedent <paramref name="tasks"/>.</typeparam> + /// <param name="tasks">The array of tasks from which to continue.</param> + /// <param name="continuationAction">The action delegate to execute when all tasks in the <paramref + /// name="tasks"/> array have completed.</param> + /// <param name="cancellationToken">The <see cref="System.Threading.CancellationToken">CancellationToken</see> + /// that will be assigned to the new continuation task.</param> + /// <param name="continuationOptions">The <see cref="System.Threading.Tasks.TaskContinuationOptions"> + /// TaskContinuationOptions</see> value that controls the behavior of + /// the created continuation <see cref="T:System.Threading.Tasks.Task">Task</see>.</param> + /// <param name="scheduler">The <see cref="System.Threading.Tasks.TaskScheduler">TaskScheduler</see> + /// that is used to schedule the created continuation <see + /// cref="T:System.Threading.Tasks.Task">Task</see>.</param> + /// <returns>The new continuation <see cref="T:System.Threading.Tasks.Task">Task</see>.</returns> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the + /// <paramref name="tasks"/> array is null.</exception> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the + /// <paramref name="continuationAction"/> argument is null.</exception> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the + /// <paramref name="scheduler"/> argument is null.</exception> + /// <exception cref="T:System.ArgumentException">The exception that is thrown when the + /// <paramref name="tasks"/> array contains a null value.</exception> + /// <exception cref="T:System.ArgumentException">The exception that is thrown when the + /// <paramref name="tasks"/> array is empty.</exception> + /// <exception cref="T:System.ArgumentOutOfRangeException">The exception that is thrown when the + /// <paramref name="continuationOptions"/> argument specifies an invalid TaskContinuationOptions + /// value.</exception> + /// <exception cref="T:System.ObjectDisposedException">The provided <see cref="System.Threading.CancellationToken">CancellationToken</see> + /// has already been disposed. + /// </exception> + /// <remarks> + /// The NotOn* and OnlyOn* <see cref="System.Threading.Tasks.TaskContinuationOptions">TaskContinuationOptions</see>, + /// which constrain for which <see cref="System.Threading.Tasks.TaskStatus">TaskStatus</see> states a continuation + /// will be executed, are illegal with ContinueWhenAll. + /// </remarks> + [MethodImplAttribute(MethodImplOptions.NoInlining)] // Methods containing StackCrawlMark local var have to be marked non-inlineable + public Task ContinueWhenAll<TAntecedentResult>(Task<TAntecedentResult>[] tasks, Action<Task<TAntecedentResult>[]> continuationAction, + CancellationToken cancellationToken, TaskContinuationOptions continuationOptions, TaskScheduler scheduler) + { + if (continuationAction == null) throw new ArgumentNullException("continuationAction"); + Contract.EndContractBlock(); + + StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller; + return TaskFactory<VoidTaskResult>.ContinueWhenAllImpl<TAntecedentResult>(tasks, null, continuationAction, continuationOptions, cancellationToken, scheduler, ref stackMark); + } + + /// <summary> + /// Creates a continuation <see cref="T:System.Threading.Tasks.Task">Task</see> + /// that will be started upon the completion of a set of provided Tasks. + /// </summary> + /// <typeparam name="TResult">The type of the result that is returned by the <paramref + /// name="continuationFunction"/> + /// delegate and associated with the created <see + /// cref="T:System.Threading.Tasks.Task{TResult}"/>.</typeparam> + /// <param name="tasks">The array of tasks from which to continue.</param> + /// <param name="continuationFunction">The function delegate to execute when all tasks in the + /// <paramref name="tasks"/> array have completed.</param> + /// <returns>The new continuation <see cref="T:System.Threading.Tasks.Task{TResult}"/>.</returns> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the + /// <paramref name="tasks"/> array is null.</exception> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the + /// <paramref name="continuationFunction"/> argument is null.</exception> + /// <exception cref="T:System.ArgumentException">The exception that is thrown when the + /// <paramref name="tasks"/> array contains a null value.</exception> + /// <exception cref="T:System.ArgumentException">The exception that is thrown when the + /// <paramref name="tasks"/> array is empty.</exception> + [MethodImplAttribute(MethodImplOptions.NoInlining)] // Methods containing StackCrawlMark local var have to be marked non-inlineable + public Task<TResult> ContinueWhenAll<TResult>(Task[] tasks, Func<Task[], TResult> continuationFunction) + { + if (continuationFunction == null) throw new ArgumentNullException("continuationFunction"); + Contract.EndContractBlock(); + + StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller; + return TaskFactory<TResult>.ContinueWhenAllImpl(tasks, continuationFunction, null, m_defaultContinuationOptions, m_defaultCancellationToken, DefaultScheduler, ref stackMark); + } + + + /// <summary> + /// Creates a continuation <see cref="T:System.Threading.Tasks.Task">Task</see> + /// that will be started upon the completion of a set of provided Tasks. + /// </summary> + /// <typeparam name="TResult">The type of the result that is returned by the <paramref + /// name="continuationFunction"/> + /// delegate and associated with the created <see + /// cref="T:System.Threading.Tasks.Task{TResult}"/>.</typeparam> + /// <param name="tasks">The array of tasks from which to continue.</param> + /// <param name="continuationFunction">The function delegate to execute when all tasks in the + /// <paramref name="tasks"/> array have completed.</param> + /// <param name="cancellationToken">The <see cref="System.Threading.CancellationToken">CancellationToken</see> + /// that will be assigned to the new continuation task.</param> + /// <returns>The new continuation <see cref="T:System.Threading.Tasks.Task{TResult}"/>.</returns> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the + /// <paramref name="tasks"/> array is null.</exception> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the + /// <paramref name="continuationFunction"/> argument is null.</exception> + /// <exception cref="T:System.ArgumentException">The exception that is thrown when the + /// <paramref name="tasks"/> array contains a null value.</exception> + /// <exception cref="T:System.ArgumentException">The exception that is thrown when the + /// <paramref name="tasks"/> array is empty.</exception> + /// <exception cref="T:System.ObjectDisposedException">The provided <see cref="System.Threading.CancellationToken">CancellationToken</see> + /// has already been disposed. + /// </exception> + [MethodImplAttribute(MethodImplOptions.NoInlining)] // Methods containing StackCrawlMark local var have to be marked non-inlineable + public Task<TResult> ContinueWhenAll<TResult>(Task[] tasks, Func<Task[], TResult> continuationFunction, CancellationToken cancellationToken) + { + if (continuationFunction == null) throw new ArgumentNullException("continuationFunction"); + Contract.EndContractBlock(); + + StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller; + return TaskFactory<TResult>.ContinueWhenAllImpl(tasks, continuationFunction, null, m_defaultContinuationOptions, cancellationToken, DefaultScheduler, ref stackMark); + } + + /// <summary> + /// Creates a continuation <see cref="T:System.Threading.Tasks.Task{TResult}">Task</see> + /// that will be started upon the completion of a set of provided Tasks. + /// </summary> + /// <typeparam name="TResult">The type of the result that is returned by the <paramref + /// name="continuationFunction"/> + /// delegate and associated with the created <see + /// cref="T:System.Threading.Tasks.Task{TResult}"/>.</typeparam> + /// <param name="tasks">The array of tasks from which to continue.</param> + /// <param name="continuationFunction">The function delegate to execute when all tasks in the + /// <paramref name="tasks"/> array have completed.</param> + /// <param name="continuationOptions">The <see cref="System.Threading.Tasks.TaskContinuationOptions"> + /// TaskContinuationOptions</see> value that controls the behavior of + /// the created continuation <see cref="T:System.Threading.Tasks.Task{TResult}">Task</see>.</param> + /// <returns>The new continuation <see cref="T:System.Threading.Tasks.Task{TResult}"/>.</returns> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the + /// <paramref name="tasks"/> array is null.</exception> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the + /// <paramref name="continuationFunction"/> argument is null.</exception> + /// <exception cref="T:System.ArgumentException">The exception that is thrown when the + /// <paramref name="tasks"/> array contains a null value.</exception> + /// <exception cref="T:System.ArgumentException">The exception that is thrown when the + /// <paramref name="tasks"/> array is empty.</exception> + /// <exception cref="T:System.ArgumentOutOfRangeException">The exception that is thrown when the + /// <paramref name="continuationOptions"/> argument specifies an invalid TaskContinuationOptions + /// value.</exception> + /// <remarks> + /// The NotOn* and OnlyOn* <see cref="System.Threading.Tasks.TaskContinuationOptions">TaskContinuationOptions</see>, + /// which constrain for which <see cref="System.Threading.Tasks.TaskStatus">TaskStatus</see> states a continuation + /// will be executed, are illegal with ContinueWhenAll. + /// </remarks> + [MethodImplAttribute(MethodImplOptions.NoInlining)] // Methods containing StackCrawlMark local var have to be marked non-inlineable + public Task<TResult> ContinueWhenAll<TResult>(Task[] tasks, Func<Task[], TResult> continuationFunction, TaskContinuationOptions continuationOptions) + { + if (continuationFunction == null) throw new ArgumentNullException("continuationFunction"); + Contract.EndContractBlock(); + + StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller; + return TaskFactory<TResult>.ContinueWhenAllImpl(tasks, continuationFunction, null, continuationOptions, m_defaultCancellationToken, DefaultScheduler, ref stackMark); + } + + /// <summary> + /// Creates a continuation <see cref="T:System.Threading.Tasks.Task{TResult}">Task</see> + /// that will be started upon the completion of a set of provided Tasks. + /// </summary> + /// <typeparam name="TResult">The type of the result that is returned by the <paramref + /// name="continuationFunction"/> + /// delegate and associated with the created <see + /// cref="T:System.Threading.Tasks.Task{TResult}"/>.</typeparam> + /// <param name="tasks">The array of tasks from which to continue.</param> + /// <param name="continuationFunction">The function delegate to execute when all tasks in the + /// <paramref name="tasks"/> array have completed.</param> + /// <param name="cancellationToken">The <see cref="System.Threading.CancellationToken">CancellationToken</see> + /// that will be assigned to the new continuation task.</param> + /// <param name="continuationOptions">The <see cref="System.Threading.Tasks.TaskContinuationOptions"> + /// TaskContinuationOptions</see> value that controls the behavior of + /// the created continuation <see cref="T:System.Threading.Tasks.Task{TResult}">Task</see>.</param> + /// <param name="scheduler">The <see cref="System.Threading.Tasks.TaskScheduler">TaskScheduler</see> + /// that is used to schedule the created continuation <see + /// cref="T:System.Threading.Tasks.Task{TResult}"/>.</param> + /// <returns>The new continuation <see cref="T:System.Threading.Tasks.Task{TResult}"/>.</returns> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the + /// <paramref name="tasks"/> array is null.</exception> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the + /// <paramref name="continuationFunction"/> argument is null.</exception> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the + /// <paramref name="scheduler"/> argument is null.</exception> + /// <exception cref="T:System.ArgumentException">The exception that is thrown when the + /// <paramref name="tasks"/> array contains a null value.</exception> + /// <exception cref="T:System.ArgumentException">The exception that is thrown when the + /// <paramref name="tasks"/> array is empty.</exception> + /// <exception cref="T:System.ArgumentOutOfRangeException">The exception that is thrown when the + /// <paramref name="continuationOptions"/> argument specifies an invalid TaskContinuationOptions + /// value.</exception> + /// <exception cref="T:System.ObjectDisposedException">The provided <see cref="System.Threading.CancellationToken">CancellationToken</see> + /// has already been disposed. + /// </exception> + /// <remarks> + /// The NotOn* and OnlyOn* <see cref="System.Threading.Tasks.TaskContinuationOptions">TaskContinuationOptions</see>, + /// which constrain for which <see cref="System.Threading.Tasks.TaskStatus">TaskStatus</see> states a continuation + /// will be executed, are illegal with ContinueWhenAll. + /// </remarks> + [MethodImplAttribute(MethodImplOptions.NoInlining)] // Methods containing StackCrawlMark local var have to be marked non-inlineable + public Task<TResult> ContinueWhenAll<TResult>(Task[] tasks, Func<Task[], TResult> continuationFunction, CancellationToken cancellationToken, + TaskContinuationOptions continuationOptions, TaskScheduler scheduler) + { + if (continuationFunction == null) throw new ArgumentNullException("continuationFunction"); + Contract.EndContractBlock(); + + StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller; + return TaskFactory<TResult>.ContinueWhenAllImpl(tasks, continuationFunction, null, continuationOptions, cancellationToken, scheduler, ref stackMark); + } + + + /// <summary> + /// Creates a continuation <see cref="T:System.Threading.Tasks.Task{TResult}">Task</see> + /// that will be started upon the completion of a set of provided Tasks. + /// </summary> + /// <typeparam name="TResult">The type of the result that is returned by the <paramref + /// name="continuationFunction"/> + /// delegate and associated with the created <see + /// cref="T:System.Threading.Tasks.Task{TResult}"/>.</typeparam> + /// <typeparam name="TAntecedentResult">The type of the result of the antecedent <paramref name="tasks"/>.</typeparam> + /// <param name="tasks">The array of tasks from which to continue.</param> + /// <param name="continuationFunction">The function delegate to execute when all tasks in the + /// <paramref name="tasks"/> array have completed.</param> + /// <returns>The new continuation <see cref="T:System.Threading.Tasks.Task{TResult}"/>.</returns> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the + /// <paramref name="tasks"/> array is null.</exception> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the + /// <paramref name="continuationFunction"/> argument is null.</exception> + /// <exception cref="T:System.ArgumentException">The exception that is thrown when the + /// <paramref name="tasks"/> array contains a null value.</exception> + /// <exception cref="T:System.ArgumentException">The exception that is thrown when the + /// <paramref name="tasks"/> array is empty.</exception> + [MethodImplAttribute(MethodImplOptions.NoInlining)] // Methods containing StackCrawlMark local var have to be marked non-inlineable + public Task<TResult> ContinueWhenAll<TAntecedentResult, TResult>(Task<TAntecedentResult>[] tasks, Func<Task<TAntecedentResult>[], TResult> continuationFunction) + { + if (continuationFunction == null) throw new ArgumentNullException("continuationFunction"); + Contract.EndContractBlock(); + + StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller; + return TaskFactory<TResult>.ContinueWhenAllImpl<TAntecedentResult>(tasks, continuationFunction, null, m_defaultContinuationOptions, m_defaultCancellationToken, DefaultScheduler, ref stackMark); + } + + /// <summary> + /// Creates a continuation <see cref="T:System.Threading.Tasks.Task{TResult}">Task</see> + /// that will be started upon the completion of a set of provided Tasks. + /// </summary> + /// <typeparam name="TResult">The type of the result that is returned by the <paramref + /// name="continuationFunction"/> + /// delegate and associated with the created <see + /// cref="T:System.Threading.Tasks.Task{TResult}"/>.</typeparam> + /// <typeparam name="TAntecedentResult">The type of the result of the antecedent <paramref name="tasks"/>.</typeparam> + /// <param name="tasks">The array of tasks from which to continue.</param> + /// <param name="continuationFunction">The function delegate to execute when all tasks in the + /// <paramref name="tasks"/> array have completed.</param> + /// <param name="cancellationToken">The <see cref="System.Threading.CancellationToken">CancellationToken</see> + /// that will be assigned to the new continuation task.</param> + /// <returns>The new continuation <see cref="T:System.Threading.Tasks.Task{TResult}"/>.</returns> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the + /// <paramref name="tasks"/> array is null.</exception> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the + /// <paramref name="continuationFunction"/> argument is null.</exception> + /// <exception cref="T:System.ArgumentException">The exception that is thrown when the + /// <paramref name="tasks"/> array contains a null value.</exception> + /// <exception cref="T:System.ArgumentException">The exception that is thrown when the + /// <paramref name="tasks"/> array is empty.</exception> + /// <exception cref="T:System.ObjectDisposedException">The provided <see cref="System.Threading.CancellationToken">CancellationToken</see> + /// has already been disposed. + /// </exception> + [MethodImplAttribute(MethodImplOptions.NoInlining)] // Methods containing StackCrawlMark local var have to be marked non-inlineable + public Task<TResult> ContinueWhenAll<TAntecedentResult, TResult>(Task<TAntecedentResult>[] tasks, Func<Task<TAntecedentResult>[], TResult> continuationFunction, + CancellationToken cancellationToken) + { + if (continuationFunction == null) throw new ArgumentNullException("continuationFunction"); + Contract.EndContractBlock(); + + StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller; + return TaskFactory<TResult>.ContinueWhenAllImpl<TAntecedentResult>(tasks, continuationFunction, null, m_defaultContinuationOptions, cancellationToken, DefaultScheduler, ref stackMark); + } + + /// <summary> + /// Creates a continuation <see cref="T:System.Threading.Tasks.Task{TResult}">Task</see> + /// that will be started upon the completion of a set of provided Tasks. + /// </summary> + /// <typeparam name="TResult">The type of the result that is returned by the <paramref + /// name="continuationFunction"/> + /// delegate and associated with the created <see + /// cref="T:System.Threading.Tasks.Task{TResult}"/>.</typeparam> + /// <typeparam name="TAntecedentResult">The type of the result of the antecedent <paramref name="tasks"/>.</typeparam> + /// <param name="tasks">The array of tasks from which to continue.</param> + /// <param name="continuationFunction">The function delegate to execute when all tasks in the + /// <paramref name="tasks"/> array have completed.</param> + /// <param name="continuationOptions">The <see cref="System.Threading.Tasks.TaskContinuationOptions"> + /// TaskContinuationOptions</see> value that controls the behavior of + /// the created continuation <see cref="T:System.Threading.Tasks.Task{TResult}">Task</see>.</param> + /// <returns>The new continuation <see cref="T:System.Threading.Tasks.Task{TResult}"/>.</returns> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the + /// <paramref name="tasks"/> array is null.</exception> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the + /// <paramref name="continuationFunction"/> argument is null.</exception> + /// <exception cref="T:System.ArgumentException">The exception that is thrown when the + /// <paramref name="tasks"/> array contains a null value.</exception> + /// <exception cref="T:System.ArgumentException">The exception that is thrown when the + /// <paramref name="tasks"/> array is empty.</exception> + /// <exception cref="T:System.ArgumentOutOfRangeException">The exception that is thrown when the + /// <paramref name="continuationOptions"/> argument specifies an invalid TaskContinuationOptions + /// value.</exception> + /// <remarks> + /// The NotOn* and OnlyOn* <see cref="System.Threading.Tasks.TaskContinuationOptions">TaskContinuationOptions</see>, + /// which constrain for which <see cref="System.Threading.Tasks.TaskStatus">TaskStatus</see> states a continuation + /// will be executed, are illegal with ContinueWhenAll. + /// </remarks> + [MethodImplAttribute(MethodImplOptions.NoInlining)] // Methods containing StackCrawlMark local var have to be marked non-inlineable + public Task<TResult> ContinueWhenAll<TAntecedentResult, TResult>(Task<TAntecedentResult>[] tasks, Func<Task<TAntecedentResult>[], TResult> continuationFunction, + TaskContinuationOptions continuationOptions) + { + if (continuationFunction == null) throw new ArgumentNullException("continuationFunction"); + Contract.EndContractBlock(); + + StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller; + return TaskFactory<TResult>.ContinueWhenAllImpl<TAntecedentResult>(tasks, continuationFunction, null, continuationOptions, m_defaultCancellationToken, DefaultScheduler, ref stackMark); + } + + /// <summary> + /// Creates a continuation <see cref="T:System.Threading.Tasks.Task{TResult}">Task</see> + /// that will be started upon the completion of a set of provided Tasks. + /// </summary> + /// <typeparam name="TResult">The type of the result that is returned by the <paramref + /// name="continuationFunction"/> + /// delegate and associated with the created <see + /// cref="T:System.Threading.Tasks.Task{TResult}"/>.</typeparam> + /// <typeparam name="TAntecedentResult">The type of the result of the antecedent <paramref name="tasks"/>.</typeparam> + /// <param name="tasks">The array of tasks from which to continue.</param> + /// <param name="continuationFunction">The function delegate to execute when all tasks in the + /// <paramref name="tasks"/> array have completed.</param> + /// <param name="cancellationToken">The <see cref="System.Threading.CancellationToken">CancellationToken</see> + /// that will be assigned to the new continuation task.</param> + /// <param name="continuationOptions">The <see cref="System.Threading.Tasks.TaskContinuationOptions"> + /// TaskContinuationOptions</see> value that controls the behavior of + /// the created continuation <see cref="T:System.Threading.Tasks.Task{TResult}">Task</see>.</param> + /// <param name="scheduler">The <see cref="System.Threading.Tasks.TaskScheduler">TaskScheduler</see> + /// that is used to schedule the created continuation <see + /// cref="T:System.Threading.Tasks.Task{TResult}"/>.</param> + /// <returns>The new continuation <see cref="T:System.Threading.Tasks.Task{TResult}"/>.</returns> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the + /// <paramref name="tasks"/> array is null.</exception> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the + /// <paramref name="continuationFunction"/> argument is null.</exception> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the + /// <paramref name="scheduler"/> argument is null.</exception> + /// <exception cref="T:System.ArgumentException">The exception that is thrown when the + /// <paramref name="tasks"/> array contains a null value.</exception> + /// <exception cref="T:System.ArgumentException">The exception that is thrown when the + /// <paramref name="tasks"/> array is empty.</exception> + /// <exception cref="T:System.ArgumentOutOfRangeException">The exception that is thrown when the + /// <paramref name="continuationOptions"/> argument specifies an invalid TaskContinuationOptions + /// value.</exception> + /// <exception cref="T:System.ObjectDisposedException">The provided <see cref="System.Threading.CancellationToken">CancellationToken</see> + /// has already been disposed. + /// </exception> + /// <remarks> + /// The NotOn* and OnlyOn* <see cref="System.Threading.Tasks.TaskContinuationOptions">TaskContinuationOptions</see>, + /// which constrain for which <see cref="System.Threading.Tasks.TaskStatus">TaskStatus</see> states a continuation + /// will be executed, are illegal with ContinueWhenAll. + /// </remarks> + [MethodImplAttribute(MethodImplOptions.NoInlining)] // Methods containing StackCrawlMark local var have to be marked non-inlineable + public Task<TResult> ContinueWhenAll<TAntecedentResult, TResult>(Task<TAntecedentResult>[] tasks, Func<Task<TAntecedentResult>[], TResult> continuationFunction, + CancellationToken cancellationToken, TaskContinuationOptions continuationOptions, TaskScheduler scheduler) + { + if (continuationFunction == null) throw new ArgumentNullException("continuationFunction"); + Contract.EndContractBlock(); + + StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller; + return TaskFactory<TResult>.ContinueWhenAllImpl<TAntecedentResult>(tasks, continuationFunction, null, continuationOptions, cancellationToken, scheduler, ref stackMark); + } + + // + // ContinueWhenAny methods + // + + // A Task<Task> that will be completed the first time that Invoke is called. + // It allows us to replace this logic: + // Task<Task> promise = new Task<Task>(...); + // Action<Task> completionAction = delegate(Task completingTask) { promise.TrySetResult(completingTask); } + // for(int i=0; i<tasksCopy.Length; i++) tasksCopy[i].AddCompletionAction(completionAction); + // with this logic: + // CompletionOnInvokePromise promise = new CompletionOnInvokePromise(tasksCopy); + // for(int i=0; i<tasksCopy.Length; i++) tasksCopy[i].AddCompletionAction(promise); + // which saves a couple of allocations. + // + // Used in TaskFactory.CommonCWAnyLogic(), below. + internal sealed class CompleteOnInvokePromise : Task<Task>, ITaskCompletionAction + { + private IList<Task> _tasks; // must track this for cleanup + private int m_firstTaskAlreadyCompleted; + + public CompleteOnInvokePromise(IList<Task> tasks) : base() + { + Contract.Requires(tasks != null, "Expected non-null collection of tasks"); + _tasks = tasks; + + if (AsyncCausalityTracer.LoggingOn) + AsyncCausalityTracer.TraceOperationCreation(CausalityTraceLevel.Required, this.Id, "TaskFactory.ContinueWhenAny", 0); + + if (Task.s_asyncDebuggingEnabled) + { + AddToActiveTasks(this); + } + } + + public void Invoke(Task completingTask) + { + if (Interlocked.CompareExchange(ref m_firstTaskAlreadyCompleted, 1, 0) == 0) + { + if (AsyncCausalityTracer.LoggingOn) + { + AsyncCausalityTracer.TraceOperationRelation(CausalityTraceLevel.Important, this.Id, CausalityRelation.Choice); + AsyncCausalityTracer.TraceOperationCompletion(CausalityTraceLevel.Required, this.Id, AsyncCausalityStatus.Completed); + } + + if (Task.s_asyncDebuggingEnabled) + { + RemoveFromActiveTasks(this.Id); + } + + bool success = TrySetResult(completingTask); + Contract.Assert(success, "Only one task should have gotten to this point, and thus this must be successful."); + + // We need to remove continuations that may be left straggling on other tasks. + // Otherwise, repeated calls to WhenAny using the same task could leak actions. + // This may also help to avoided unnecessary invocations of this whenComplete delegate. + // Note that we may be attempting to remove a continuation from a task that hasn't had it + // added yet; while there's overhead there, the operation won't hurt anything. + var tasks = _tasks; + int numTasks = tasks.Count; + for (int i = 0; i < numTasks; i++) + { + var task = tasks[i]; + if (task != null && // if an element was erroneously nulled out concurrently, just skip it; worst case is we don't remove a continuation + !task.IsCompleted) task.RemoveContinuation(this); + } + _tasks = null; + + } + } + + public bool InvokeMayRunArbitraryCode { get { return true; } } + } + // Common ContinueWhenAny logic + // If the tasks list is not an array, it must be an internal defensive copy so that + // we don't need to be concerned about concurrent modifications to the list. If the task list + // is an array, it should be a defensive copy if this functionality is being used + // asynchronously (e.g. WhenAny) rather than synchronously (e.g. WaitAny). + internal static Task<Task> CommonCWAnyLogic(IList<Task> tasks) + { + Contract.Requires(tasks != null); + + // Create a promise task to be returned to the user + var promise = new CompleteOnInvokePromise(tasks); + + // At the completion of any of the tasks, complete the promise. + + bool checkArgsOnly = false; + int numTasks = tasks.Count; + for(int i=0; i<numTasks; i++) + { + var task = tasks[i]; + if (task == null) throw new ArgumentException(Environment.GetResourceString("Task_MultiTaskContinuation_NullTask"), "tasks"); + + if (checkArgsOnly) continue; + + // If the promise has already completed, don't bother with checking any more tasks. + if (promise.IsCompleted) + { + checkArgsOnly = true; + } + // If a task has already completed, complete the promise. + else if (task.IsCompleted) + { + promise.Invoke(task); + checkArgsOnly = true; + } + // Otherwise, add the completion action and keep going. + else task.AddCompletionAction(promise); + } + + return promise; + } + + + /// <summary> + /// Creates a continuation <see cref="T:System.Threading.Tasks.Task">Task</see> + /// that will be started upon the completion of any Task in the provided set. + /// </summary> + /// <param name="tasks">The array of tasks from which to continue when one task completes.</param> + /// <param name="continuationAction">The action delegate to execute when one task in the <paramref + /// name="tasks"/> array completes.</param> + /// <returns>The new continuation <see cref="T:System.Threading.Tasks.Task">Task</see>.</returns> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the + /// <paramref name="tasks"/> array is null.</exception> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the + /// <paramref name="continuationAction"/> argument is null.</exception> + /// <exception cref="T:System.ArgumentException">The exception that is thrown when the + /// <paramref name="tasks"/> array contains a null value.</exception> + /// <exception cref="T:System.ArgumentException">The exception that is thrown when the + /// <paramref name="tasks"/> array is empty.</exception> + [MethodImplAttribute(MethodImplOptions.NoInlining)] // Methods containing StackCrawlMark local var have to be marked non-inlineable + public Task ContinueWhenAny(Task[] tasks, Action<Task> continuationAction) + { + if (continuationAction == null) throw new ArgumentNullException("continuationAction"); + Contract.EndContractBlock(); + + StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller; + return TaskFactory<VoidTaskResult>.ContinueWhenAnyImpl(tasks, null, continuationAction, m_defaultContinuationOptions, m_defaultCancellationToken, DefaultScheduler, ref stackMark); + } + + /// <summary> + /// Creates a continuation <see cref="T:System.Threading.Tasks.Task">Task</see> + /// that will be started upon the completion of any Task in the provided set. + /// </summary> + /// <param name="tasks">The array of tasks from which to continue when one task completes.</param> + /// <param name="continuationAction">The action delegate to execute when one task in the <paramref + /// name="tasks"/> array completes.</param> + /// <param name="cancellationToken">The <see cref="System.Threading.CancellationToken">CancellationToken</see> + /// that will be assigned to the new continuation task.</param> + /// <returns>The new continuation <see cref="T:System.Threading.Tasks.Task">Task</see>.</returns> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the + /// <paramref name="tasks"/> array is null.</exception> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the + /// <paramref name="continuationAction"/> argument is null.</exception> + /// <exception cref="T:System.ArgumentException">The exception that is thrown when the + /// <paramref name="tasks"/> array contains a null value.</exception> + /// <exception cref="T:System.ArgumentException">The exception that is thrown when the + /// <paramref name="tasks"/> array is empty.</exception> + /// <exception cref="T:System.ObjectDisposedException">The provided <see cref="System.Threading.CancellationToken">CancellationToken</see> + /// has already been disposed. + /// </exception> + [MethodImplAttribute(MethodImplOptions.NoInlining)] // Methods containing StackCrawlMark local var have to be marked non-inlineable + public Task ContinueWhenAny(Task[] tasks, Action<Task> continuationAction, CancellationToken cancellationToken) + { + if (continuationAction == null) throw new ArgumentNullException("continuationAction"); + Contract.EndContractBlock(); + + StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller; + return TaskFactory<VoidTaskResult>.ContinueWhenAnyImpl(tasks, null, continuationAction, m_defaultContinuationOptions, cancellationToken, DefaultScheduler, ref stackMark); + } + + /// <summary> + /// Creates a continuation <see cref="T:System.Threading.Tasks.Task">Task</see> + /// that will be started upon the completion of any Task in the provided set. + /// </summary> + /// <param name="tasks">The array of tasks from which to continue when one task completes.</param> + /// <param name="continuationAction">The action delegate to execute when one task in the <paramref + /// name="tasks"/> array completes.</param> + /// <param name="continuationOptions">The <see cref="System.Threading.Tasks.TaskContinuationOptions"> + /// TaskContinuationOptions</see> value that controls the behavior of + /// the created continuation <see cref="T:System.Threading.Tasks.Task">Task</see>.</param> + /// <returns>The new continuation <see cref="T:System.Threading.Tasks.Task">Task</see>.</returns> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the + /// <paramref name="tasks"/> array is null.</exception> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the + /// <paramref name="continuationAction"/> argument is null.</exception> + /// <exception cref="T:System.ArgumentException">The exception that is thrown when the + /// <paramref name="tasks"/> array contains a null value.</exception> + /// <exception cref="T:System.ArgumentException">The exception that is thrown when the + /// <paramref name="tasks"/> array is empty.</exception> + /// <exception cref="T:System.ArgumentOutOfRangeException">The exception that is thrown when the + /// <paramref name="continuationOptions"/> argument specifies an invalid TaskContinuationOptions + /// value.</exception> + /// <remarks> + /// The NotOn* and OnlyOn* <see cref="System.Threading.Tasks.TaskContinuationOptions">TaskContinuationOptions</see>, + /// which constrain for which <see cref="System.Threading.Tasks.TaskStatus">TaskStatus</see> states a continuation + /// will be executed, are illegal with ContinueWhenAny. + /// </remarks> + [MethodImplAttribute(MethodImplOptions.NoInlining)] // Methods containing StackCrawlMark local var have to be marked non-inlineable + public Task ContinueWhenAny(Task[] tasks, Action<Task> continuationAction, TaskContinuationOptions continuationOptions) + { + if (continuationAction == null) throw new ArgumentNullException("continuationAction"); + Contract.EndContractBlock(); + + StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller; + return TaskFactory<VoidTaskResult>.ContinueWhenAnyImpl(tasks, null, continuationAction, continuationOptions, m_defaultCancellationToken, DefaultScheduler, ref stackMark); + } + + /// <summary> + /// Creates a continuation <see cref="T:System.Threading.Tasks.Task">Task</see> + /// that will be started upon the completion of any Task in the provided set. + /// </summary> + /// <param name="tasks">The array of tasks from which to continue when one task completes.</param> + /// <param name="continuationAction">The action delegate to execute when one task in the <paramref + /// name="tasks"/> array completes.</param> + /// <param name="cancellationToken">The <see cref="System.Threading.CancellationToken">CancellationToken</see> + /// that will be assigned to the new continuation task.</param> + /// <param name="continuationOptions">The <see cref="System.Threading.Tasks.TaskContinuationOptions"> + /// TaskContinuationOptions</see> value that controls the behavior of + /// the created continuation <see cref="T:System.Threading.Tasks.Task">Task</see>.</param> + /// <param name="scheduler">The <see cref="System.Threading.Tasks.TaskScheduler">TaskScheduler</see> + /// that is used to schedule the created continuation <see + /// cref="T:System.Threading.Tasks.Task">Task</see>.</param> + /// <returns>The new continuation <see cref="T:System.Threading.Tasks.Task">Task</see>.</returns> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the + /// <paramref name="tasks"/> array is null.</exception> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the + /// <paramref name="continuationAction"/> argument is null.</exception> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the + /// <paramref name="scheduler"/> argument is null.</exception> + /// <exception cref="T:System.ArgumentException">The exception that is thrown when the + /// <paramref name="tasks"/> array contains a null value.</exception> + /// <exception cref="T:System.ArgumentException">The exception that is thrown when the + /// <paramref name="tasks"/> array is empty.</exception> + /// <exception cref="T:System.ArgumentOutOfRangeException">The exception that is thrown when the + /// <paramref name="continuationOptions"/> argument specifies an invalid TaskContinuationOptions + /// value.</exception> + /// <exception cref="T:System.ObjectDisposedException">The provided <see cref="System.Threading.CancellationToken">CancellationToken</see> + /// has already been disposed. + /// </exception> + /// <remarks> + /// The NotOn* and OnlyOn* <see cref="System.Threading.Tasks.TaskContinuationOptions">TaskContinuationOptions</see>, + /// which constrain for which <see cref="System.Threading.Tasks.TaskStatus">TaskStatus</see> states a continuation + /// will be executed, are illegal with ContinueWhenAny. + /// </remarks> + [MethodImplAttribute(MethodImplOptions.NoInlining)] // Methods containing StackCrawlMark local var have to be marked non-inlineable + public Task ContinueWhenAny(Task[] tasks, Action<Task> continuationAction, CancellationToken cancellationToken, + TaskContinuationOptions continuationOptions, TaskScheduler scheduler) + { + if (continuationAction == null) throw new ArgumentNullException("continuationAction"); + Contract.EndContractBlock(); + + StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller; + return TaskFactory<VoidTaskResult>.ContinueWhenAnyImpl(tasks, null, continuationAction, continuationOptions, cancellationToken, scheduler, ref stackMark); + } + + + /// <summary> + /// Creates a continuation <see cref="T:System.Threading.Tasks.Task{TResult}">Task</see> + /// that will be started upon the completion of any Task in the provided set. + /// </summary> + /// <typeparam name="TResult">The type of the result that is returned by the <paramref + /// name="continuationFunction"/> + /// delegate and associated with the created <see + /// cref="T:System.Threading.Tasks.Task{TResult}"/>.</typeparam> + /// <param name="tasks">The array of tasks from which to continue when one task completes.</param> + /// <param name="continuationFunction">The function delegate to execute when one task in the + /// <paramref name="tasks"/> array completes.</param> + /// <returns>The new continuation <see cref="T:System.Threading.Tasks.Task{TResult}"/>.</returns> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the + /// <paramref name="tasks"/> array is null.</exception> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the + /// <paramref name="continuationFunction"/> argument is null.</exception> + /// <exception cref="T:System.ArgumentException">The exception that is thrown when the + /// <paramref name="tasks"/> array contains a null value.</exception> + /// <exception cref="T:System.ArgumentException">The exception that is thrown when the + /// <paramref name="tasks"/> array is empty.</exception> + [MethodImplAttribute(MethodImplOptions.NoInlining)] // Methods containing StackCrawlMark local var have to be marked non-inlineable + public Task<TResult> ContinueWhenAny<TResult>(Task[] tasks, Func<Task, TResult> continuationFunction) + { + if (continuationFunction == null) throw new ArgumentNullException("continuationFunction"); + Contract.EndContractBlock(); + + StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller; + return TaskFactory<TResult>.ContinueWhenAnyImpl(tasks, continuationFunction, null, m_defaultContinuationOptions, m_defaultCancellationToken, DefaultScheduler, ref stackMark); + } + + /// <summary> + /// Creates a continuation <see cref="T:System.Threading.Tasks.Task{TResult}">Task</see> + /// that will be started upon the completion of any Task in the provided set. + /// </summary> + /// <typeparam name="TResult">The type of the result that is returned by the <paramref + /// name="continuationFunction"/> + /// delegate and associated with the created <see + /// cref="T:System.Threading.Tasks.Task{TResult}"/>.</typeparam> + /// <param name="tasks">The array of tasks from which to continue when one task completes.</param> + /// <param name="continuationFunction">The function delegate to execute when one task in the + /// <paramref name="tasks"/> array completes.</param> + /// <param name="cancellationToken">The <see cref="System.Threading.CancellationToken">CancellationToken</see> + /// that will be assigned to the new continuation task.</param> + /// <returns>The new continuation <see cref="T:System.Threading.Tasks.Task{TResult}"/>.</returns> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the + /// <paramref name="tasks"/> array is null.</exception> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the + /// <paramref name="continuationFunction"/> argument is null.</exception> + /// <exception cref="T:System.ArgumentException">The exception that is thrown when the + /// <paramref name="tasks"/> array contains a null value.</exception> + /// <exception cref="T:System.ArgumentException">The exception that is thrown when the + /// <paramref name="tasks"/> array is empty.</exception> + /// <exception cref="T:System.ObjectDisposedException">The provided <see cref="System.Threading.CancellationToken">CancellationToken</see> + /// has already been disposed. + /// </exception> + [MethodImplAttribute(MethodImplOptions.NoInlining)] // Methods containing StackCrawlMark local var have to be marked non-inlineable + public Task<TResult> ContinueWhenAny<TResult>(Task[] tasks, Func<Task, TResult> continuationFunction, CancellationToken cancellationToken) + { + if (continuationFunction == null) throw new ArgumentNullException("continuationFunction"); + Contract.EndContractBlock(); + + StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller; + return TaskFactory<TResult>.ContinueWhenAnyImpl(tasks, continuationFunction, null, m_defaultContinuationOptions, cancellationToken, DefaultScheduler, ref stackMark); + } + + /// <summary> + /// Creates a continuation <see cref="T:System.Threading.Tasks.Task{TResult}">Task</see> + /// that will be started upon the completion of any Task in the provided set. + /// </summary> + /// <typeparam name="TResult">The type of the result that is returned by the <paramref + /// name="continuationFunction"/> + /// delegate and associated with the created <see + /// cref="T:System.Threading.Tasks.Task{TResult}"/>.</typeparam> + /// <param name="tasks">The array of tasks from which to continue when one task completes.</param> + /// <param name="continuationFunction">The function delegate to execute when one task in the + /// <paramref name="tasks"/> array completes.</param> + /// <param name="continuationOptions">The <see cref="System.Threading.Tasks.TaskContinuationOptions"> + /// TaskContinuationOptions</see> value that controls the behavior of + /// the created continuation <see cref="T:System.Threading.Tasks.Task{TResult}">Task</see>.</param> + /// <returns>The new continuation <see cref="T:System.Threading.Tasks.Task{TResult}"/>.</returns> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the + /// <paramref name="tasks"/> array is null.</exception> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the + /// <paramref name="continuationFunction"/> argument is null.</exception> + /// <exception cref="T:System.ArgumentException">The exception that is thrown when the + /// <paramref name="tasks"/> array contains a null value.</exception> + /// <exception cref="T:System.ArgumentException">The exception that is thrown when the + /// <paramref name="tasks"/> array is empty.</exception> + /// <exception cref="T:System.ArgumentOutOfRangeException">The exception that is thrown when the + /// <paramref name="continuationOptions"/> argument specifies an invalid TaskContinuationOptions + /// value.</exception> + /// <remarks> + /// The NotOn* and OnlyOn* <see cref="System.Threading.Tasks.TaskContinuationOptions">TaskContinuationOptions</see>, + /// which constrain for which <see cref="System.Threading.Tasks.TaskStatus">TaskStatus</see> states a continuation + /// will be executed, are illegal with ContinueWhenAny. + /// </remarks> + [MethodImplAttribute(MethodImplOptions.NoInlining)] // Methods containing StackCrawlMark local var have to be marked non-inlineable + public Task<TResult> ContinueWhenAny<TResult>(Task[] tasks, Func<Task, TResult> continuationFunction, TaskContinuationOptions continuationOptions) + { + if (continuationFunction == null) throw new ArgumentNullException("continuationFunction"); + Contract.EndContractBlock(); + + StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller; + return TaskFactory<TResult>.ContinueWhenAnyImpl(tasks, continuationFunction, null,continuationOptions, m_defaultCancellationToken, DefaultScheduler, ref stackMark); + } + + /// <summary> + /// Creates a continuation <see cref="T:System.Threading.Tasks.Task{TResult}">Task</see> + /// that will be started upon the completion of any Task in the provided set. + /// </summary> + /// <typeparam name="TResult">The type of the result that is returned by the <paramref + /// name="continuationFunction"/> + /// delegate and associated with the created <see + /// cref="T:System.Threading.Tasks.Task{TResult}"/>.</typeparam> + /// <param name="tasks">The array of tasks from which to continue when one task completes.</param> + /// <param name="continuationFunction">The function delegate to execute when one task in the + /// <paramref name="tasks"/> array completes.</param> + /// <param name="cancellationToken">The <see cref="System.Threading.CancellationToken">CancellationToken</see> + /// that will be assigned to the new continuation task.</param> + /// <param name="continuationOptions">The <see cref="System.Threading.Tasks.TaskContinuationOptions"> + /// TaskContinuationOptions</see> value that controls the behavior of + /// the created continuation <see cref="T:System.Threading.Tasks.Task{TResult}">Task</see>.</param> + /// <param name="scheduler">The <see cref="System.Threading.Tasks.TaskScheduler">TaskScheduler</see> + /// that is used to schedule the created continuation <see + /// cref="T:System.Threading.Tasks.Task{TResult}"/>.</param> + /// <returns>The new continuation <see cref="T:System.Threading.Tasks.Task{TResult}"/>.</returns> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the + /// <paramref name="tasks"/> array is null.</exception> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the + /// <paramref name="continuationFunction"/> argument is null.</exception> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the + /// <paramref name="scheduler"/> argument is null.</exception> + /// <exception cref="T:System.ArgumentException">The exception that is thrown when the + /// <paramref name="tasks"/> array contains a null value.</exception> + /// <exception cref="T:System.ArgumentException">The exception that is thrown when the + /// <paramref name="tasks"/> array is empty.</exception> + /// <exception cref="T:System.ArgumentOutOfRangeException">The exception that is thrown when the + /// <paramref name="continuationOptions"/> argument specifies an invalid TaskContinuationOptions + /// value.</exception> + /// <exception cref="T:System.ObjectDisposedException">The provided <see cref="System.Threading.CancellationToken">CancellationToken</see> + /// has already been disposed. + /// </exception> + /// <remarks> + /// The NotOn* and OnlyOn* <see cref="System.Threading.Tasks.TaskContinuationOptions">TaskContinuationOptions</see>, + /// which constrain for which <see cref="System.Threading.Tasks.TaskStatus">TaskStatus</see> states a continuation + /// will be executed, are illegal with ContinueWhenAny. + /// </remarks> + [MethodImplAttribute(MethodImplOptions.NoInlining)] // Methods containing StackCrawlMark local var have to be marked non-inlineable + public Task<TResult> ContinueWhenAny<TResult>(Task[] tasks, Func<Task, TResult> continuationFunction, CancellationToken cancellationToken, + TaskContinuationOptions continuationOptions, TaskScheduler scheduler) + { + if (continuationFunction == null) throw new ArgumentNullException("continuationFunction"); + Contract.EndContractBlock(); + + StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller; + return TaskFactory<TResult>.ContinueWhenAnyImpl(tasks, continuationFunction, null, continuationOptions, cancellationToken, scheduler, ref stackMark); + } + + /// <summary> + /// Creates a continuation <see cref="T:System.Threading.Tasks.Task{TResult}">Task</see> + /// that will be started upon the completion of any Task in the provided set. + /// </summary> + /// <typeparam name="TResult">The type of the result that is returned by the <paramref + /// name="continuationFunction"/> + /// delegate and associated with the created <see + /// cref="T:System.Threading.Tasks.Task{TResult}"/>.</typeparam> + /// <typeparam name="TAntecedentResult">The type of the result of the antecedent <paramref name="tasks"/>.</typeparam> + /// <param name="tasks">The array of tasks from which to continue when one task completes.</param> + /// <param name="continuationFunction">The function delegate to execute when one task in the + /// <paramref name="tasks"/> array completes.</param> + /// <returns>The new continuation <see cref="T:System.Threading.Tasks.Task{TResult}"/>.</returns> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the + /// <paramref name="tasks"/> array is null.</exception> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the + /// <paramref name="continuationFunction"/> argument is null.</exception> + /// <exception cref="T:System.ArgumentException">The exception that is thrown when the + /// <paramref name="tasks"/> array contains a null value.</exception> + /// <exception cref="T:System.ArgumentException">The exception that is thrown when the + /// <paramref name="tasks"/> array is empty.</exception> + [MethodImplAttribute(MethodImplOptions.NoInlining)] // Methods containing StackCrawlMark local var have to be marked non-inlineable + public Task<TResult> ContinueWhenAny<TAntecedentResult, TResult>(Task<TAntecedentResult>[] tasks, Func<Task<TAntecedentResult>, TResult> continuationFunction) + { + StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller; + if (continuationFunction == null) throw new ArgumentNullException("continuationFunction"); + return TaskFactory<TResult>.ContinueWhenAnyImpl<TAntecedentResult>(tasks, continuationFunction, null, m_defaultContinuationOptions, m_defaultCancellationToken, DefaultScheduler, ref stackMark); + } + + /// <summary> + /// Creates a continuation <see cref="T:System.Threading.Tasks.Task{TResult}">Task</see> + /// that will be started upon the completion of any Task in the provided set. + /// </summary> + /// <typeparam name="TResult">The type of the result that is returned by the <paramref + /// name="continuationFunction"/> + /// delegate and associated with the created <see + /// cref="T:System.Threading.Tasks.Task{TResult}"/>.</typeparam> + /// <typeparam name="TAntecedentResult">The type of the result of the antecedent <paramref name="tasks"/>.</typeparam> + /// <param name="tasks">The array of tasks from which to continue when one task completes.</param> + /// <param name="continuationFunction">The function delegate to execute when one task in the + /// <paramref name="tasks"/> array completes.</param> + /// <param name="cancellationToken">The <see cref="System.Threading.CancellationToken">CancellationToken</see> + /// that will be assigned to the new continuation task.</param> + /// <returns>The new continuation <see cref="T:System.Threading.Tasks.Task{TResult}"/>.</returns> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the + /// <paramref name="tasks"/> array is null.</exception> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the + /// <paramref name="continuationFunction"/> argument is null.</exception> + /// <exception cref="T:System.ArgumentException">The exception that is thrown when the + /// <paramref name="tasks"/> array contains a null value.</exception> + /// <exception cref="T:System.ArgumentException">The exception that is thrown when the + /// <paramref name="tasks"/> array is empty.</exception> + /// <exception cref="T:System.ObjectDisposedException">The provided <see cref="System.Threading.CancellationToken">CancellationToken</see> + /// has already been disposed. + /// </exception> + [MethodImplAttribute(MethodImplOptions.NoInlining)] // Methods containing StackCrawlMark local var have to be marked non-inlineable + public Task<TResult> ContinueWhenAny<TAntecedentResult, TResult>(Task<TAntecedentResult>[] tasks, Func<Task<TAntecedentResult>, TResult> continuationFunction, + CancellationToken cancellationToken) + { + if (continuationFunction == null) throw new ArgumentNullException("continuationFunction"); + Contract.EndContractBlock(); + + StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller; + return TaskFactory<TResult>.ContinueWhenAnyImpl<TAntecedentResult>(tasks, continuationFunction, null, m_defaultContinuationOptions, cancellationToken, DefaultScheduler, ref stackMark); + } + + /// <summary> + /// Creates a continuation <see cref="T:System.Threading.Tasks.Task{TResult}">Task</see> + /// that will be started upon the completion of any Task in the provided set. + /// </summary> + /// <typeparam name="TResult">The type of the result that is returned by the <paramref + /// name="continuationFunction"/> + /// delegate and associated with the created <see + /// cref="T:System.Threading.Tasks.Task{TResult}"/>.</typeparam> + /// <typeparam name="TAntecedentResult">The type of the result of the antecedent <paramref name="tasks"/>.</typeparam> + /// <param name="tasks">The array of tasks from which to continue when one task completes.</param> + /// <param name="continuationFunction">The function delegate to execute when one task in the + /// <paramref name="tasks"/> array completes.</param> + /// <param name="continuationOptions">The <see cref="System.Threading.Tasks.TaskContinuationOptions"> + /// TaskContinuationOptions</see> value that controls the behavior of + /// the created continuation <see cref="T:System.Threading.Tasks.Task{TResult}">Task</see>.</param> + /// <returns>The new continuation <see cref="T:System.Threading.Tasks.Task{TResult}"/>.</returns> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the + /// <paramref name="tasks"/> array is null.</exception> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the + /// <paramref name="continuationFunction"/> argument is null.</exception> + /// <exception cref="T:System.ArgumentException">The exception that is thrown when the + /// <paramref name="tasks"/> array contains a null value.</exception> + /// <exception cref="T:System.ArgumentException">The exception that is thrown when the + /// <paramref name="tasks"/> array is empty.</exception> + /// <exception cref="T:System.ArgumentOutOfRangeException">The exception that is thrown when the + /// <paramref name="continuationOptions"/> argument specifies an invalid TaskContinuationOptions + /// value.</exception> + /// <remarks> + /// The NotOn* and OnlyOn* <see cref="System.Threading.Tasks.TaskContinuationOptions">TaskContinuationOptions</see>, + /// which constrain for which <see cref="System.Threading.Tasks.TaskStatus">TaskStatus</see> states a continuation + /// will be executed, are illegal with ContinueWhenAny. + /// </remarks> + [MethodImplAttribute(MethodImplOptions.NoInlining)] // Methods containing StackCrawlMark local var have to be marked non-inlineable + public Task<TResult> ContinueWhenAny<TAntecedentResult, TResult>(Task<TAntecedentResult>[] tasks, Func<Task<TAntecedentResult>, TResult> continuationFunction, + TaskContinuationOptions continuationOptions) + { + if (continuationFunction == null) throw new ArgumentNullException("continuationFunction"); + Contract.EndContractBlock(); + + StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller; + return TaskFactory<TResult>.ContinueWhenAnyImpl<TAntecedentResult>(tasks, continuationFunction, null, continuationOptions, m_defaultCancellationToken, DefaultScheduler, ref stackMark); + } + + /// <summary> + /// Creates a continuation <see cref="T:System.Threading.Tasks.Task{TResult}">Task</see> + /// that will be started upon the completion of any Task in the provided set. + /// </summary> + /// <typeparam name="TResult">The type of the result that is returned by the <paramref + /// name="continuationFunction"/> + /// delegate and associated with the created <see + /// cref="T:System.Threading.Tasks.Task{TResult}"/>.</typeparam> + /// <typeparam name="TAntecedentResult">The type of the result of the antecedent <paramref name="tasks"/>.</typeparam> + /// <param name="tasks">The array of tasks from which to continue when one task completes.</param> + /// <param name="continuationFunction">The function delegate to execute when one task in the + /// <paramref name="tasks"/> array completes.</param> + /// <param name="cancellationToken">The <see cref="System.Threading.CancellationToken">CancellationToken</see> + /// that will be assigned to the new continuation task.</param> + /// <param name="continuationOptions">The <see cref="System.Threading.Tasks.TaskContinuationOptions"> + /// TaskContinuationOptions</see> value that controls the behavior of + /// the created continuation <see cref="T:System.Threading.Tasks.Task{TResult}">Task</see>.</param> + /// <param name="scheduler">The <see cref="System.Threading.Tasks.TaskScheduler">TaskScheduler</see> + /// that is used to schedule the created continuation <see + /// cref="T:System.Threading.Tasks.Task{TResult}"/>.</param> + /// <returns>The new continuation <see cref="T:System.Threading.Tasks.Task{TResult}"/>.</returns> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the + /// <paramref name="tasks"/> array is null.</exception> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the + /// <paramref name="continuationFunction"/> argument is null.</exception> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the + /// <paramref name="scheduler"/> argument is null.</exception> + /// <exception cref="T:System.ArgumentException">The exception that is thrown when the + /// <paramref name="tasks"/> array contains a null value.</exception> + /// <exception cref="T:System.ArgumentException">The exception that is thrown when the + /// <paramref name="tasks"/> array is empty.</exception> + /// <exception cref="T:System.ArgumentOutOfRangeException">The exception that is thrown when the + /// <paramref name="continuationOptions"/> argument specifies an invalid TaskContinuationOptions + /// value.</exception> + /// <exception cref="T:System.ObjectDisposedException">The provided <see cref="System.Threading.CancellationToken">CancellationToken</see> + /// has already been disposed. + /// </exception> + /// <remarks> + /// The NotOn* and OnlyOn* <see cref="System.Threading.Tasks.TaskContinuationOptions">TaskContinuationOptions</see>, + /// which constrain for which <see cref="System.Threading.Tasks.TaskStatus">TaskStatus</see> states a continuation + /// will be executed, are illegal with ContinueWhenAny. + /// </remarks> + [MethodImplAttribute(MethodImplOptions.NoInlining)] // Methods containing StackCrawlMark local var have to be marked non-inlineable + public Task<TResult> ContinueWhenAny<TAntecedentResult, TResult>(Task<TAntecedentResult>[] tasks, Func<Task<TAntecedentResult>, TResult> continuationFunction, + CancellationToken cancellationToken, TaskContinuationOptions continuationOptions, TaskScheduler scheduler) + { + if (continuationFunction == null) throw new ArgumentNullException("continuationFunction"); + Contract.EndContractBlock(); + + StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller; + return TaskFactory<TResult>.ContinueWhenAnyImpl<TAntecedentResult>(tasks, continuationFunction, null, continuationOptions, cancellationToken, scheduler, ref stackMark); + } + + + /// <summary> + /// Creates a continuation <see cref="T:System.Threading.Tasks.Task">Task</see> + /// that will be started upon the completion of any Task in the provided set. + /// </summary> + /// <typeparam name="TAntecedentResult">The type of the result of the antecedent <paramref name="tasks"/>.</typeparam> + /// <param name="tasks">The array of tasks from which to continue when one task completes.</param> + /// <param name="continuationAction">The action delegate to execute when one task in the + /// <paramref name="tasks"/> array completes.</param> + /// <returns>The new continuation <see cref="T:System.Threading.Tasks.Task"/>.</returns> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the + /// <paramref name="tasks"/> array is null.</exception> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the + /// <paramref name="continuationAction"/> argument is null.</exception> + /// <exception cref="T:System.ArgumentException">The exception that is thrown when the + /// <paramref name="tasks"/> array contains a null value.</exception> + /// <exception cref="T:System.ArgumentException">The exception that is thrown when the + /// <paramref name="tasks"/> array is empty.</exception> + [MethodImplAttribute(MethodImplOptions.NoInlining)] // Methods containing StackCrawlMark local var have to be marked non-inlineable + public Task ContinueWhenAny<TAntecedentResult>(Task<TAntecedentResult>[] tasks, Action<Task<TAntecedentResult>> continuationAction) + { + if (continuationAction == null) throw new ArgumentNullException("continuationAction"); + Contract.EndContractBlock(); + + StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller; + return TaskFactory<VoidTaskResult>.ContinueWhenAnyImpl<TAntecedentResult>(tasks, null, continuationAction, m_defaultContinuationOptions, m_defaultCancellationToken, DefaultScheduler, ref stackMark); + } + + /// <summary> + /// Creates a continuation <see cref="T:System.Threading.Tasks.Task">Task</see> + /// that will be started upon the completion of any Task in the provided set. + /// </summary> + /// <typeparam name="TAntecedentResult">The type of the result of the antecedent <paramref name="tasks"/>.</typeparam> + /// <param name="tasks">The array of tasks from which to continue when one task completes.</param> + /// <param name="continuationAction">The action delegate to execute when one task in the + /// <paramref name="tasks"/> array completes.</param> + /// <param name="cancellationToken">The <see cref="System.Threading.CancellationToken">CancellationToken</see> + /// that will be assigned to the new continuation task.</param> + /// <returns>The new continuation <see cref="T:System.Threading.Tasks.Task"/>.</returns> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the + /// <paramref name="tasks"/> array is null.</exception> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the + /// <paramref name="continuationAction"/> argument is null.</exception> + /// <exception cref="T:System.ArgumentException">The exception that is thrown when the + /// <paramref name="tasks"/> array contains a null value.</exception> + /// <exception cref="T:System.ArgumentException">The exception that is thrown when the + /// <paramref name="tasks"/> array is empty.</exception> + /// <exception cref="T:System.ObjectDisposedException">The provided <see cref="System.Threading.CancellationToken">CancellationToken</see> + /// has already been disposed. + /// </exception> + [MethodImplAttribute(MethodImplOptions.NoInlining)] // Methods containing StackCrawlMark local var have to be marked non-inlineable + public Task ContinueWhenAny<TAntecedentResult>(Task<TAntecedentResult>[] tasks, Action<Task<TAntecedentResult>> continuationAction, + CancellationToken cancellationToken) + { + if (continuationAction == null) throw new ArgumentNullException("continuationAction"); + Contract.EndContractBlock(); + + StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller; + return TaskFactory<VoidTaskResult>.ContinueWhenAnyImpl<TAntecedentResult>(tasks, null, continuationAction, m_defaultContinuationOptions, cancellationToken, DefaultScheduler, ref stackMark); + } + + /// <summary> + /// Creates a continuation <see cref="T:System.Threading.Tasks.Task">Task</see> + /// that will be started upon the completion of any Task in the provided set. + /// </summary> + /// <typeparam name="TAntecedentResult">The type of the result of the antecedent <paramref name="tasks"/>.</typeparam> + /// <param name="tasks">The array of tasks from which to continue when one task completes.</param> + /// <param name="continuationAction">The action delegate to execute when one task in the + /// <paramref name="tasks"/> array completes.</param> + /// <param name="continuationOptions">The <see cref="System.Threading.Tasks.TaskContinuationOptions"> + /// TaskContinuationOptions</see> value that controls the behavior of + /// the created continuation <see cref="T:System.Threading.Tasks.Task">Task</see>.</param> + /// <returns>The new continuation <see cref="T:System.Threading.Tasks.Task"/>.</returns> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the + /// <paramref name="tasks"/> array is null.</exception> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the + /// <paramref name="continuationAction"/> argument is null.</exception> + /// <exception cref="T:System.ArgumentException">The exception that is thrown when the + /// <paramref name="tasks"/> array contains a null value.</exception> + /// <exception cref="T:System.ArgumentException">The exception that is thrown when the + /// <paramref name="tasks"/> array is empty.</exception> + /// <exception cref="T:System.ArgumentOutOfRangeException">The exception that is thrown when the + /// <paramref name="continuationOptions"/> argument specifies an invalid TaskContinuationOptions + /// value.</exception> + /// <remarks> + /// The NotOn* and OnlyOn* <see cref="System.Threading.Tasks.TaskContinuationOptions">TaskContinuationOptions</see>, + /// which constrain for which <see cref="System.Threading.Tasks.TaskStatus">TaskStatus</see> states a continuation + /// will be executed, are illegal with ContinueWhenAny. + /// </remarks> + [MethodImplAttribute(MethodImplOptions.NoInlining)] // Methods containing StackCrawlMark local var have to be marked non-inlineable + public Task ContinueWhenAny<TAntecedentResult>(Task<TAntecedentResult>[] tasks, Action<Task<TAntecedentResult>> continuationAction, + TaskContinuationOptions continuationOptions) + { + if (continuationAction == null) throw new ArgumentNullException("continuationAction"); + Contract.EndContractBlock(); + + StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller; + return TaskFactory<VoidTaskResult>.ContinueWhenAnyImpl<TAntecedentResult>(tasks, null, continuationAction, continuationOptions, m_defaultCancellationToken, DefaultScheduler, ref stackMark); + } + + /// <summary> + /// Creates a continuation <see cref="T:System.Threading.Tasks.Task">Task</see> + /// that will be started upon the completion of any Task in the provided set. + /// </summary> + /// <typeparam name="TAntecedentResult">The type of the result of the antecedent <paramref name="tasks"/>.</typeparam> + /// <param name="tasks">The array of tasks from which to continue when one task completes.</param> + /// <param name="continuationAction">The action delegate to execute when one task in the + /// <paramref name="tasks"/> array completes.</param> + /// <param name="cancellationToken">The <see cref="System.Threading.CancellationToken">CancellationToken</see> + /// that will be assigned to the new continuation task.</param> + /// <param name="continuationOptions">The <see cref="System.Threading.Tasks.TaskContinuationOptions"> + /// TaskContinuationOptions</see> value that controls the behavior of + /// the created continuation <see cref="T:System.Threading.Tasks.Task">Task</see>.</param> + /// <param name="scheduler">The <see cref="System.Threading.Tasks.TaskScheduler">TaskScheduler</see> + /// that is used to schedule the created continuation <see + /// cref="T:System.Threading.Tasks.Task{TResult}"/>.</param> + /// <returns>The new continuation <see cref="T:System.Threading.Tasks.Task"/>.</returns> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the + /// <paramref name="tasks"/> array is null.</exception> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the + /// <paramref name="continuationAction"/> argument is null.</exception> + /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the + /// <paramref name="scheduler"/> argument is null.</exception> + /// <exception cref="T:System.ArgumentException">The exception that is thrown when the + /// <paramref name="tasks"/> array contains a null value.</exception> + /// <exception cref="T:System.ArgumentException">The exception that is thrown when the + /// <paramref name="tasks"/> array is empty.</exception> + /// <exception cref="T:System.ArgumentOutOfRangeException">The exception that is thrown when the + /// <paramref name="continuationOptions"/> argument specifies an invalid TaskContinuationOptions + /// value.</exception> + /// <exception cref="T:System.ObjectDisposedException">The provided <see cref="System.Threading.CancellationToken">CancellationToken</see> + /// has already been disposed. + /// </exception> + /// <remarks> + /// The NotOn* and OnlyOn* <see cref="System.Threading.Tasks.TaskContinuationOptions">TaskContinuationOptions</see>, + /// which constrain for which <see cref="System.Threading.Tasks.TaskStatus">TaskStatus</see> states a continuation + /// will be executed, are illegal with ContinueWhenAny. + /// </remarks> + [MethodImplAttribute(MethodImplOptions.NoInlining)] // Methods containing StackCrawlMark local var have to be marked non-inlineable + public Task ContinueWhenAny<TAntecedentResult>(Task<TAntecedentResult>[] tasks, Action<Task<TAntecedentResult>> continuationAction, + CancellationToken cancellationToken, TaskContinuationOptions continuationOptions, TaskScheduler scheduler) + { + if (continuationAction == null) throw new ArgumentNullException("continuationAction"); + Contract.EndContractBlock(); + + StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller; + return TaskFactory<VoidTaskResult>.ContinueWhenAnyImpl<TAntecedentResult>(tasks, null, continuationAction, continuationOptions, cancellationToken, scheduler, ref stackMark); + } + + // Check task array and return a defensive copy. + // Used with ContinueWhenAll()/ContinueWhenAny(). + internal static Task[] CheckMultiContinuationTasksAndCopy(Task[] tasks) + { + if (tasks == null) + throw new ArgumentNullException("tasks"); + if (tasks.Length == 0) + throw new ArgumentException(Environment.GetResourceString("Task_MultiTaskContinuation_EmptyTaskList"), "tasks"); + Contract.EndContractBlock(); + + Task[] tasksCopy = new Task[tasks.Length]; + for (int i = 0; i < tasks.Length; i++) + { + tasksCopy[i] = tasks[i]; + + if (tasksCopy[i] == null) + throw new ArgumentException(Environment.GetResourceString("Task_MultiTaskContinuation_NullTask"), "tasks"); + } + + return tasksCopy; + } + + internal static Task<TResult>[] CheckMultiContinuationTasksAndCopy<TResult>(Task<TResult>[] tasks) + { + if (tasks == null) + throw new ArgumentNullException("tasks"); + if (tasks.Length == 0) + throw new ArgumentException(Environment.GetResourceString("Task_MultiTaskContinuation_EmptyTaskList"), "tasks"); + Contract.EndContractBlock(); + + Task<TResult>[] tasksCopy = new Task<TResult>[tasks.Length]; + for (int i = 0; i < tasks.Length; i++) + { + tasksCopy[i] = tasks[i]; + + if (tasksCopy[i] == null) + throw new ArgumentException(Environment.GetResourceString("Task_MultiTaskContinuation_NullTask"), "tasks"); + } + + return tasksCopy; + } + + // Throw an exception if "options" argument specifies illegal options + [ContractArgumentValidatorAttribute] + internal static void CheckMultiTaskContinuationOptions(TaskContinuationOptions continuationOptions) + { + // Construct a mask to check for illegal options + const TaskContinuationOptions NotOnAny = TaskContinuationOptions.NotOnCanceled | + TaskContinuationOptions.NotOnFaulted | + TaskContinuationOptions.NotOnRanToCompletion; + + // Check that LongRunning and ExecuteSynchronously are not specified together + const TaskContinuationOptions illegalMask = TaskContinuationOptions.ExecuteSynchronously | TaskContinuationOptions.LongRunning; + if ((continuationOptions & illegalMask) == illegalMask) + { + throw new ArgumentOutOfRangeException("continuationOptions", Environment.GetResourceString("Task_ContinueWith_ESandLR")); + } + + // Check that no nonsensical options are specified. + if ((continuationOptions & ~( + TaskContinuationOptions.LongRunning | + TaskContinuationOptions.PreferFairness | + TaskContinuationOptions.AttachedToParent | + TaskContinuationOptions.DenyChildAttach | + TaskContinuationOptions.HideScheduler | + TaskContinuationOptions.LazyCancellation | + NotOnAny | + TaskContinuationOptions.ExecuteSynchronously)) != 0) + { + throw new ArgumentOutOfRangeException("continuationOptions"); + } + + // Check that no "fire" options are specified. + if ((continuationOptions & NotOnAny) != 0) + throw new ArgumentOutOfRangeException("continuationOptions", Environment.GetResourceString("Task_MultiTaskContinuation_FireOptions")); + Contract.EndContractBlock(); + } + } + +} diff --git a/src/mscorlib/src/System/Threading/Tasks/TaskScheduler.cs b/src/mscorlib/src/System/Threading/Tasks/TaskScheduler.cs new file mode 100644 index 0000000000..f82492499c --- /dev/null +++ b/src/mscorlib/src/System/Threading/Tasks/TaskScheduler.cs @@ -0,0 +1,757 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +// =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ +// +// +// +// This file contains the primary interface and management of tasks and queues. +// +// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- +// Disable the "reference to volatile field not treated as volatile" error. +#pragma warning disable 0420 +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Threading; +using System.Security; +using System.Security.Permissions; +using System.Collections.Concurrent; +using System.Diagnostics.Contracts; +using System.Diagnostics; +using System.Runtime.CompilerServices; + +namespace System.Threading.Tasks +{ + + /// <summary> + /// Represents an abstract scheduler for tasks. + /// </summary> + /// <remarks> + /// <para> + /// <see cref="System.Threading.Tasks.TaskScheduler">TaskScheduler</see> acts as the extension point for all + /// pluggable scheduling logic. This includes mechanisms such as how to schedule a task for execution, and + /// how scheduled tasks should be exposed to debuggers. + /// </para> + /// <para> + /// All members of the abstract <see cref="TaskScheduler"/> type are thread-safe + /// and may be used from multiple threads concurrently. + /// </para> + /// </remarks> + [DebuggerDisplay("Id={Id}")] + [DebuggerTypeProxy(typeof(SystemThreadingTasks_TaskSchedulerDebugView))] + [HostProtection(Synchronization = true, ExternalThreading = true)] +#pragma warning disable 618 + [PermissionSet(SecurityAction.InheritanceDemand, Unrestricted = true)] +#pragma warning restore 618 + public abstract class TaskScheduler + { + //////////////////////////////////////////////////////////// + // + // User Provided Methods and Properties + // + + /// <summary> + /// Queues a <see cref="T:System.Threading.Tasks.Task">Task</see> to the scheduler. + /// </summary> + /// <remarks> + /// <para> + /// A class derived from <see cref="T:System.Threading.Tasks.TaskScheduler">TaskScheduler</see> + /// implements this method to accept tasks being scheduled on the scheduler. + /// A typical implementation would store the task in an internal data structure, which would + /// be serviced by threads that would execute those tasks at some time in the future. + /// </para> + /// <para> + /// This method is only meant to be called by the .NET Framework and + /// should not be called directly by the derived class. This is necessary + /// for maintaining the consistency of the system. + /// </para> + /// </remarks> + /// <param name="task">The <see cref="T:System.Threading.Tasks.Task">Task</see> to be queued.</param> + /// <exception cref="T:System.ArgumentNullException">The <paramref name="task"/> argument is null.</exception> + [SecurityCritical] + protected internal abstract void QueueTask(Task task); + + /// <summary> + /// Determines whether the provided <see cref="T:System.Threading.Tasks.Task">Task</see> + /// can be executed synchronously in this call, and if it can, executes it. + /// </summary> + /// <remarks> + /// <para> + /// A class derived from <see cref="TaskScheduler">TaskScheduler</see> implements this function to + /// support inline execution of a task on a thread that initiates a wait on that task object. Inline + /// execution is optional, and the request may be rejected by returning false. However, better + /// scalability typically results the more tasks that can be inlined, and in fact a scheduler that + /// inlines too little may be prone to deadlocks. A proper implementation should ensure that a + /// request executing under the policies guaranteed by the scheduler can successfully inline. For + /// example, if a scheduler uses a dedicated thread to execute tasks, any inlining requests from that + /// thread should succeed. + /// </para> + /// <para> + /// If a scheduler decides to perform the inline execution, it should do so by calling to the base + /// TaskScheduler's + /// <see cref="TryExecuteTask">TryExecuteTask</see> method with the provided task object, propagating + /// the return value. It may also be appropriate for the scheduler to remove an inlined task from its + /// internal data structures if it decides to honor the inlining request. Note, however, that under + /// some circumstances a scheduler may be asked to inline a task that was not previously provided to + /// it with the <see cref="QueueTask"/> method. + /// </para> + /// <para> + /// The derived scheduler is responsible for making sure that the calling thread is suitable for + /// executing the given task as far as its own scheduling and execution policies are concerned. + /// </para> + /// </remarks> + /// <param name="task">The <see cref="T:System.Threading.Tasks.Task">Task</see> to be + /// executed.</param> + /// <param name="taskWasPreviouslyQueued">A Boolean denoting whether or not task has previously been + /// queued. If this parameter is True, then the task may have been previously queued (scheduled); if + /// False, then the task is known not to have been queued, and this call is being made in order to + /// execute the task inline without queueing it.</param> + /// <returns>A Boolean value indicating whether the task was executed inline.</returns> + /// <exception cref="T:System.ArgumentNullException">The <paramref name="task"/> argument is + /// null.</exception> + /// <exception cref="T:System.InvalidOperationException">The <paramref name="task"/> was already + /// executed.</exception> + [SecurityCritical] + protected abstract bool TryExecuteTaskInline(Task task, bool taskWasPreviouslyQueued); + + /// <summary> + /// Generates an enumerable of <see cref="T:System.Threading.Tasks.Task">Task</see> instances + /// currently queued to the scheduler waiting to be executed. + /// </summary> + /// <remarks> + /// <para> + /// A class derived from <see cref="TaskScheduler"/> implements this method in order to support + /// integration with debuggers. This method will only be invoked by the .NET Framework when the + /// debugger requests access to the data. The enumerable returned will be traversed by debugging + /// utilities to access the tasks currently queued to this scheduler, enabling the debugger to + /// provide a representation of this information in the user interface. + /// </para> + /// <para> + /// It is important to note that, when this method is called, all other threads in the process will + /// be frozen. Therefore, it's important to avoid synchronization with other threads that may lead to + /// blocking. If synchronization is necessary, the method should prefer to throw a <see + /// cref="System.NotSupportedException"/> + /// than to block, which could cause a debugger to experience delays. Additionally, this method and + /// the enumerable returned must not modify any globally visible state. + /// </para> + /// <para> + /// The returned enumerable should never be null. If there are currently no queued tasks, an empty + /// enumerable should be returned instead. + /// </para> + /// <para> + /// For developers implementing a custom debugger, this method shouldn't be called directly, but + /// rather this functionality should be accessed through the internal wrapper method + /// GetScheduledTasksForDebugger: + /// <c>internal Task[] GetScheduledTasksForDebugger()</c>. This method returns an array of tasks, + /// rather than an enumerable. In order to retrieve a list of active schedulers, a debugger may use + /// another internal method: <c>internal static TaskScheduler[] GetTaskSchedulersForDebugger()</c>. + /// This static method returns an array of all active TaskScheduler instances. + /// GetScheduledTasksForDebugger then may be used on each of these scheduler instances to retrieve + /// the list of scheduled tasks for each. + /// </para> + /// </remarks> + /// <returns>An enumerable that allows traversal of tasks currently queued to this scheduler. + /// </returns> + /// <exception cref="T:System.NotSupportedException"> + /// This scheduler is unable to generate a list of queued tasks at this time. + /// </exception> + [SecurityCritical] + protected abstract IEnumerable<Task> GetScheduledTasks(); + + /// <summary> + /// Indicates the maximum concurrency level this + /// <see cref="TaskScheduler"/> is able to support. + /// </summary> + public virtual Int32 MaximumConcurrencyLevel + { + get + { + return Int32.MaxValue; + } + } + + //////////////////////////////////////////////////////////// + // + // Internal overridable methods + // + + + /// <summary> + /// Attempts to execute the target task synchronously. + /// </summary> + /// <param name="task">The task to run.</param> + /// <param name="taskWasPreviouslyQueued">True if the task may have been previously queued, + /// false if the task was absolutely not previously queued.</param> + /// <returns>True if it ran, false otherwise.</returns> + [SecuritySafeCritical] + internal bool TryRunInline(Task task, bool taskWasPreviouslyQueued) + { + // Do not inline unstarted tasks (i.e., task.ExecutingTaskScheduler == null). + // Do not inline TaskCompletionSource-style (a.k.a. "promise") tasks. + // No need to attempt inlining if the task body was already run (i.e. either TASK_STATE_DELEGATE_INVOKED or TASK_STATE_CANCELED bits set) + TaskScheduler ets = task.ExecutingTaskScheduler; + + // Delegate cross-scheduler inlining requests to target scheduler + if(ets != this && ets !=null) return ets.TryRunInline(task, taskWasPreviouslyQueued); + + StackGuard currentStackGuard; + if( (ets == null) || + (task.m_action == null) || + task.IsDelegateInvoked || + task.IsCanceled || + (currentStackGuard = Task.CurrentStackGuard).TryBeginInliningScope() == false) + { + return false; + } + + // Task class will still call into TaskScheduler.TryRunInline rather than TryExecuteTaskInline() so that + // 1) we can adjust the return code from TryExecuteTaskInline in case a buggy custom scheduler lies to us + // 2) we maintain a mechanism for the TLS lookup optimization that we used to have for the ConcRT scheduler (will potentially introduce the same for TP) + bool bInlined = false; + try + { + task.FireTaskScheduledIfNeeded(this); + bInlined = TryExecuteTaskInline(task, taskWasPreviouslyQueued); + } + finally + { + currentStackGuard.EndInliningScope(); + } + + // If the custom scheduler returned true, we should either have the TASK_STATE_DELEGATE_INVOKED or TASK_STATE_CANCELED bit set + // Otherwise the scheduler is buggy + if (bInlined && !(task.IsDelegateInvoked || task.IsCanceled)) + { + throw new InvalidOperationException(Environment.GetResourceString("TaskScheduler_InconsistentStateAfterTryExecuteTaskInline")); + } + + return bInlined; + } + + /// <summary> + /// Attempts to dequeue a <see cref="T:System.Threading.Tasks.Task">Task</see> that was previously queued to + /// this scheduler. + /// </summary> + /// <param name="task">The <see cref="T:System.Threading.Tasks.Task">Task</see> to be dequeued.</param> + /// <returns>A Boolean denoting whether the <paramref name="task"/> argument was successfully dequeued.</returns> + /// <exception cref="T:System.ArgumentNullException">The <paramref name="task"/> argument is null.</exception> + [SecurityCritical] + protected internal virtual bool TryDequeue(Task task) + { + return false; + } + + /// <summary> + /// Notifies the scheduler that a work item has made progress. + /// </summary> + internal virtual void NotifyWorkItemProgress() + { + } + + /// <summary> + /// Indicates whether this is a custom scheduler, in which case the safe code paths will be taken upon task entry + /// using a CAS to transition from queued state to executing. + /// </summary> + internal virtual bool RequiresAtomicStartTransition + { + get { return true; } + } + + /// <summary> + /// Calls QueueTask() after performing any needed firing of events + /// </summary> + [SecurityCritical] + internal void InternalQueueTask(Task task) + { + Contract.Requires(task != null); + + task.FireTaskScheduledIfNeeded(this); + + this.QueueTask(task); + } + + + //////////////////////////////////////////////////////////// + // + // Member variables + // + + // The global container that keeps track of TaskScheduler instances for debugging purposes. + private static ConditionalWeakTable<TaskScheduler, object> s_activeTaskSchedulers; + + // An AppDomain-wide default manager. + private static readonly TaskScheduler s_defaultTaskScheduler = new ThreadPoolTaskScheduler(); + + //static counter used to generate unique TaskScheduler IDs + internal static int s_taskSchedulerIdCounter; + + // this TaskScheduler's unique ID + private volatile int m_taskSchedulerId; + + + + //////////////////////////////////////////////////////////// + // + // Constructors and public properties + // + + /// <summary> + /// Initializes the <see cref="System.Threading.Tasks.TaskScheduler"/>. + /// </summary> + protected TaskScheduler() + { + // Register the scheduler in the active scheduler list. This is only relevant when debugging, + // so we only pay the cost if the debugger is attached when the scheduler is created. This + // means that the internal TaskScheduler.GetTaskSchedulersForDebugger() will only include + // schedulers created while the debugger is attached. + if (Debugger.IsAttached) + { + AddToActiveTaskSchedulers(); + } + } + + /// <summary>Adds this scheduler ot the active schedulers tracking collection for debugging purposes.</summary> + private void AddToActiveTaskSchedulers() + { + ConditionalWeakTable<TaskScheduler, object> activeTaskSchedulers = s_activeTaskSchedulers; + if (activeTaskSchedulers == null) + { + Interlocked.CompareExchange(ref s_activeTaskSchedulers, new ConditionalWeakTable<TaskScheduler, object>(), null); + activeTaskSchedulers = s_activeTaskSchedulers; + } + activeTaskSchedulers.Add(this, null); + } + + /// <summary> + /// Gets the default <see cref="System.Threading.Tasks.TaskScheduler">TaskScheduler</see> instance. + /// </summary> + public static TaskScheduler Default + { + get + { + return s_defaultTaskScheduler; + } + } + + /// <summary> + /// Gets the <see cref="System.Threading.Tasks.TaskScheduler">TaskScheduler</see> + /// associated with the currently executing task. + /// </summary> + /// <remarks> + /// When not called from within a task, <see cref="Current"/> will return the <see cref="Default"/> scheduler. + /// </remarks> + public static TaskScheduler Current + { + get + { + TaskScheduler current = InternalCurrent; + return current ?? TaskScheduler.Default; + } + } + + /// <summary> + /// Gets the <see cref="System.Threading.Tasks.TaskScheduler">TaskScheduler</see> + /// associated with the currently executing task. + /// </summary> + /// <remarks> + /// When not called from within a task, <see cref="InternalCurrent"/> will return null. + /// </remarks> + internal static TaskScheduler InternalCurrent + { + get + { + Task currentTask = Task.InternalCurrent; + return ( (currentTask != null) + && ((currentTask.CreationOptions & TaskCreationOptions.HideScheduler) == 0) + ) ? currentTask.ExecutingTaskScheduler : null; + } + } + + /// <summary> + /// Creates a <see cref="TaskScheduler"/> + /// associated with the current <see cref="T:System.Threading.SynchronizationContext"/>. + /// </summary> + /// <remarks> + /// All <see cref="System.Threading.Tasks.Task">Task</see> instances queued to + /// the returned scheduler will be executed through a call to the + /// <see cref="System.Threading.SynchronizationContext.Post">Post</see> method + /// on that context. + /// </remarks> + /// <returns> + /// A <see cref="TaskScheduler"/> associated with + /// the current <see cref="T:System.Threading.SynchronizationContext">SynchronizationContext</see>, as + /// determined by <see cref="System.Threading.SynchronizationContext.Current">SynchronizationContext.Current</see>. + /// </returns> + /// <exception cref="T:System.InvalidOperationException"> + /// The current SynchronizationContext may not be used as a TaskScheduler. + /// </exception> + public static TaskScheduler FromCurrentSynchronizationContext() + { + return new SynchronizationContextTaskScheduler(); + } + + /// <summary> + /// Gets the unique ID for this <see cref="TaskScheduler"/>. + /// </summary> + public Int32 Id + { + get + { + if (m_taskSchedulerId == 0) + { + int newId = 0; + + // We need to repeat if Interlocked.Increment wraps around and returns 0. + // Otherwise next time this scheduler's Id is queried it will get a new value + do + { + newId = Interlocked.Increment(ref s_taskSchedulerIdCounter); + } while (newId == 0); + + Interlocked.CompareExchange(ref m_taskSchedulerId, newId, 0); + } + + return m_taskSchedulerId; + } + } + + /// <summary> + /// Attempts to execute the provided <see cref="T:System.Threading.Tasks.Task">Task</see> + /// on this scheduler. + /// </summary> + /// <remarks> + /// <para> + /// Scheduler implementations are provided with <see cref="T:System.Threading.Tasks.Task">Task</see> + /// instances to be executed through either the <see cref="QueueTask"/> method or the + /// <see cref="TryExecuteTaskInline"/> method. When the scheduler deems it appropriate to run the + /// provided task, <see cref="TryExecuteTask"/> should be used to do so. TryExecuteTask handles all + /// aspects of executing a task, including action invocation, exception handling, state management, + /// and lifecycle control. + /// </para> + /// <para> + /// <see cref="TryExecuteTask"/> must only be used for tasks provided to this scheduler by the .NET + /// Framework infrastructure. It should not be used to execute arbitrary tasks obtained through + /// custom mechanisms. + /// </para> + /// </remarks> + /// <param name="task"> + /// A <see cref="T:System.Threading.Tasks.Task">Task</see> object to be executed.</param> + /// <exception cref="T:System.InvalidOperationException"> + /// The <paramref name="task"/> is not associated with this scheduler. + /// </exception> + /// <returns>A Boolean that is true if <paramref name="task"/> was successfully executed, false if it + /// was not. A common reason for execution failure is that the task had previously been executed or + /// is in the process of being executed by another thread.</returns> + [SecurityCritical] + protected bool TryExecuteTask(Task task) + { + if (task.ExecutingTaskScheduler != this) + { + throw new InvalidOperationException(Environment.GetResourceString("TaskScheduler_ExecuteTask_WrongTaskScheduler")); + } + + return task.ExecuteEntry(true); + } + + //////////////////////////////////////////////////////////// + // + // Events + // + + private static EventHandler<UnobservedTaskExceptionEventArgs> _unobservedTaskException; + private static readonly object _unobservedTaskExceptionLockObject = new object(); + + /// <summary> + /// Occurs when a faulted <see cref="System.Threading.Tasks.Task"/>'s unobserved exception is about to trigger exception escalation + /// policy, which, by default, would terminate the process. + /// </summary> + /// <remarks> + /// This AppDomain-wide event provides a mechanism to prevent exception + /// escalation policy (which, by default, terminates the process) from triggering. + /// Each handler is passed a <see cref="T:System.Threading.Tasks.UnobservedTaskExceptionEventArgs"/> + /// instance, which may be used to examine the exception and to mark it as observed. + /// </remarks> + public static event EventHandler<UnobservedTaskExceptionEventArgs> UnobservedTaskException + { + [System.Security.SecurityCritical] + add + { + if (value != null) + { + RuntimeHelpers.PrepareContractedDelegate(value); + lock (_unobservedTaskExceptionLockObject) _unobservedTaskException += value; + } + } + + [System.Security.SecurityCritical] + remove + { + lock (_unobservedTaskExceptionLockObject) _unobservedTaskException -= value; + } + } + + + + + + + //////////////////////////////////////////////////////////// + // + // Internal methods + // + + // This is called by the TaskExceptionHolder finalizer. + internal static void PublishUnobservedTaskException(object sender, UnobservedTaskExceptionEventArgs ueea) + { + // Lock this logic to prevent just-unregistered handlers from being called. + lock (_unobservedTaskExceptionLockObject) + { + // Since we are under lock, it is technically no longer necessary + // to make a copy. It is done here for convenience. + EventHandler<UnobservedTaskExceptionEventArgs> handler = _unobservedTaskException; + if (handler != null) + { + handler(sender, ueea); + } + } + } + + /// <summary> + /// Provides an array of all queued <see cref="System.Threading.Tasks.Task">Task</see> instances + /// for the debugger. + /// </summary> + /// <remarks> + /// The returned array is populated through a call to <see cref="GetScheduledTasks"/>. + /// Note that this function is only meant to be invoked by a debugger remotely. + /// It should not be called by any other codepaths. + /// </remarks> + /// <returns>An array of <see cref="System.Threading.Tasks.Task">Task</see> instances.</returns> + /// <exception cref="T:System.NotSupportedException"> + /// This scheduler is unable to generate a list of queued tasks at this time. + /// </exception> + [SecurityCritical] + internal Task[] GetScheduledTasksForDebugger() + { + // this can throw InvalidOperationException indicating that they are unable to provide the info + // at the moment. We should let the debugger receive that exception so that it can indicate it in the UI + IEnumerable<Task> activeTasksSource = GetScheduledTasks(); + + if (activeTasksSource == null) + return null; + + // If it can be cast to an array, use it directly + Task[] activeTasksArray = activeTasksSource as Task[]; + if (activeTasksArray == null) + { + activeTasksArray = (new List<Task>(activeTasksSource)).ToArray(); + } + + // touch all Task.Id fields so that the debugger doesn't need to do a lot of cross-proc calls to generate them + foreach (Task t in activeTasksArray) + { + int tmp = t.Id; + } + + return activeTasksArray; + } + + /// <summary> + /// Provides an array of all active <see cref="System.Threading.Tasks.TaskScheduler">TaskScheduler</see> + /// instances for the debugger. + /// </summary> + /// <remarks> + /// This function is only meant to be invoked by a debugger remotely. + /// It should not be called by any other codepaths. + /// </remarks> + /// <returns>An array of <see cref="System.Threading.Tasks.TaskScheduler">TaskScheduler</see> instances.</returns> + [SecurityCritical] + internal static TaskScheduler[] GetTaskSchedulersForDebugger() + { + if (s_activeTaskSchedulers == null) + { + // No schedulers were tracked. Just give back the default. + return new TaskScheduler[] { s_defaultTaskScheduler }; + } + + ICollection<TaskScheduler> schedulers = s_activeTaskSchedulers.Keys; + if (!schedulers.Contains(s_defaultTaskScheduler)) + { + // Make sure the default is included, in case the debugger attached + // after it was created. + schedulers.Add(s_defaultTaskScheduler); + } + + var arr = new TaskScheduler[schedulers.Count]; + schedulers.CopyTo(arr, 0); + foreach (var scheduler in arr) + { + Contract.Assert(scheduler != null, "Table returned an incorrect Count or CopyTo failed"); + int tmp = scheduler.Id; // force Ids for debugger + } + return arr; + } + + /// <summary> + /// Nested class that provides debugger view for TaskScheduler + /// </summary> + internal sealed class SystemThreadingTasks_TaskSchedulerDebugView + { + private readonly TaskScheduler m_taskScheduler; + public SystemThreadingTasks_TaskSchedulerDebugView(TaskScheduler scheduler) + { + m_taskScheduler = scheduler; + } + + // returns the scheduler’s Id + public Int32 Id + { + get { return m_taskScheduler.Id; } + } + + // returns the scheduler’s GetScheduledTasks + public IEnumerable<Task> ScheduledTasks + { + [SecurityCritical] + get { return m_taskScheduler.GetScheduledTasks(); } + } + } + + } + + + + + /// <summary> + /// A TaskScheduler implementation that executes all tasks queued to it through a call to + /// <see cref="System.Threading.SynchronizationContext.Post"/> on the <see cref="T:System.Threading.SynchronizationContext"/> + /// that its associated with. The default constructor for this class binds to the current <see cref="T:System.Threading.SynchronizationContext"/> + /// </summary> + internal sealed class SynchronizationContextTaskScheduler : TaskScheduler + { + private SynchronizationContext m_synchronizationContext; + + /// <summary> + /// Constructs a SynchronizationContextTaskScheduler associated with <see cref="T:System.Threading.SynchronizationContext.Current"/> + /// </summary> + /// <exception cref="T:System.InvalidOperationException">This constructor expects <see cref="T:System.Threading.SynchronizationContext.Current"/> to be set.</exception> + internal SynchronizationContextTaskScheduler() + { + SynchronizationContext synContext = SynchronizationContext.Current; + + // make sure we have a synccontext to work with + if (synContext == null) + { + throw new InvalidOperationException(Environment.GetResourceString("TaskScheduler_FromCurrentSynchronizationContext_NoCurrent")); + } + + m_synchronizationContext = synContext; + + } + + /// <summary> + /// Implemetation of <see cref="T:System.Threading.Tasks.TaskScheduler.QueueTask"/> for this scheduler class. + /// + /// Simply posts the tasks to be executed on the associated <see cref="T:System.Threading.SynchronizationContext"/>. + /// </summary> + /// <param name="task"></param> + [SecurityCritical] + protected internal override void QueueTask(Task task) + { + m_synchronizationContext.Post(s_postCallback, (object)task); + } + + /// <summary> + /// Implementation of <see cref="T:System.Threading.Tasks.TaskScheduler.TryExecuteTaskInline"/> for this scheduler class. + /// + /// The task will be executed inline only if the call happens within + /// the associated <see cref="T:System.Threading.SynchronizationContext"/>. + /// </summary> + /// <param name="task"></param> + /// <param name="taskWasPreviouslyQueued"></param> + [SecurityCritical] + protected override bool TryExecuteTaskInline(Task task, bool taskWasPreviouslyQueued) + { + if (SynchronizationContext.Current == m_synchronizationContext) + { + return TryExecuteTask(task); + } + else + return false; + } + + // not implemented + [SecurityCritical] + protected override IEnumerable<Task> GetScheduledTasks() + { + return null; + } + + /// <summary> + /// Implementes the <see cref="T:System.Threading.Tasks.TaskScheduler.MaximumConcurrencyLevel"/> property for + /// this scheduler class. + /// + /// By default it returns 1, because a <see cref="T:System.Threading.SynchronizationContext"/> based + /// scheduler only supports execution on a single thread. + /// </summary> + public override Int32 MaximumConcurrencyLevel + { + get + { + return 1; + } + } + + // preallocated SendOrPostCallback delegate + private static SendOrPostCallback s_postCallback = new SendOrPostCallback(PostCallback); + + // this is where the actual task invocation occures + private static void PostCallback(object obj) + { + Task task = (Task) obj; + + // calling ExecuteEntry with double execute check enabled because a user implemented SynchronizationContext could be buggy + task.ExecuteEntry(true); + } + } + + /// <summary> + /// Provides data for the event that is raised when a faulted <see cref="System.Threading.Tasks.Task"/>'s + /// exception goes unobserved. + /// </summary> + /// <remarks> + /// The Exception property is used to examine the exception without marking it + /// as observed, whereas the <see cref="SetObserved"/> method is used to mark the exception + /// as observed. Marking the exception as observed prevents it from triggering exception escalation policy + /// which, by default, terminates the process. + /// </remarks> + public class UnobservedTaskExceptionEventArgs : EventArgs + { + private AggregateException m_exception; + internal bool m_observed = false; + + /// <summary> + /// Initializes a new instance of the <see cref="UnobservedTaskExceptionEventArgs"/> class + /// with the unobserved exception. + /// </summary> + /// <param name="exception">The Exception that has gone unobserved.</param> + public UnobservedTaskExceptionEventArgs(AggregateException exception) { m_exception = exception; } + + /// <summary> + /// Marks the <see cref="Exception"/> as "observed," thus preventing it + /// from triggering exception escalation policy which, by default, terminates the process. + /// </summary> + public void SetObserved() { m_observed = true; } + + /// <summary> + /// Gets whether this exception has been marked as "observed." + /// </summary> + public bool Observed { get { return m_observed; } } + + /// <summary> + /// The Exception that went unobserved. + /// </summary> + public AggregateException Exception { get { return m_exception; } } + } +} diff --git a/src/mscorlib/src/System/Threading/Tasks/TaskSchedulerException.cs b/src/mscorlib/src/System/Threading/Tasks/TaskSchedulerException.cs new file mode 100644 index 0000000000..1d85e49342 --- /dev/null +++ b/src/mscorlib/src/System/Threading/Tasks/TaskSchedulerException.cs @@ -0,0 +1,81 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +// =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ +// +// +// +// An exception for task schedulers. +// +// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + +using System; +using System.Runtime.InteropServices; +using System.Runtime.Serialization; + +namespace System.Threading.Tasks +{ + + /// <summary> + /// Represents an exception used to communicate an invalid operation by a + /// <see cref="T:System.Threading.Tasks.TaskScheduler"/>. + /// </summary> + [Serializable] + public class TaskSchedulerException : Exception + { + /// <summary> + /// Initializes a new instance of the <see cref="T:System.Threading.Tasks.TaskSchedulerException"/> class. + /// </summary> + public TaskSchedulerException() : base(Environment.GetResourceString("TaskSchedulerException_ctor_DefaultMessage")) // + { + } + + /// <summary> + /// Initializes a new instance of the <see cref="T:System.Threading.Tasks.TaskSchedulerException"/> + /// class with a specified error message. + /// </summary> + /// <param name="message">The error message that explains the reason for the exception.</param> + public TaskSchedulerException(string message) : base(message) + { + } + + /// <summary> + /// Initializes a new instance of the <see cref="T:System.Threading.Tasks.TaskSchedulerException"/> + /// class using the default error message and a reference to the inner exception that is the cause of + /// this exception. + /// </summary> + /// <param name="innerException">The exception that is the cause of the current exception.</param> + public TaskSchedulerException(Exception innerException) + : base(Environment.GetResourceString("TaskSchedulerException_ctor_DefaultMessage"), innerException) + { + } + + /// <summary> + /// Initializes a new instance of the <see cref="T:System.Threading.Tasks.TaskSchedulerException"/> + /// class with a specified error message and a reference to the inner exception that is the cause of + /// this exception. + /// </summary> + /// <param name="message">The error message that explains the reason for the exception.</param> + /// <param name="innerException">The exception that is the cause of the current exception.</param> + public TaskSchedulerException(string message, Exception innerException) : base(message, innerException) + { + } + + /// <summary> + /// Initializes a new instance of the <see cref="T:System.Threading.Tasks.TaskSchedulerException"/> + /// class with serialized data. + /// </summary> + /// <param name="info">The <see cref="T:System.Runtime.Serialization.SerializationInfo"/> that holds + /// the serialized object data about the exception being thrown.</param> + /// <param name="context">The <see cref="T:System.Runtime.Serialization.StreamingContext"/> that + /// contains contextual information about the source or destination. </param> + protected TaskSchedulerException(SerializationInfo info, StreamingContext context) + : base(info, context) + { + } + + + } + +} diff --git a/src/mscorlib/src/System/Threading/Tasks/TaskToApm.cs b/src/mscorlib/src/System/Threading/Tasks/TaskToApm.cs new file mode 100644 index 0000000000..02b130c297 --- /dev/null +++ b/src/mscorlib/src/System/Threading/Tasks/TaskToApm.cs @@ -0,0 +1,189 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +// =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ +// +// +// +// Helper methods for using Tasks to implement the APM pattern. +// +// Example usage, wrapping a Task<int>-returning FooAsync method with Begin/EndFoo methods: +// public IAsyncResult BeginFoo(..., AsyncCallback callback, object state) +// { +// Task<int> t = FooAsync(...); +// return TaskToApm.Begin(t, callback, state); +// } +// public int EndFoo(IAsyncResult asyncResult) +// { +// return TaskToApm.End<int>(asyncResult); +// } +// +// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + +using System.IO; +using System.Diagnostics.Contracts; + +namespace System.Threading.Tasks +{ + /// <summary> + /// Provides support for efficiently using Tasks to implement the APM (Begin/End) pattern. + /// </summary> + internal static class TaskToApm + { + /// <summary> + /// Marshals the Task as an IAsyncResult, using the supplied callback and state + /// to implement the APM pattern. + /// </summary> + /// <param name="task">The Task to be marshaled.</param> + /// <param name="callback">The callback to be invoked upon completion.</param> + /// <param name="state">The state to be stored in the IAsyncResult.</param> + /// <returns>An IAsyncResult to represent the task's asynchronous operation.</returns> + public static IAsyncResult Begin(Task task, AsyncCallback callback, object state) + { + Contract.Requires(task != null); + + // If the task has already completed, then since the Task's CompletedSynchronously==false + // and we want it to be true, we need to create a new IAsyncResult. (We also need the AsyncState to match.) + IAsyncResult asyncResult; + if (task.IsCompleted) + { + // Synchronous completion + asyncResult = new TaskWrapperAsyncResult(task, state, completedSynchronously: true); + if (callback != null) + callback(asyncResult); + } + // Otherwise, we need to schedule a callback. Whether we can use the Task as the IAsyncResult + // depends on whether the Task's AsyncState has reference equality with the requested state. + else + { + // Asynchronous completion + asyncResult = task.AsyncState == state ? (IAsyncResult)task : new TaskWrapperAsyncResult(task, state, completedSynchronously: false); + if (callback != null) + InvokeCallbackWhenTaskCompletes(task, callback, asyncResult); + } + return asyncResult; + } + + /// <summary>Processes an IAsyncResult returned by Begin.</summary> + /// <param name="asyncResult">The IAsyncResult to unwrap.</param> + public static void End(IAsyncResult asyncResult) + { + Task task; + + // If the IAsyncResult is our task-wrapping IAsyncResult, extract the Task. + var twar = asyncResult as TaskWrapperAsyncResult; + if (twar != null) + { + task = twar.Task; + Contract.Assert(task != null, "TaskWrapperAsyncResult should never wrap a null Task."); + } + // Otherwise, the IAsyncResult should be a Task. + else + { + task = asyncResult as Task; + } + + // Make sure we actually got a task, then complete the operation by waiting on it. + if (task == null) + __Error.WrongAsyncResult(); + task.GetAwaiter().GetResult(); + } + + /// <summary>Processes an IAsyncResult returned by Begin.</summary> + /// <param name="asyncResult">The IAsyncResult to unwrap.</param> + public static TResult End<TResult>(IAsyncResult asyncResult) + { + Task<TResult> task; + + // If the IAsyncResult is our task-wrapping IAsyncResult, extract the Task. + var twar = asyncResult as TaskWrapperAsyncResult; + if (twar != null) + { + task = twar.Task as Task<TResult>; + Contract.Assert(twar.Task != null, "TaskWrapperAsyncResult should never wrap a null Task."); + } + // Otherwise, the IAsyncResult should be a Task<TResult>. + else + { + task = asyncResult as Task<TResult>; + } + + // Make sure we actually got a task, then complete the operation by waiting on it. + if (task == null) + __Error.WrongAsyncResult(); + return task.GetAwaiter().GetResult(); + } + + /// <summary>Invokes the callback asynchronously when the task has completed.</summary> + /// <param name="antecedent">The Task to await.</param> + /// <param name="callback">The callback to invoke when the Task completes.</param> + /// <param name="asyncResult">The Task used as the IAsyncResult.</param> + private static void InvokeCallbackWhenTaskCompletes(Task antecedent, AsyncCallback callback, IAsyncResult asyncResult) + { + Contract.Requires(antecedent != null); + Contract.Requires(callback != null); + Contract.Requires(asyncResult != null); + + // We use OnCompleted rather than ContinueWith in order to avoid running synchronously + // if the task has already completed by the time we get here. This is separated out into + // its own method currently so that we only pay for the closure if necessary. + antecedent.ConfigureAwait(continueOnCapturedContext:false) + .GetAwaiter() + .OnCompleted(() => callback(asyncResult)); + + // PERFORMANCE NOTE: + // Assuming we're in the default ExecutionContext, the "slow path" of an incomplete + // task will result in four allocations: the new IAsyncResult, the delegate+closure + // in this method, and the continuation object inside of OnCompleted (necessary + // to capture both the Action delegate and the ExecutionContext in a single object). + // In the future, if performance requirements drove a need, those four + // allocations could be reduced to one. This would be achieved by having TaskWrapperAsyncResult + // also implement ITaskCompletionAction (and optionally IThreadPoolWorkItem). It would need + // additional fields to store the AsyncCallback and an ExecutionContext. Once configured, + // it would be set into the Task as a continuation. Its Invoke method would then be run when + // the antecedent completed, and, doing all of the necessary work to flow ExecutionContext, + // it would invoke the AsyncCallback. It could also have a field on it for the antecedent, + // so that the End method would have access to the completed antecedent. For related examples, + // see other implementations of ITaskCompletionAction, and in particular ReadWriteTask + // used in Stream.Begin/EndXx's implementation. + } + + /// <summary> + /// Provides a simple IAsyncResult that wraps a Task. This, in effect, allows + /// for overriding what's seen for the CompletedSynchronously and AsyncState values. + /// </summary> + private sealed class TaskWrapperAsyncResult : IAsyncResult + { + /// <summary>The wrapped Task.</summary> + internal readonly Task Task; + /// <summary>The new AsyncState value.</summary> + private readonly object m_state; + /// <summary>The new CompletedSynchronously value.</summary> + private readonly bool m_completedSynchronously; + + /// <summary>Initializes the IAsyncResult with the Task to wrap and the overriding AsyncState and CompletedSynchronously values.</summary> + /// <param name="task">The Task to wrap.</param> + /// <param name="state">The new AsyncState value</param> + /// <param name="completedSynchronously">The new CompletedSynchronously value.</param> + internal TaskWrapperAsyncResult(Task task, object state, bool completedSynchronously) + { + Contract.Requires(task != null); + Contract.Requires(!completedSynchronously || task.IsCompleted, "If completedSynchronously is true, the task must be completed."); + + this.Task = task; + m_state = state; + m_completedSynchronously = completedSynchronously; + } + + // The IAsyncResult implementation. + // - IsCompleted and AsyncWaitHandle just pass through to the Task. + // - AsyncState and CompletedSynchronously return the corresponding values stored in this object. + + object IAsyncResult.AsyncState { get { return m_state; } } + bool IAsyncResult.CompletedSynchronously { get { return m_completedSynchronously; } } + bool IAsyncResult.IsCompleted { get { return this.Task.IsCompleted; } } + WaitHandle IAsyncResult.AsyncWaitHandle { get { return ((IAsyncResult)this.Task).AsyncWaitHandle; } } + } + } +} diff --git a/src/mscorlib/src/System/Threading/Tasks/ThreadPoolTaskScheduler.cs b/src/mscorlib/src/System/Threading/Tasks/ThreadPoolTaskScheduler.cs new file mode 100644 index 0000000000..dd4cbc9a66 --- /dev/null +++ b/src/mscorlib/src/System/Threading/Tasks/ThreadPoolTaskScheduler.cs @@ -0,0 +1,139 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +// =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ +// +// TaskScheduler.cs +// +// +// This file contains the primary interface and management of tasks and queues. +// +// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + +using System; +using System.Security; +using System.Diagnostics.Contracts; +using System.Collections.Generic; +using System.Text; + +namespace System.Threading.Tasks +{ + /// <summary> + /// An implementation of TaskScheduler that uses the ThreadPool scheduler + /// </summary> + internal sealed class ThreadPoolTaskScheduler: TaskScheduler + { + /// <summary> + /// Constructs a new ThreadPool task scheduler object + /// </summary> + internal ThreadPoolTaskScheduler() + { + int id = base.Id; // force ID creation of the default scheduler + } + + // static delegate for threads allocated to handle LongRunning tasks. + private static readonly ParameterizedThreadStart s_longRunningThreadWork = new ParameterizedThreadStart(LongRunningThreadWork); + + private static void LongRunningThreadWork(object obj) + { + Contract.Requires(obj != null, "TaskScheduler.LongRunningThreadWork: obj is null"); + Task t = obj as Task; + Contract.Assert(t != null, "TaskScheduler.LongRunningThreadWork: t is null"); + t.ExecuteEntry(false); + } + + /// <summary> + /// Schedules a task to the ThreadPool. + /// </summary> + /// <param name="task">The task to schedule.</param> + [SecurityCritical] + protected internal override void QueueTask(Task task) + { + if ((task.Options & TaskCreationOptions.LongRunning) != 0) + { + // Run LongRunning tasks on their own dedicated thread. + Thread thread = new Thread(s_longRunningThreadWork); + thread.IsBackground = true; // Keep this thread from blocking process shutdown + thread.Start(task); + } + else + { + // Normal handling for non-LongRunning tasks. + bool forceToGlobalQueue = ((task.Options & TaskCreationOptions.PreferFairness) != 0); + ThreadPool.UnsafeQueueCustomWorkItem(task, forceToGlobalQueue); + } + } + + /// <summary> + /// This internal function will do this: + /// (1) If the task had previously been queued, attempt to pop it and return false if that fails. + /// (2) Propagate the return value from Task.ExecuteEntry() back to the caller. + /// + /// IMPORTANT NOTE: TryExecuteTaskInline will NOT throw task exceptions itself. Any wait code path using this function needs + /// to account for exceptions that need to be propagated, and throw themselves accordingly. + /// </summary> + [SecurityCritical] + protected override bool TryExecuteTaskInline(Task task, bool taskWasPreviouslyQueued) + { + // If the task was previously scheduled, and we can't pop it, then return false. + if (taskWasPreviouslyQueued && !ThreadPool.TryPopCustomWorkItem(task)) + return false; + + // Propagate the return value of Task.ExecuteEntry() + bool rval = false; + try + { + rval = task.ExecuteEntry(false); // handles switching Task.Current etc. + } + finally + { + // Only call NWIP() if task was previously queued + if(taskWasPreviouslyQueued) NotifyWorkItemProgress(); + } + + return rval; + } + + [SecurityCritical] + protected internal override bool TryDequeue(Task task) + { + // just delegate to TP + return ThreadPool.TryPopCustomWorkItem(task); + } + + [SecurityCritical] + protected override IEnumerable<Task> GetScheduledTasks() + { + return FilterTasksFromWorkItems(ThreadPool.GetQueuedWorkItems()); + } + + private IEnumerable<Task> FilterTasksFromWorkItems(IEnumerable<IThreadPoolWorkItem> tpwItems) + { + foreach (IThreadPoolWorkItem tpwi in tpwItems) + { + if (tpwi is Task) + { + yield return (Task)tpwi; + } + } + } + + /// <summary> + /// Notifies the scheduler that work is progressing (no-op). + /// </summary> + internal override void NotifyWorkItemProgress() + { + ThreadPool.NotifyWorkItemProgress(); + } + + /// <summary> + /// This is the only scheduler that returns false for this property, indicating that the task entry codepath is unsafe (CAS free) + /// since we know that the underlying scheduler already takes care of atomic transitions from queued to non-queued. + /// </summary> + internal override bool RequiresAtomicStartTransition + { + get { return false; } + } + } +} diff --git a/src/mscorlib/src/System/Threading/Tasks/future.cs b/src/mscorlib/src/System/Threading/Tasks/future.cs new file mode 100644 index 0000000000..39e6ca1d45 --- /dev/null +++ b/src/mscorlib/src/System/Threading/Tasks/future.cs @@ -0,0 +1,1667 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +// =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ +// +// +// +// A task that produces a value. +// +// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + +using System; +using System.Collections.Generic; +using System.Runtime; +using System.Runtime.CompilerServices; +using System.Runtime.ExceptionServices; +using System.Security; +using System.Security.Permissions; +using System.Threading; +using System.Diagnostics; +using System.Diagnostics.Contracts; + +// Disable the "reference to volatile field not treated as volatile" error. +#pragma warning disable 0420 + +namespace System.Threading.Tasks +{ + /// <summary> + /// Represents an asynchronous operation that produces a result at some time in the future. + /// </summary> + /// <typeparam name="TResult"> + /// The type of the result produced by this <see cref="Task{TResult}"/>. + /// </typeparam> + /// <remarks> + /// <para> + /// <see cref="Task{TResult}"/> instances may be created in a variety of ways. The most common approach is by + /// using the task's <see cref="Factory"/> property to retrieve a <see + /// cref="System.Threading.Tasks.TaskFactory{TResult}"/> instance that can be used to create tasks for several + /// purposes. For example, to create a <see cref="Task{TResult}"/> that runs a function, the factory's StartNew + /// method may be used: + /// <code> + /// // C# + /// var t = Task<int>.Factory.StartNew(() => GenerateResult()); + /// - or - + /// var t = Task.Factory.StartNew(() => GenerateResult()); + /// + /// ' Visual Basic + /// Dim t = Task<int>.Factory.StartNew(Function() GenerateResult()) + /// - or - + /// Dim t = Task.Factory.StartNew(Function() GenerateResult()) + /// </code> + /// </para> + /// <para> + /// The <see cref="Task{TResult}"/> class also provides constructors that initialize the task but that do not + /// schedule it for execution. For performance reasons, the StartNew method should be the + /// preferred mechanism for creating and scheduling computational tasks, but for scenarios where creation + /// and scheduling must be separated, the constructors may be used, and the task's + /// <see cref="System.Threading.Tasks.Task.Start()">Start</see> + /// method may then be used to schedule the task for execution at a later time. + /// </para> + /// <para> + /// All members of <see cref="Task{TResult}"/>, except for + /// <see cref="System.Threading.Tasks.Task.Dispose()">Dispose</see>, are thread-safe + /// and may be used from multiple threads concurrently. + /// </para> + /// </remarks> + [HostProtection(Synchronization = true, ExternalThreading = true)] + [DebuggerTypeProxy(typeof(SystemThreadingTasks_FutureDebugView<>))] + [DebuggerDisplay("Id = {Id}, Status = {Status}, Method = {DebuggerDisplayMethodDescription}, Result = {DebuggerDisplayResultDescription}")] + public class Task<TResult> : Task +#if SUPPORT_IOBSERVABLE + , IObservable<TResult> +#endif + { + internal TResult m_result; // The value itself, if set. + + private static readonly TaskFactory<TResult> s_Factory = new TaskFactory<TResult>(); + + // Delegate used by: + // public static Task<Task<TResult>> WhenAny<TResult>(IEnumerable<Task<TResult>> tasks); + // public static Task<Task<TResult>> WhenAny<TResult>(params Task<TResult>[] tasks); + // Used to "cast" from Task<Task> to Task<Task<TResult>>. + internal static readonly Func<Task<Task>, Task<TResult>> TaskWhenAnyCast = completed => (Task<TResult>)completed.Result; + + // Construct a promise-style task without any options. + internal Task() : + base() + { + } + + // Construct a promise-style task with state and options. + internal Task(object state, TaskCreationOptions options) : + base(state, options, promiseStyle:true) + { + } + + + // Construct a pre-completed Task<TResult> + internal Task(TResult result) : + base(false, TaskCreationOptions.None, default(CancellationToken)) + { + m_result = result; + } + + internal Task(bool canceled, TResult result, TaskCreationOptions creationOptions, CancellationToken ct) + : base(canceled, creationOptions, ct) + { + if (!canceled) + { + m_result = result; + } + } + + // Uncomment if/when we want Task.FromException + //// Construct a pre-faulted Task<TResult> + //internal Task(Exception exception) + // : base(exception) + //{ + //} + + /// <summary> + /// Initializes a new <see cref="Task{TResult}"/> with the specified function. + /// </summary> + /// <param name="function"> + /// The delegate that represents the code to execute in the task. When the function has completed, + /// the task's <see cref="Result"/> property will be set to return the result value of the function. + /// </param> + /// <exception cref="T:System.ArgumentException"> + /// The <paramref name="function"/> argument is null. + /// </exception> + [MethodImplAttribute(MethodImplOptions.NoInlining)] // Methods containing StackCrawlMark local var have to be marked non-inlineable + public Task(Func<TResult> function) + : this(function, null, default(CancellationToken), + TaskCreationOptions.None, InternalTaskOptions.None, null) + { + StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller; + PossiblyCaptureContext(ref stackMark); + } + + + /// <summary> + /// Initializes a new <see cref="Task{TResult}"/> with the specified function. + /// </summary> + /// <param name="function"> + /// The delegate that represents the code to execute in the task. When the function has completed, + /// the task's <see cref="Result"/> property will be set to return the result value of the function. + /// </param> + /// <param name="cancellationToken">The <see cref="CancellationToken"/> to be assigned to this task.</param> + /// <exception cref="T:System.ArgumentException"> + /// The <paramref name="function"/> argument is null. + /// </exception> + /// <exception cref="T:System.ObjectDisposedException">The provided <see cref="System.Threading.CancellationToken">CancellationToken</see> + /// has already been disposed. + /// </exception> + [MethodImplAttribute(MethodImplOptions.NoInlining)] // Methods containing StackCrawlMark local var have to be marked non-inlineable + public Task(Func<TResult> function, CancellationToken cancellationToken) + : this(function, null, cancellationToken, + TaskCreationOptions.None, InternalTaskOptions.None, null) + { + StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller; + PossiblyCaptureContext(ref stackMark); + } + + /// <summary> + /// Initializes a new <see cref="Task{TResult}"/> with the specified function and creation options. + /// </summary> + /// <param name="function"> + /// The delegate that represents the code to execute in the task. When the function has completed, + /// the task's <see cref="Result"/> property will be set to return the result value of the function. + /// </param> + /// <param name="creationOptions"> + /// The <see cref="System.Threading.Tasks.TaskCreationOptions">TaskCreationOptions</see> used to + /// customize the task's behavior. + /// </param> + /// <exception cref="T:System.ArgumentException"> + /// The <paramref name="function"/> argument is null. + /// </exception> + /// <exception cref="T:System.ArgumentOutOfRangeException"> + /// The <paramref name="creationOptions"/> argument specifies an invalid value for <see + /// cref="T:System.Threading.Tasks.TaskCreationOptions"/>. + /// </exception> + [MethodImplAttribute(MethodImplOptions.NoInlining)] // Methods containing StackCrawlMark local var have to be marked non-inlineable + public Task(Func<TResult> function, TaskCreationOptions creationOptions) + : this(function, Task.InternalCurrentIfAttached(creationOptions), default(CancellationToken), creationOptions, InternalTaskOptions.None, null) + { + StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller; + PossiblyCaptureContext(ref stackMark); + } + + /// <summary> + /// Initializes a new <see cref="Task{TResult}"/> with the specified function and creation options. + /// </summary> + /// <param name="function"> + /// The delegate that represents the code to execute in the task. When the function has completed, + /// the task's <see cref="Result"/> property will be set to return the result value of the function. + /// </param> + /// <param name="cancellationToken">The <see cref="CancellationToken"/> that will be assigned to the new task.</param> + /// <param name="creationOptions"> + /// The <see cref="System.Threading.Tasks.TaskCreationOptions">TaskCreationOptions</see> used to + /// customize the task's behavior. + /// </param> + /// <exception cref="T:System.ArgumentException"> + /// The <paramref name="function"/> argument is null. + /// </exception> + /// <exception cref="T:System.ArgumentOutOfRangeException"> + /// The <paramref name="creationOptions"/> argument specifies an invalid value for <see + /// cref="T:System.Threading.Tasks.TaskCreationOptions"/>. + /// </exception> + /// <exception cref="T:System.ObjectDisposedException">The provided <see cref="System.Threading.CancellationToken">CancellationToken</see> + /// has already been disposed. + /// </exception> + [MethodImplAttribute(MethodImplOptions.NoInlining)] // Methods containing StackCrawlMark local var have to be marked non-inlineable + public Task(Func<TResult> function, CancellationToken cancellationToken, TaskCreationOptions creationOptions) + : this(function, Task.InternalCurrentIfAttached(creationOptions), cancellationToken, creationOptions, InternalTaskOptions.None, null) + { + StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller; + PossiblyCaptureContext(ref stackMark); + } + + /// <summary> + /// Initializes a new <see cref="Task{TResult}"/> with the specified function and state. + /// </summary> + /// <param name="function"> + /// The delegate that represents the code to execute in the task. When the function has completed, + /// the task's <see cref="Result"/> property will be set to return the result value of the function. + /// </param> + /// <param name="state">An object representing data to be used by the action.</param> + /// <exception cref="T:System.ArgumentException"> + /// The <paramref name="function"/> argument is null. + /// </exception> + [MethodImplAttribute(MethodImplOptions.NoInlining)] // Methods containing StackCrawlMark local var have to be marked non-inlineable + public Task(Func<object, TResult> function, object state) + : this(function, state, null, default(CancellationToken), + TaskCreationOptions.None, InternalTaskOptions.None, null) + { + StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller; + PossiblyCaptureContext(ref stackMark); + } + + /// <summary> + /// Initializes a new <see cref="Task{TResult}"/> with the specified action, state, and options. + /// </summary> + /// <param name="function"> + /// The delegate that represents the code to execute in the task. When the function has completed, + /// the task's <see cref="Result"/> property will be set to return the result value of the function. + /// </param> + /// <param name="state">An object representing data to be used by the function.</param> + /// <param name="cancellationToken">The <see cref="CancellationToken"/> to be assigned to the new task.</param> + /// <exception cref="T:System.ArgumentException"> + /// The <paramref name="function"/> argument is null. + /// </exception> + /// <exception cref="T:System.ObjectDisposedException">The provided <see cref="System.Threading.CancellationToken">CancellationToken</see> + /// has already been disposed. + /// </exception> + [MethodImplAttribute(MethodImplOptions.NoInlining)] // Methods containing StackCrawlMark local var have to be marked non-inlineable + public Task(Func<object, TResult> function, object state, CancellationToken cancellationToken) + : this(function, state, null, cancellationToken, + TaskCreationOptions.None, InternalTaskOptions.None, null) + { + StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller; + PossiblyCaptureContext(ref stackMark); + } + + /// <summary> + /// Initializes a new <see cref="Task{TResult}"/> with the specified action, state, and options. + /// </summary> + /// <param name="function"> + /// The delegate that represents the code to execute in the task. When the function has completed, + /// the task's <see cref="Result"/> property will be set to return the result value of the function. + /// </param> + /// <param name="state">An object representing data to be used by the function.</param> + /// <param name="creationOptions"> + /// The <see cref="System.Threading.Tasks.TaskCreationOptions">TaskCreationOptions</see> used to + /// customize the task's behavior. + /// </param> + /// <exception cref="T:System.ArgumentException"> + /// The <paramref name="function"/> argument is null. + /// </exception> + /// <exception cref="T:System.ArgumentOutOfRangeException"> + /// The <paramref name="creationOptions"/> argument specifies an invalid value for <see + /// cref="T:System.Threading.Tasks.TaskCreationOptions"/>. + /// </exception> + [MethodImplAttribute(MethodImplOptions.NoInlining)] // Methods containing StackCrawlMark local var have to be marked non-inlineable + public Task(Func<object, TResult> function, object state, TaskCreationOptions creationOptions) + : this(function, state, Task.InternalCurrentIfAttached(creationOptions), default(CancellationToken), + creationOptions, InternalTaskOptions.None, null) + { + StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller; + PossiblyCaptureContext(ref stackMark); + } + + + /// <summary> + /// Initializes a new <see cref="Task{TResult}"/> with the specified action, state, and options. + /// </summary> + /// <param name="function"> + /// The delegate that represents the code to execute in the task. When the function has completed, + /// the task's <see cref="Result"/> property will be set to return the result value of the function. + /// </param> + /// <param name="state">An object representing data to be used by the function.</param> + /// <param name="cancellationToken">The <see cref="CancellationToken"/> to be assigned to the new task.</param> + /// <param name="creationOptions"> + /// The <see cref="System.Threading.Tasks.TaskCreationOptions">TaskCreationOptions</see> used to + /// customize the task's behavior. + /// </param> + /// <exception cref="T:System.ArgumentException"> + /// The <paramref name="function"/> argument is null. + /// </exception> + /// <exception cref="T:System.ArgumentOutOfRangeException"> + /// The <paramref name="creationOptions"/> argument specifies an invalid value for <see + /// cref="T:System.Threading.Tasks.TaskCreationOptions"/>. + /// </exception> + /// <exception cref="T:System.ObjectDisposedException">The provided <see cref="System.Threading.CancellationToken">CancellationToken</see> + /// has already been disposed. + /// </exception> + [MethodImplAttribute(MethodImplOptions.NoInlining)] // Methods containing StackCrawlMark local var have to be marked non-inlineable + public Task(Func<object, TResult> function, object state, CancellationToken cancellationToken, TaskCreationOptions creationOptions) + : this(function, state, Task.InternalCurrentIfAttached(creationOptions), cancellationToken, + creationOptions, InternalTaskOptions.None, null) + { + StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller; + PossiblyCaptureContext(ref stackMark); + } + + internal Task( + Func<TResult> valueSelector, Task parent, CancellationToken cancellationToken, + TaskCreationOptions creationOptions, InternalTaskOptions internalOptions, TaskScheduler scheduler, + ref StackCrawlMark stackMark) : + this(valueSelector, parent, cancellationToken, + creationOptions, internalOptions, scheduler) + { + PossiblyCaptureContext(ref stackMark); + } + + /// <summary> + /// Creates a new future object. + /// </summary> + /// <param name="parent">The parent task for this future.</param> + /// <param name="valueSelector">A function that yields the future value.</param> + /// <param name="scheduler">The task scheduler which will be used to execute the future.</param> + /// <param name="cancellationToken">The CancellationToken for the task.</param> + /// <param name="creationOptions">Options to control the future's behavior.</param> + /// <param name="internalOptions">Internal options to control the future's behavior.</param> + /// <exception cref="T:System.ArgumentOutOfRangeException">The <paramref name="creationOptions"/> argument specifies + /// a SelfReplicating <see cref="Task{TResult}"/>, which is illegal."/>.</exception> + internal Task(Func<TResult> valueSelector, Task parent, CancellationToken cancellationToken, + TaskCreationOptions creationOptions, InternalTaskOptions internalOptions, TaskScheduler scheduler) : + base(valueSelector, null, parent, cancellationToken, creationOptions, internalOptions, scheduler) + { + if ((internalOptions & InternalTaskOptions.SelfReplicating) != 0) + { + ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.creationOptions, ExceptionResource.TaskT_ctor_SelfReplicating); + } + } + + internal Task( + Func<object, TResult> valueSelector, object state, Task parent, CancellationToken cancellationToken, + TaskCreationOptions creationOptions, InternalTaskOptions internalOptions, TaskScheduler scheduler, ref StackCrawlMark stackMark) : + this(valueSelector, state, parent, cancellationToken, creationOptions, internalOptions, scheduler) + { + PossiblyCaptureContext(ref stackMark); + } + + /// <summary> + /// Creates a new future object. + /// </summary> + /// <param name="parent">The parent task for this future.</param> + /// <param name="state">An object containing data to be used by the action; may be null.</param> + /// <param name="valueSelector">A function that yields the future value.</param> + /// <param name="cancellationToken">The CancellationToken for the task.</param> + /// <param name="scheduler">The task scheduler which will be used to execute the future.</param> + /// <param name="creationOptions">Options to control the future's behavior.</param> + /// <param name="internalOptions">Internal options to control the future's behavior.</param> + /// <exception cref="T:System.ArgumentOutOfRangeException">The <paramref name="creationOptions"/> argument specifies + /// a SelfReplicating <see cref="Task{TResult}"/>, which is illegal."/>.</exception> + internal Task(Delegate valueSelector, object state, Task parent, CancellationToken cancellationToken, + TaskCreationOptions creationOptions, InternalTaskOptions internalOptions, TaskScheduler scheduler) : + base(valueSelector, state, parent, cancellationToken, creationOptions, internalOptions, scheduler) + { + if ((internalOptions & InternalTaskOptions.SelfReplicating) != 0) + { + ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.creationOptions, ExceptionResource.TaskT_ctor_SelfReplicating); + } + } + + + // Internal method used by TaskFactory<TResult>.StartNew() methods + internal static Task<TResult> StartNew(Task parent, Func<TResult> function, CancellationToken cancellationToken, + TaskCreationOptions creationOptions, InternalTaskOptions internalOptions, TaskScheduler scheduler, ref StackCrawlMark stackMark) + { + if (function == null) + { + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.function); + } + if (scheduler == null) + { + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.scheduler); + } + if ((internalOptions & InternalTaskOptions.SelfReplicating) != 0) + { + ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.creationOptions, ExceptionResource.TaskT_ctor_SelfReplicating); + } + + // Create and schedule the future. + Task<TResult> f = new Task<TResult>(function, parent, cancellationToken, creationOptions, internalOptions | InternalTaskOptions.QueuedByRuntime, scheduler, ref stackMark); + + f.ScheduleAndStart(false); + return f; + } + + // Internal method used by TaskFactory<TResult>.StartNew() methods + internal static Task<TResult> StartNew(Task parent, Func<object, TResult> function, object state, CancellationToken cancellationToken, + TaskCreationOptions creationOptions, InternalTaskOptions internalOptions, TaskScheduler scheduler, ref StackCrawlMark stackMark) + { + if (function == null) + { + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.function); + } + if (scheduler == null) + { + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.scheduler); + } + if ((internalOptions & InternalTaskOptions.SelfReplicating) != 0) + { + ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.creationOptions, ExceptionResource.TaskT_ctor_SelfReplicating); + } + + // Create and schedule the future. + Task<TResult> f = new Task<TResult>(function, state, parent, cancellationToken, creationOptions, internalOptions | InternalTaskOptions.QueuedByRuntime, scheduler, ref stackMark); + + f.ScheduleAndStart(false); + return f; + } + + // Debugger support + private string DebuggerDisplayResultDescription + { + get + { + return IsRanToCompletion ? "" + m_result : Environment.GetResourceString("TaskT_DebuggerNoResult"); + } + } + + // Debugger support + private string DebuggerDisplayMethodDescription + { + get + { + Delegate d = (Delegate)m_action; + return d != null ? d.Method.ToString() : "{null}"; + } + } + + + // internal helper function breaks out logic used by TaskCompletionSource + internal bool TrySetResult(TResult result) + { + if (IsCompleted) return false; + Contract.Assert(m_action == null, "Task<T>.TrySetResult(): non-null m_action"); + + // "Reserve" the completion for this task, while making sure that: (1) No prior reservation + // has been made, (2) The result has not already been set, (3) An exception has not previously + // been recorded, and (4) Cancellation has not been requested. + // + // If the reservation is successful, then set the result and finish completion processing. + if (AtomicStateUpdate(TASK_STATE_COMPLETION_RESERVED, + TASK_STATE_COMPLETION_RESERVED | TASK_STATE_RAN_TO_COMPLETION | TASK_STATE_FAULTED | TASK_STATE_CANCELED)) + { + m_result = result; + + // Signal completion, for waiting tasks + + // This logic used to be: + // Finish(false); + // However, that goes through a windy code path, involves many non-inlineable functions + // and which can be summarized more concisely with the following snippet from + // FinishStageTwo, omitting everything that doesn't pertain to TrySetResult. + Interlocked.Exchange(ref m_stateFlags, m_stateFlags | TASK_STATE_RAN_TO_COMPLETION); + + var cp = m_contingentProperties; + if (cp != null) cp.SetCompleted(); + + FinishStageThree(); + + return true; + } + + return false; + } + + // Transitions the promise task into a successfully completed state with the specified result. + // This is dangerous, as no synchronization is used, and thus must only be used + // before this task is handed out to any consumers, before any continuations are hooked up, + // before its wait handle is accessed, etc. It's use is limited to places like in FromAsync + // where the operation completes synchronously, and thus we know we can forcefully complete + // the task, avoiding expensive completion paths, before the task is actually given to anyone. + internal void DangerousSetResult(TResult result) + { + Contract.Assert(!IsCompleted, "The promise must not yet be completed."); + + // If we have a parent, we need to notify it of the completion. Take the slow path to handle that. + if (m_contingentProperties?.m_parent != null) + { + bool success = TrySetResult(result); + + // Nobody else has had a chance to complete this Task yet, so we should succeed. + Contract.Assert(success); + } + else + { + m_result = result; + m_stateFlags |= TASK_STATE_RAN_TO_COMPLETION; + } + } + + /// <summary> + /// Gets the result value of this <see cref="Task{TResult}"/>. + /// </summary> + /// <remarks> + /// The get accessor for this property ensures that the asynchronous operation is complete before + /// returning. Once the result of the computation is available, it is stored and will be returned + /// immediately on later calls to <see cref="Result"/>. + /// </remarks> + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + public TResult Result + { + get { return IsWaitNotificationEnabledOrNotRanToCompletion ? GetResultCore(waitCompletionNotification: true) : m_result; } + } + + /// <summary> + /// Gets the result value of this <see cref="Task{TResult}"/> once the task has completed successfully. + /// </summary> + /// <remarks> + /// This version of Result should only be used if the task completed successfully and if there's + /// no debugger wait notification enabled for this task. + /// </remarks> + internal TResult ResultOnSuccess + { + get + { + Contract.Assert(!IsWaitNotificationEnabledOrNotRanToCompletion, + "Should only be used when the task completed successfully and there's no wait notification enabled"); + return m_result; + } + } + + // Implements Result. Result delegates to this method if the result isn't already available. + internal TResult GetResultCore(bool waitCompletionNotification) + { + // If the result has not been calculated yet, wait for it. + if (!IsCompleted) InternalWait(Timeout.Infinite, default(CancellationToken)); // won't throw if task faulted or canceled; that's handled below + + // Notify the debugger of the wait completion if it's requested such a notification + if (waitCompletionNotification) NotifyDebuggerOfWaitCompletionIfNecessary(); + + // Throw an exception if appropriate. + if (!IsRanToCompletion) ThrowIfExceptional(includeTaskCanceledExceptions: true); + + // We shouldn't be here if the result has not been set. + Contract.Assert(IsRanToCompletion, "Task<T>.Result getter: Expected result to have been set."); + + return m_result; + } + + // Allow multiple exceptions to be assigned to a promise-style task. + // This is useful when a TaskCompletionSource<T> stands in as a proxy + // for a "real" task (as we do in Unwrap(), ContinueWhenAny() and ContinueWhenAll()) + // and the "real" task ends up with multiple exceptions, which is possible when + // a task has children. + // + // Called from TaskCompletionSource<T>.SetException(IEnumerable<Exception>). + internal bool TrySetException(object exceptionObject) + { + Contract.Assert(m_action == null, "Task<T>.TrySetException(): non-null m_action"); + + // TCS.{Try}SetException() should have checked for this + Contract.Assert(exceptionObject != null, "Expected non-null exceptionObject argument"); + + // Only accept these types. + Contract.Assert( + (exceptionObject is Exception) || (exceptionObject is IEnumerable<Exception>) || + (exceptionObject is ExceptionDispatchInfo) || (exceptionObject is IEnumerable<ExceptionDispatchInfo>), + "Expected exceptionObject to be either Exception, ExceptionDispatchInfo, or IEnumerable<> of one of those"); + + bool returnValue = false; + + // "Reserve" the completion for this task, while making sure that: (1) No prior reservation + // has been made, (2) The result has not already been set, (3) An exception has not previously + // been recorded, and (4) Cancellation has not been requested. + // + // If the reservation is successful, then add the exception(s) and finish completion processing. + // + // The lazy initialization may not be strictly necessary, but I'd like to keep it here + // anyway. Some downstream logic may depend upon an inflated m_contingentProperties. + EnsureContingentPropertiesInitialized(); + if (AtomicStateUpdate(TASK_STATE_COMPLETION_RESERVED, + TASK_STATE_COMPLETION_RESERVED | TASK_STATE_RAN_TO_COMPLETION | TASK_STATE_FAULTED | TASK_STATE_CANCELED)) + { + AddException(exceptionObject); // handles singleton exception or exception collection + Finish(false); + returnValue = true; + } + + return returnValue; + + } + + // internal helper function breaks out logic used by TaskCompletionSource and AsyncMethodBuilder + // If the tokenToRecord is not None, it will be stored onto the task. + // This method is only valid for promise tasks. + internal bool TrySetCanceled(CancellationToken tokenToRecord) + { + return TrySetCanceled(tokenToRecord, null); + } + + // internal helper function breaks out logic used by TaskCompletionSource and AsyncMethodBuilder + // If the tokenToRecord is not None, it will be stored onto the task. + // If the OperationCanceledException is not null, it will be stored into the task's exception holder. + // This method is only valid for promise tasks. + internal bool TrySetCanceled(CancellationToken tokenToRecord, object cancellationException) + { + Contract.Assert(m_action == null, "Task<T>.TrySetCanceled(): non-null m_action"); +#if DEBUG + var ceAsEdi = cancellationException as ExceptionDispatchInfo; + Contract.Assert( + cancellationException == null || + cancellationException is OperationCanceledException || + (ceAsEdi != null && ceAsEdi.SourceException is OperationCanceledException), + "Expected null or an OperationCanceledException"); +#endif + + bool returnValue = false; + + // "Reserve" the completion for this task, while making sure that: (1) No prior reservation + // has been made, (2) The result has not already been set, (3) An exception has not previously + // been recorded, and (4) Cancellation has not been requested. + // + // If the reservation is successful, then record the cancellation and finish completion processing. + // + // Note: I had to access static Task variables through Task<object> + // instead of Task, because I have a property named Task and that + // was confusing the compiler. + if (AtomicStateUpdate(Task<object>.TASK_STATE_COMPLETION_RESERVED, + Task<object>.TASK_STATE_COMPLETION_RESERVED | Task<object>.TASK_STATE_CANCELED | + Task<object>.TASK_STATE_FAULTED | Task<object>.TASK_STATE_RAN_TO_COMPLETION)) + { + RecordInternalCancellationRequest(tokenToRecord, cancellationException); + CancellationCleanupLogic(); // perform cancellation cleanup actions + returnValue = true; + } + + return returnValue; + } + + /// <summary> + /// Provides access to factory methods for creating <see cref="Task{TResult}"/> instances. + /// </summary> + /// <remarks> + /// The factory returned from <see cref="Factory"/> is a default instance + /// of <see cref="System.Threading.Tasks.TaskFactory{TResult}"/>, as would result from using + /// the default constructor on the factory type. + /// </remarks> + public new static TaskFactory<TResult> Factory { get { return s_Factory; } } + + /// <summary> + /// Evaluates the value selector of the Task which is passed in as an object and stores the result. + /// </summary> + internal override void InnerInvoke() + { + // Invoke the delegate + Contract.Assert(m_action != null); + var func = m_action as Func<TResult>; + if (func != null) + { + m_result = func(); + return; + } + var funcWithState = m_action as Func<object, TResult>; + if (funcWithState != null) + { + m_result = funcWithState(m_stateObject); + return; + } + Contract.Assert(false, "Invalid m_action in Task<TResult>"); + } + + #region Await Support + + /// <summary>Gets an awaiter used to await this <see cref="System.Threading.Tasks.Task{TResult}"/>.</summary> + /// <returns>An awaiter instance.</returns> + /// <remarks>This method is intended for compiler user rather than use directly in code.</remarks> + public new TaskAwaiter<TResult> GetAwaiter() + { + return new TaskAwaiter<TResult>(this); + } + + /// <summary>Configures an awaiter used to await this <see cref="System.Threading.Tasks.Task{TResult}"/>.</summary> + /// <param name="continueOnCapturedContext"> + /// true to attempt to marshal the continuation back to the original context captured; otherwise, false. + /// </param> + /// <returns>An object used to await this task.</returns> + public new ConfiguredTaskAwaitable<TResult> ConfigureAwait(bool continueOnCapturedContext) + { + return new ConfiguredTaskAwaitable<TResult>(this, continueOnCapturedContext); + } + + #endregion + + #region Continuation methods + + #region Action<Task<TResult>> continuations + + /// <summary> + /// Creates a continuation that executes when the target <see cref="Task{TResult}"/> completes. + /// </summary> + /// <param name="continuationAction"> + /// An action to run when the <see cref="Task{TResult}"/> completes. When run, the delegate will be + /// passed the completed task as an argument. + /// </param> + /// <returns>A new continuation <see cref="Task"/>.</returns> + /// <remarks> + /// The returned <see cref="Task"/> will not be scheduled for execution until the current task has + /// completed, whether it completes due to running to completion successfully, faulting due to an + /// unhandled exception, or exiting out early due to being canceled. + /// </remarks> + /// <exception cref="T:System.ArgumentNullException"> + /// The <paramref name="continuationAction"/> argument is null. + /// </exception> + [MethodImplAttribute(MethodImplOptions.NoInlining)] // Methods containing StackCrawlMark local var have to be marked non-inlineable + public Task ContinueWith(Action<Task<TResult>> continuationAction) + { + StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller; + return ContinueWith(continuationAction, TaskScheduler.Current, default(CancellationToken), TaskContinuationOptions.None, ref stackMark); + } + + + /// <summary> + /// Creates a continuation that executes when the target <see cref="Task{TResult}"/> completes. + /// </summary> + /// <param name="continuationAction"> + /// An action to run when the <see cref="Task{TResult}"/> completes. When run, the delegate will be + /// passed the completed task as an argument. + /// </param> + /// <param name="cancellationToken">The <see cref="CancellationToken"/> that will be assigned to the new continuation task.</param> + /// <returns>A new continuation <see cref="Task"/>.</returns> + /// <remarks> + /// The returned <see cref="Task"/> will not be scheduled for execution until the current task has + /// completed, whether it completes due to running to completion successfully, faulting due to an + /// unhandled exception, or exiting out early due to being canceled. + /// </remarks> + /// <exception cref="T:System.ArgumentNullException"> + /// The <paramref name="continuationAction"/> argument is null. + /// </exception> + /// <exception cref="T:System.ObjectDisposedException">The provided <see cref="System.Threading.CancellationToken">CancellationToken</see> + /// has already been disposed. + /// </exception> + [MethodImplAttribute(MethodImplOptions.NoInlining)] // Methods containing StackCrawlMark local var have to be marked non-inlineable + public Task ContinueWith(Action<Task<TResult>> continuationAction, CancellationToken cancellationToken) + { + StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller; + return ContinueWith(continuationAction, TaskScheduler.Current, cancellationToken, TaskContinuationOptions.None, ref stackMark); + } + + + /// <summary> + /// Creates a continuation that executes when the target <see cref="Task{TResult}"/> completes. + /// </summary> + /// <param name="continuationAction"> + /// An action to run when the <see cref="Task{TResult}"/> completes. When run, the delegate will be + /// passed the completed task as an argument. + /// </param> + /// <param name="scheduler"> + /// The <see cref="TaskScheduler"/> to associate with the continuation task and to use for its execution. + /// </param> + /// <returns>A new continuation <see cref="Task"/>.</returns> + /// <remarks> + /// The returned <see cref="Task"/> will not be scheduled for execution until the current task has + /// completed, whether it completes due to running to completion successfully, faulting due to an + /// unhandled exception, or exiting out early due to being canceled. + /// </remarks> + /// <exception cref="T:System.ArgumentNullException"> + /// The <paramref name="continuationAction"/> argument is null. + /// </exception> + /// <exception cref="T:System.ArgumentNullException"> + /// The <paramref name="scheduler"/> argument is null. + /// </exception> + [MethodImplAttribute(MethodImplOptions.NoInlining)] // Methods containing StackCrawlMark local var have to be marked non-inlineable + public Task ContinueWith(Action<Task<TResult>> continuationAction, TaskScheduler scheduler) + { + StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller; + return ContinueWith(continuationAction, scheduler, default(CancellationToken), TaskContinuationOptions.None, ref stackMark); + } + + /// <summary> + /// Creates a continuation that executes when the target <see cref="Task{TResult}"/> completes. + /// </summary> + /// <param name="continuationAction"> + /// An action to run when the <see cref="Task{TResult}"/> completes. When run, the delegate will be + /// passed the completed task as an argument. + /// </param> + /// <param name="continuationOptions"> + /// Options for when the continuation is scheduled and how it behaves. This includes criteria, such + /// as <see + /// cref="System.Threading.Tasks.TaskContinuationOptions.OnlyOnCanceled">OnlyOnCanceled</see>, as + /// well as execution options, such as <see + /// cref="System.Threading.Tasks.TaskContinuationOptions.ExecuteSynchronously">ExecuteSynchronously</see>. + /// </param> + /// <returns>A new continuation <see cref="Task"/>.</returns> + /// <remarks> + /// The returned <see cref="Task"/> will not be scheduled for execution until the current task has + /// completed. If the continuation criteria specified through the <paramref + /// name="continuationOptions"/> parameter are not met, the continuation task will be canceled + /// instead of scheduled. + /// </remarks> + /// <exception cref="T:System.ArgumentNullException"> + /// The <paramref name="continuationAction"/> argument is null. + /// </exception> + /// <exception cref="T:System.ArgumentOutOfRangeException"> + /// The <paramref name="continuationOptions"/> argument specifies an invalid value for <see + /// cref="T:System.Threading.Tasks.TaskContinuationOptions">TaskContinuationOptions</see>. + /// </exception> + [MethodImplAttribute(MethodImplOptions.NoInlining)] // Methods containing StackCrawlMark local var have to be marked non-inlineable + public Task ContinueWith(Action<Task<TResult>> continuationAction, TaskContinuationOptions continuationOptions) + { + StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller; + return ContinueWith(continuationAction, TaskScheduler.Current, default(CancellationToken), continuationOptions, ref stackMark); + } + + /// <summary> + /// Creates a continuation that executes when the target <see cref="Task{TResult}"/> completes. + /// </summary> + /// <param name="continuationAction"> + /// An action to run when the <see cref="Task{TResult}"/> completes. When run, the delegate will be + /// passed the completed task as an argument. + /// </param> + /// <param name="cancellationToken">The <see cref="CancellationToken"/> that will be assigned to the new continuation task.</param> + /// <param name="continuationOptions"> + /// Options for when the continuation is scheduled and how it behaves. This includes criteria, such + /// as <see + /// cref="System.Threading.Tasks.TaskContinuationOptions.OnlyOnCanceled">OnlyOnCanceled</see>, as + /// well as execution options, such as <see + /// cref="System.Threading.Tasks.TaskContinuationOptions.ExecuteSynchronously">ExecuteSynchronously</see>. + /// </param> + /// <param name="scheduler"> + /// The <see cref="TaskScheduler"/> to associate with the continuation task and to use for its + /// execution. + /// </param> + /// <returns>A new continuation <see cref="Task"/>.</returns> + /// <remarks> + /// The returned <see cref="Task"/> will not be scheduled for execution until the current task has + /// completed. If the criteria specified through the <paramref name="continuationOptions"/> parameter + /// are not met, the continuation task will be canceled instead of scheduled. + /// </remarks> + /// <exception cref="T:System.ArgumentNullException"> + /// The <paramref name="continuationAction"/> argument is null. + /// </exception> + /// <exception cref="T:System.ArgumentOutOfRangeException"> + /// The <paramref name="continuationOptions"/> argument specifies an invalid value for <see + /// cref="T:System.Threading.Tasks.TaskContinuationOptions">TaskContinuationOptions</see>. + /// </exception> + /// <exception cref="T:System.ArgumentNullException"> + /// The <paramref name="scheduler"/> argument is null. + /// </exception> + /// <exception cref="T:System.ObjectDisposedException">The provided <see cref="System.Threading.CancellationToken">CancellationToken</see> + /// has already been disposed. + /// </exception> + [MethodImplAttribute(MethodImplOptions.NoInlining)] // Methods containing StackCrawlMark local var have to be marked non-inlineable + public Task ContinueWith(Action<Task<TResult>> continuationAction, CancellationToken cancellationToken, + TaskContinuationOptions continuationOptions, TaskScheduler scheduler) + { + StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller; + return ContinueWith(continuationAction, scheduler, cancellationToken, continuationOptions, ref stackMark); + } + + // Same as the above overload, only with a stack mark. + internal Task ContinueWith(Action<Task<TResult>> continuationAction, TaskScheduler scheduler, CancellationToken cancellationToken, + TaskContinuationOptions continuationOptions, ref StackCrawlMark stackMark) + { + if (continuationAction == null) + { + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.continuationAction); + } + + if (scheduler == null) + { + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.scheduler); + } + + TaskCreationOptions creationOptions; + InternalTaskOptions internalOptions; + CreationOptionsFromContinuationOptions( + continuationOptions, + out creationOptions, + out internalOptions); + + Task continuationTask = new ContinuationTaskFromResultTask<TResult>( + this, continuationAction, null, + creationOptions, internalOptions, + ref stackMark + ); + + // Register the continuation. If synchronous execution is requested, this may + // actually invoke the continuation before returning. + ContinueWithCore(continuationTask, scheduler, cancellationToken, continuationOptions); + + return continuationTask; + } + #endregion + + #region Action<Task<TResult>, Object> continuations + + /// <summary> + /// Creates a continuation that executes when the target <see cref="Task{TResult}"/> completes. + /// </summary> + /// <param name="continuationAction"> + /// An action to run when the <see cref="Task{TResult}"/> completes. When run, the delegate will be + /// passed the completed task and the caller-supplied state object as arguments. + /// </param> + /// <param name="state">An object representing data to be used by the continuation action.</param> + /// <returns>A new continuation <see cref="Task"/>.</returns> + /// <remarks> + /// The returned <see cref="Task"/> will not be scheduled for execution until the current task has + /// completed, whether it completes due to running to completion successfully, faulting due to an + /// unhandled exception, or exiting out early due to being canceled. + /// </remarks> + /// <exception cref="T:System.ArgumentNullException"> + /// The <paramref name="continuationAction"/> argument is null. + /// </exception> + [MethodImplAttribute(MethodImplOptions.NoInlining)] // Methods containing StackCrawlMark local var have to be marked non-inlineable + public Task ContinueWith(Action<Task<TResult>, Object> continuationAction, Object state) + { + StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller; + return ContinueWith(continuationAction, state, TaskScheduler.Current, default(CancellationToken), TaskContinuationOptions.None, ref stackMark); + } + + + /// <summary> + /// Creates a continuation that executes when the target <see cref="Task{TResult}"/> completes. + /// </summary> + /// <param name="continuationAction"> + /// An action to run when the <see cref="Task{TResult}"/> completes. When run, the delegate will be + /// passed the completed task and the caller-supplied state object as arguments. + /// </param> + /// <param name="state">An object representing data to be used by the continuation action.</param> + /// <param name="cancellationToken">The <see cref="CancellationToken"/> that will be assigned to the new continuation task.</param> + /// <returns>A new continuation <see cref="Task"/>.</returns> + /// <remarks> + /// The returned <see cref="Task"/> will not be scheduled for execution until the current task has + /// completed, whether it completes due to running to completion successfully, faulting due to an + /// unhandled exception, or exiting out early due to being canceled. + /// </remarks> + /// <exception cref="T:System.ArgumentNullException"> + /// The <paramref name="continuationAction"/> argument is null. + /// </exception> + /// <exception cref="T:System.ObjectDisposedException">The provided <see cref="System.Threading.CancellationToken">CancellationToken</see> + /// has already been disposed. + /// </exception> + [MethodImplAttribute(MethodImplOptions.NoInlining)] // Methods containing StackCrawlMark local var have to be marked non-inlineable + public Task ContinueWith(Action<Task<TResult>, Object> continuationAction, Object state,CancellationToken cancellationToken) + { + StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller; + return ContinueWith(continuationAction, state, TaskScheduler.Current, cancellationToken, TaskContinuationOptions.None, ref stackMark); + } + + + /// <summary> + /// Creates a continuation that executes when the target <see cref="Task{TResult}"/> completes. + /// </summary> + /// <param name="continuationAction"> + /// An action to run when the <see cref="Task{TResult}"/> completes. When run, the delegate will be + /// passed the completed task and the caller-supplied state object as arguments. + /// </param> + /// <param name="state">An object representing data to be used by the continuation action.</param> + /// <param name="scheduler"> + /// The <see cref="TaskScheduler"/> to associate with the continuation task and to use for its execution. + /// </param> + /// <returns>A new continuation <see cref="Task"/>.</returns> + /// <remarks> + /// The returned <see cref="Task"/> will not be scheduled for execution until the current task has + /// completed, whether it completes due to running to completion successfully, faulting due to an + /// unhandled exception, or exiting out early due to being canceled. + /// </remarks> + /// <exception cref="T:System.ArgumentNullException"> + /// The <paramref name="continuationAction"/> argument is null. + /// </exception> + /// <exception cref="T:System.ArgumentNullException"> + /// The <paramref name="scheduler"/> argument is null. + /// </exception> + [MethodImplAttribute(MethodImplOptions.NoInlining)] // Methods containing StackCrawlMark local var have to be marked non-inlineable + public Task ContinueWith(Action<Task<TResult>, Object> continuationAction, Object state, TaskScheduler scheduler) + { + StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller; + return ContinueWith(continuationAction, state, scheduler, default(CancellationToken), TaskContinuationOptions.None, ref stackMark); + } + + /// <summary> + /// Creates a continuation that executes when the target <see cref="Task{TResult}"/> completes. + /// </summary> + /// <param name="continuationAction"> + /// An action to run when the <see cref="Task{TResult}"/> completes. When run, the delegate will be + /// passed the completed task and the caller-supplied state object as arguments. + /// </param> + /// <param name="state">An object representing data to be used by the continuation action.</param> + /// <param name="continuationOptions"> + /// Options for when the continuation is scheduled and how it behaves. This includes criteria, such + /// as <see + /// cref="System.Threading.Tasks.TaskContinuationOptions.OnlyOnCanceled">OnlyOnCanceled</see>, as + /// well as execution options, such as <see + /// cref="System.Threading.Tasks.TaskContinuationOptions.ExecuteSynchronously">ExecuteSynchronously</see>. + /// </param> + /// <returns>A new continuation <see cref="Task"/>.</returns> + /// <remarks> + /// The returned <see cref="Task"/> will not be scheduled for execution until the current task has + /// completed. If the continuation criteria specified through the <paramref + /// name="continuationOptions"/> parameter are not met, the continuation task will be canceled + /// instead of scheduled. + /// </remarks> + /// <exception cref="T:System.ArgumentNullException"> + /// The <paramref name="continuationAction"/> argument is null. + /// </exception> + /// <exception cref="T:System.ArgumentOutOfRangeException"> + /// The <paramref name="continuationOptions"/> argument specifies an invalid value for <see + /// cref="T:System.Threading.Tasks.TaskContinuationOptions">TaskContinuationOptions</see>. + /// </exception> + [MethodImplAttribute(MethodImplOptions.NoInlining)] // Methods containing StackCrawlMark local var have to be marked non-inlineable + public Task ContinueWith(Action<Task<TResult>, Object> continuationAction, Object state,TaskContinuationOptions continuationOptions) + { + StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller; + return ContinueWith(continuationAction, state, TaskScheduler.Current, default(CancellationToken), continuationOptions, ref stackMark); + } + + /// <summary> + /// Creates a continuation that executes when the target <see cref="Task{TResult}"/> completes. + /// </summary> + /// <param name="continuationAction"> + /// An action to run when the <see cref="Task{TResult}"/> completes. When run, the delegate will be + /// passed the completed task and the caller-supplied state object as arguments. + /// </param> + /// <param name="state">An object representing data to be used by the continuation action.</param> + /// <param name="cancellationToken">The <see cref="CancellationToken"/> that will be assigned to the new continuation task.</param> + /// <param name="continuationOptions"> + /// Options for when the continuation is scheduled and how it behaves. This includes criteria, such + /// as <see + /// cref="System.Threading.Tasks.TaskContinuationOptions.OnlyOnCanceled">OnlyOnCanceled</see>, as + /// well as execution options, such as <see + /// cref="System.Threading.Tasks.TaskContinuationOptions.ExecuteSynchronously">ExecuteSynchronously</see>. + /// </param> + /// <param name="scheduler"> + /// The <see cref="TaskScheduler"/> to associate with the continuation task and to use for its + /// execution. + /// </param> + /// <returns>A new continuation <see cref="Task"/>.</returns> + /// <remarks> + /// The returned <see cref="Task"/> will not be scheduled for execution until the current task has + /// completed. If the criteria specified through the <paramref name="continuationOptions"/> parameter + /// are not met, the continuation task will be canceled instead of scheduled. + /// </remarks> + /// <exception cref="T:System.ArgumentNullException"> + /// The <paramref name="continuationAction"/> argument is null. + /// </exception> + /// <exception cref="T:System.ArgumentOutOfRangeException"> + /// The <paramref name="continuationOptions"/> argument specifies an invalid value for <see + /// cref="T:System.Threading.Tasks.TaskContinuationOptions">TaskContinuationOptions</see>. + /// </exception> + /// <exception cref="T:System.ArgumentNullException"> + /// The <paramref name="scheduler"/> argument is null. + /// </exception> + /// <exception cref="T:System.ObjectDisposedException">The provided <see cref="System.Threading.CancellationToken">CancellationToken</see> + /// has already been disposed. + /// </exception> + [MethodImplAttribute(MethodImplOptions.NoInlining)] // Methods containing StackCrawlMark local var have to be marked non-inlineable + public Task ContinueWith(Action<Task<TResult>, Object> continuationAction, Object state, CancellationToken cancellationToken, + TaskContinuationOptions continuationOptions, TaskScheduler scheduler) + { + StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller; + return ContinueWith(continuationAction, state, scheduler, cancellationToken, continuationOptions, ref stackMark); + } + + // Same as the above overload, only with a stack mark. + internal Task ContinueWith(Action<Task<TResult>, Object> continuationAction, Object state, TaskScheduler scheduler, CancellationToken cancellationToken, + TaskContinuationOptions continuationOptions, ref StackCrawlMark stackMark) + { + if (continuationAction == null) + { + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.continuationAction); + } + + if (scheduler == null) + { + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.scheduler); + } + + TaskCreationOptions creationOptions; + InternalTaskOptions internalOptions; + CreationOptionsFromContinuationOptions( + continuationOptions, + out creationOptions, + out internalOptions); + + Task continuationTask = new ContinuationTaskFromResultTask<TResult>( + this, continuationAction, state, + creationOptions, internalOptions, + ref stackMark + ); + + // Register the continuation. If synchronous execution is requested, this may + // actually invoke the continuation before returning. + ContinueWithCore(continuationTask, scheduler, cancellationToken, continuationOptions); + + return continuationTask; + } + + #endregion + + #region Func<Task<TResult>,TNewResult> continuations + + /// <summary> + /// Creates a continuation that executes when the target <see cref="Task{TResult}"/> completes. + /// </summary> + /// <typeparam name="TNewResult"> + /// The type of the result produced by the continuation. + /// </typeparam> + /// <param name="continuationFunction"> + /// A function to run when the <see cref="Task{TResult}"/> completes. When run, the delegate will be + /// passed the completed task as an argument. + /// </param> + /// <returns>A new continuation <see cref="Task{TNewResult}"/>.</returns> + /// <remarks> + /// The returned <see cref="Task{TNewResult}"/> will not be scheduled for execution until the current + /// task has completed, whether it completes due to running to completion successfully, faulting due + /// to an unhandled exception, or exiting out early due to being canceled. + /// </remarks> + /// <exception cref="T:System.ArgumentNullException"> + /// The <paramref name="continuationFunction"/> argument is null. + /// </exception> + [MethodImplAttribute(MethodImplOptions.NoInlining)] // Methods containing StackCrawlMark local var have to be marked non-inlineable + public Task<TNewResult> ContinueWith<TNewResult>(Func<Task<TResult>, TNewResult> continuationFunction) + { + StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller; + return ContinueWith<TNewResult>(continuationFunction, TaskScheduler.Current, default(CancellationToken), TaskContinuationOptions.None, ref stackMark); + } + + + /// <summary> + /// Creates a continuation that executes when the target <see cref="Task{TResult}"/> completes. + /// </summary> + /// <typeparam name="TNewResult"> + /// The type of the result produced by the continuation. + /// </typeparam> + /// <param name="continuationFunction"> + /// A function to run when the <see cref="Task{TResult}"/> completes. When run, the delegate will be + /// passed the completed task as an argument. + /// </param> + /// <param name="cancellationToken">The <see cref="CancellationToken"/> that will be assigned to the new task.</param> + /// <returns>A new continuation <see cref="Task{TNewResult}"/>.</returns> + /// <remarks> + /// The returned <see cref="Task{TNewResult}"/> will not be scheduled for execution until the current + /// task has completed, whether it completes due to running to completion successfully, faulting due + /// to an unhandled exception, or exiting out early due to being canceled. + /// </remarks> + /// <exception cref="T:System.ArgumentNullException"> + /// The <paramref name="continuationFunction"/> argument is null. + /// </exception> + /// <exception cref="T:System.ObjectDisposedException">The provided <see cref="System.Threading.CancellationToken">CancellationToken</see> + /// has already been disposed. + /// </exception> + [MethodImplAttribute(MethodImplOptions.NoInlining)] // Methods containing StackCrawlMark local var have to be marked non-inlineable + public Task<TNewResult> ContinueWith<TNewResult>(Func<Task<TResult>, TNewResult> continuationFunction, CancellationToken cancellationToken) + { + StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller; + return ContinueWith<TNewResult>(continuationFunction, TaskScheduler.Current, cancellationToken, TaskContinuationOptions.None, ref stackMark); + } + + /// <summary> + /// Creates a continuation that executes when the target <see cref="Task{TResult}"/> completes. + /// </summary> + /// <typeparam name="TNewResult"> + /// The type of the result produced by the continuation. + /// </typeparam> + /// <param name="continuationFunction"> + /// A function to run when the <see cref="Task{TResult}"/> completes. When run, the delegate will be + /// passed the completed task as an argument. + /// </param> + /// <param name="scheduler"> + /// The <see cref="TaskScheduler"/> to associate with the continuation task and to use for its execution. + /// </param> + /// <returns>A new continuation <see cref="Task{TNewResult}"/>.</returns> + /// <remarks> + /// The returned <see cref="Task{TNewResult}"/> will not be scheduled for execution until the current task has + /// completed, whether it completes due to running to completion successfully, faulting due to an + /// unhandled exception, or exiting out early due to being canceled. + /// </remarks> + /// <exception cref="T:System.ArgumentNullException"> + /// The <paramref name="continuationFunction"/> argument is null. + /// </exception> + /// <exception cref="T:System.ArgumentNullException"> + /// The <paramref name="scheduler"/> argument is null. + /// </exception> + [MethodImplAttribute(MethodImplOptions.NoInlining)] // Methods containing StackCrawlMark local var have to be marked non-inlineable + public Task<TNewResult> ContinueWith<TNewResult>(Func<Task<TResult>, TNewResult> continuationFunction, TaskScheduler scheduler) + { + StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller; + return ContinueWith<TNewResult>(continuationFunction, scheduler, default(CancellationToken), TaskContinuationOptions.None, ref stackMark); + } + + /// <summary> + /// Creates a continuation that executes when the target <see cref="Task{TResult}"/> completes. + /// </summary> + /// <typeparam name="TNewResult"> + /// The type of the result produced by the continuation. + /// </typeparam> + /// <param name="continuationFunction"> + /// A function to run when the <see cref="Task{TResult}"/> completes. When run, the delegate will be + /// passed the completed task as an argument. + /// </param> + /// <param name="continuationOptions"> + /// Options for when the continuation is scheduled and how it behaves. This includes criteria, such + /// as <see + /// cref="System.Threading.Tasks.TaskContinuationOptions.OnlyOnCanceled">OnlyOnCanceled</see>, as + /// well as execution options, such as <see + /// cref="System.Threading.Tasks.TaskContinuationOptions.ExecuteSynchronously">ExecuteSynchronously</see>. + /// </param> + /// <returns>A new continuation <see cref="Task{TNewResult}"/>.</returns> + /// <remarks> + /// <para> + /// The returned <see cref="Task{TNewResult}"/> will not be scheduled for execution until the current + /// task has completed, whether it completes due to running to completion successfully, faulting due + /// to an unhandled exception, or exiting out early due to being canceled. + /// </para> + /// <para> + /// The <paramref name="continuationFunction"/>, when executed, should return a <see + /// cref="Task{TNewResult}"/>. This task's completion state will be transferred to the task returned + /// from the ContinueWith call. + /// </para> + /// </remarks> + /// <exception cref="T:System.ArgumentNullException"> + /// The <paramref name="continuationFunction"/> argument is null. + /// </exception> + /// <exception cref="T:System.ArgumentOutOfRangeException"> + /// The <paramref name="continuationOptions"/> argument specifies an invalid value for <see + /// cref="T:System.Threading.Tasks.TaskContinuationOptions">TaskContinuationOptions</see>. + /// </exception> + [MethodImplAttribute(MethodImplOptions.NoInlining)] // Methods containing StackCrawlMark local var have to be marked non-inlineable + public Task<TNewResult> ContinueWith<TNewResult>(Func<Task<TResult>, TNewResult> continuationFunction, TaskContinuationOptions continuationOptions) + { + StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller; + return ContinueWith<TNewResult>(continuationFunction, TaskScheduler.Current, default(CancellationToken), continuationOptions, ref stackMark); + } + + /// <summary> + /// Creates a continuation that executes when the target <see cref="Task{TResult}"/> completes. + /// </summary> + /// <typeparam name="TNewResult"> + /// The type of the result produced by the continuation. + /// </typeparam> + /// <param name="continuationFunction"> + /// A function to run when the <see cref="Task{TResult}"/> completes. When run, the delegate will be passed as + /// an argument this completed task. + /// </param> + /// <param name="cancellationToken">The <see cref="CancellationToken"/> that will be assigned to the new task.</param> + /// <param name="continuationOptions"> + /// Options for when the continuation is scheduled and how it behaves. This includes criteria, such + /// as <see + /// cref="System.Threading.Tasks.TaskContinuationOptions.OnlyOnCanceled">OnlyOnCanceled</see>, as + /// well as execution options, such as <see + /// cref="System.Threading.Tasks.TaskContinuationOptions.ExecuteSynchronously">ExecuteSynchronously</see>. + /// </param> + /// <param name="scheduler"> + /// The <see cref="TaskScheduler"/> to associate with the continuation task and to use for its + /// execution. + /// </param> + /// <returns>A new continuation <see cref="Task{TNewResult}"/>.</returns> + /// <remarks> + /// <para> + /// The returned <see cref="Task{TNewResult}"/> will not be scheduled for execution until the current task has + /// completed, whether it completes due to running to completion successfully, faulting due to an + /// unhandled exception, or exiting out early due to being canceled. + /// </para> + /// <para> + /// The <paramref name="continuationFunction"/>, when executed, should return a <see cref="Task{TNewResult}"/>. + /// This task's completion state will be transferred to the task returned from the + /// ContinueWith call. + /// </para> + /// </remarks> + /// <exception cref="T:System.ArgumentNullException"> + /// The <paramref name="continuationFunction"/> argument is null. + /// </exception> + /// <exception cref="T:System.ArgumentOutOfRangeException"> + /// The <paramref name="continuationOptions"/> argument specifies an invalid value for <see + /// cref="T:System.Threading.Tasks.TaskContinuationOptions">TaskContinuationOptions</see>. + /// </exception> + /// <exception cref="T:System.ArgumentNullException"> + /// The <paramref name="scheduler"/> argument is null. + /// </exception> + /// <exception cref="T:System.ObjectDisposedException">The provided <see cref="System.Threading.CancellationToken">CancellationToken</see> + /// has already been disposed. + /// </exception> + [MethodImplAttribute(MethodImplOptions.NoInlining)] // Methods containing StackCrawlMark local var have to be marked non-inlineable + public Task<TNewResult> ContinueWith<TNewResult>(Func<Task<TResult>, TNewResult> continuationFunction, CancellationToken cancellationToken, + TaskContinuationOptions continuationOptions, TaskScheduler scheduler) + { + StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller; + return ContinueWith<TNewResult>(continuationFunction, scheduler, cancellationToken, continuationOptions, ref stackMark); + } + + // Same as the above overload, just with a stack mark. + internal Task<TNewResult> ContinueWith<TNewResult>(Func<Task<TResult>, TNewResult> continuationFunction, TaskScheduler scheduler, + CancellationToken cancellationToken, TaskContinuationOptions continuationOptions, ref StackCrawlMark stackMark) + { + if (continuationFunction == null) + { + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.continuationFunction); + } + + if (scheduler == null) + { + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.scheduler); + } + + TaskCreationOptions creationOptions; + InternalTaskOptions internalOptions; + CreationOptionsFromContinuationOptions( + continuationOptions, + out creationOptions, + out internalOptions); + + Task<TNewResult> continuationFuture = new ContinuationResultTaskFromResultTask<TResult,TNewResult>( + this, continuationFunction, null, + creationOptions, internalOptions, + ref stackMark + ); + + // Register the continuation. If synchronous execution is requested, this may + // actually invoke the continuation before returning. + ContinueWithCore(continuationFuture, scheduler, cancellationToken, continuationOptions); + + return continuationFuture; + } + #endregion + + #region Func<Task<TResult>, Object,TNewResult> continuations + + /// <summary> + /// Creates a continuation that executes when the target <see cref="Task{TResult}"/> completes. + /// </summary> + /// <typeparam name="TNewResult"> + /// The type of the result produced by the continuation. + /// </typeparam> + /// <param name="continuationFunction"> + /// A function to run when the <see cref="Task{TResult}"/> completes. When run, the delegate will be + /// passed the completed task and the caller-supplied state object as arguments. + /// </param> + /// <param name="state">An object representing data to be used by the continuation function.</param> + /// <returns>A new continuation <see cref="Task{TNewResult}"/>.</returns> + /// <remarks> + /// The returned <see cref="Task{TNewResult}"/> will not be scheduled for execution until the current + /// task has completed, whether it completes due to running to completion successfully, faulting due + /// to an unhandled exception, or exiting out early due to being canceled. + /// </remarks> + /// <exception cref="T:System.ArgumentNullException"> + /// The <paramref name="continuationFunction"/> argument is null. + /// </exception> + [MethodImplAttribute(MethodImplOptions.NoInlining)] // Methods containing StackCrawlMark local var have to be marked non-inlineable + public Task<TNewResult> ContinueWith<TNewResult>(Func<Task<TResult>, Object, TNewResult> continuationFunction, Object state) + { + StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller; + return ContinueWith<TNewResult>(continuationFunction, state, TaskScheduler.Current, default(CancellationToken), TaskContinuationOptions.None, ref stackMark); + } + + + /// <summary> + /// Creates a continuation that executes when the target <see cref="Task{TResult}"/> completes. + /// </summary> + /// <typeparam name="TNewResult"> + /// The type of the result produced by the continuation. + /// </typeparam> + /// <param name="continuationFunction"> + /// A function to run when the <see cref="Task{TResult}"/> completes. When run, the delegate will be + /// passed the completed task and the caller-supplied state object as arguments. + /// </param> + /// <param name="state">An object representing data to be used by the continuation function.</param> + /// <param name="cancellationToken">The <see cref="CancellationToken"/> that will be assigned to the new task.</param> + /// <returns>A new continuation <see cref="Task{TNewResult}"/>.</returns> + /// <remarks> + /// The returned <see cref="Task{TNewResult}"/> will not be scheduled for execution until the current + /// task has completed, whether it completes due to running to completion successfully, faulting due + /// to an unhandled exception, or exiting out early due to being canceled. + /// </remarks> + /// <exception cref="T:System.ArgumentNullException"> + /// The <paramref name="continuationFunction"/> argument is null. + /// </exception> + /// <exception cref="T:System.ObjectDisposedException">The provided <see cref="System.Threading.CancellationToken">CancellationToken</see> + /// has already been disposed. + /// </exception> + [MethodImplAttribute(MethodImplOptions.NoInlining)] // Methods containing StackCrawlMark local var have to be marked non-inlineable + public Task<TNewResult> ContinueWith<TNewResult>(Func<Task<TResult>, Object, TNewResult> continuationFunction, Object state, + CancellationToken cancellationToken) + { + StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller; + return ContinueWith<TNewResult>(continuationFunction, state, TaskScheduler.Current, cancellationToken, TaskContinuationOptions.None, ref stackMark); + } + + /// <summary> + /// Creates a continuation that executes when the target <see cref="Task{TResult}"/> completes. + /// </summary> + /// <typeparam name="TNewResult"> + /// The type of the result produced by the continuation. + /// </typeparam> + /// <param name="continuationFunction"> + /// A function to run when the <see cref="Task{TResult}"/> completes. When run, the delegate will be + /// passed the completed task and the caller-supplied state object as arguments. + /// </param> + /// <param name="state">An object representing data to be used by the continuation function.</param> + /// <param name="scheduler"> + /// The <see cref="TaskScheduler"/> to associate with the continuation task and to use for its execution. + /// </param> + /// <returns>A new continuation <see cref="Task{TNewResult}"/>.</returns> + /// <remarks> + /// The returned <see cref="Task{TNewResult}"/> will not be scheduled for execution until the current task has + /// completed, whether it completes due to running to completion successfully, faulting due to an + /// unhandled exception, or exiting out early due to being canceled. + /// </remarks> + /// <exception cref="T:System.ArgumentNullException"> + /// The <paramref name="continuationFunction"/> argument is null. + /// </exception> + /// <exception cref="T:System.ArgumentNullException"> + /// The <paramref name="scheduler"/> argument is null. + /// </exception> + [MethodImplAttribute(MethodImplOptions.NoInlining)] // Methods containing StackCrawlMark local var have to be marked non-inlineable + public Task<TNewResult> ContinueWith<TNewResult>(Func<Task<TResult>, Object, TNewResult> continuationFunction, Object state, + TaskScheduler scheduler) + { + StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller; + return ContinueWith<TNewResult>(continuationFunction, state, scheduler, default(CancellationToken), TaskContinuationOptions.None, ref stackMark); + } + + /// <summary> + /// Creates a continuation that executes when the target <see cref="Task{TResult}"/> completes. + /// </summary> + /// <typeparam name="TNewResult"> + /// The type of the result produced by the continuation. + /// </typeparam> + /// <param name="continuationFunction"> + /// A function to run when the <see cref="Task{TResult}"/> completes. When run, the delegate will be + /// passed the completed task and the caller-supplied state object as arguments. + /// </param> + /// <param name="state">An object representing data to be used by the continuation function.</param> + /// <param name="continuationOptions"> + /// Options for when the continuation is scheduled and how it behaves. This includes criteria, such + /// as <see + /// cref="System.Threading.Tasks.TaskContinuationOptions.OnlyOnCanceled">OnlyOnCanceled</see>, as + /// well as execution options, such as <see + /// cref="System.Threading.Tasks.TaskContinuationOptions.ExecuteSynchronously">ExecuteSynchronously</see>. + /// </param> + /// <returns>A new continuation <see cref="Task{TNewResult}"/>.</returns> + /// <remarks> + /// <para> + /// The returned <see cref="Task{TNewResult}"/> will not be scheduled for execution until the current + /// task has completed, whether it completes due to running to completion successfully, faulting due + /// to an unhandled exception, or exiting out early due to being canceled. + /// </para> + /// <para> + /// The <paramref name="continuationFunction"/>, when executed, should return a <see + /// cref="Task{TNewResult}"/>. This task's completion state will be transferred to the task returned + /// from the ContinueWith call. + /// </para> + /// </remarks> + /// <exception cref="T:System.ArgumentNullException"> + /// The <paramref name="continuationFunction"/> argument is null. + /// </exception> + /// <exception cref="T:System.ArgumentOutOfRangeException"> + /// The <paramref name="continuationOptions"/> argument specifies an invalid value for <see + /// cref="T:System.Threading.Tasks.TaskContinuationOptions">TaskContinuationOptions</see>. + /// </exception> + [MethodImplAttribute(MethodImplOptions.NoInlining)] // Methods containing StackCrawlMark local var have to be marked non-inlineable + public Task<TNewResult> ContinueWith<TNewResult>(Func<Task<TResult>, Object, TNewResult> continuationFunction, Object state, + TaskContinuationOptions continuationOptions) + { + StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller; + return ContinueWith<TNewResult>(continuationFunction, state, TaskScheduler.Current, default(CancellationToken), continuationOptions, ref stackMark); + } + + /// <summary> + /// Creates a continuation that executes when the target <see cref="Task{TResult}"/> completes. + /// </summary> + /// <typeparam name="TNewResult"> + /// The type of the result produced by the continuation. + /// </typeparam> + /// <param name="continuationFunction"> + /// A function to run when the <see cref="Task{TResult}"/> completes. When run, the delegate will be + /// passed the completed task and the caller-supplied state object as arguments. + /// </param> + /// <param name="state">An object representing data to be used by the continuation function.</param> + /// <param name="cancellationToken">The <see cref="CancellationToken"/> that will be assigned to the new task.</param> + /// <param name="continuationOptions"> + /// Options for when the continuation is scheduled and how it behaves. This includes criteria, such + /// as <see + /// cref="System.Threading.Tasks.TaskContinuationOptions.OnlyOnCanceled">OnlyOnCanceled</see>, as + /// well as execution options, such as <see + /// cref="System.Threading.Tasks.TaskContinuationOptions.ExecuteSynchronously">ExecuteSynchronously</see>. + /// </param> + /// <param name="scheduler"> + /// The <see cref="TaskScheduler"/> to associate with the continuation task and to use for its + /// execution. + /// </param> + /// <returns>A new continuation <see cref="Task{TNewResult}"/>.</returns> + /// <remarks> + /// <para> + /// The returned <see cref="Task{TNewResult}"/> will not be scheduled for execution until the current task has + /// completed, whether it completes due to running to completion successfully, faulting due to an + /// unhandled exception, or exiting out early due to being canceled. + /// </para> + /// <para> + /// The <paramref name="continuationFunction"/>, when executed, should return a <see cref="Task{TNewResult}"/>. + /// This task's completion state will be transferred to the task returned from the + /// ContinueWith call. + /// </para> + /// </remarks> + /// <exception cref="T:System.ArgumentNullException"> + /// The <paramref name="continuationFunction"/> argument is null. + /// </exception> + /// <exception cref="T:System.ArgumentOutOfRangeException"> + /// The <paramref name="continuationOptions"/> argument specifies an invalid value for <see + /// cref="T:System.Threading.Tasks.TaskContinuationOptions">TaskContinuationOptions</see>. + /// </exception> + /// <exception cref="T:System.ArgumentNullException"> + /// The <paramref name="scheduler"/> argument is null. + /// </exception> + /// <exception cref="T:System.ObjectDisposedException">The provided <see cref="System.Threading.CancellationToken">CancellationToken</see> + /// has already been disposed. + /// </exception> + [MethodImplAttribute(MethodImplOptions.NoInlining)] // Methods containing StackCrawlMark local var have to be marked non-inlineable + public Task<TNewResult> ContinueWith<TNewResult>(Func<Task<TResult>, Object, TNewResult> continuationFunction, Object state, + CancellationToken cancellationToken, TaskContinuationOptions continuationOptions, TaskScheduler scheduler) + { + StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller; + return ContinueWith<TNewResult>(continuationFunction, state, scheduler, cancellationToken, continuationOptions, ref stackMark); + } + + // Same as the above overload, just with a stack mark. + internal Task<TNewResult> ContinueWith<TNewResult>(Func<Task<TResult>, Object, TNewResult> continuationFunction, Object state, + TaskScheduler scheduler, CancellationToken cancellationToken, TaskContinuationOptions continuationOptions, ref StackCrawlMark stackMark) + { + if (continuationFunction == null) + { + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.continuationFunction); + } + + if (scheduler == null) + { + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.scheduler); + } + + TaskCreationOptions creationOptions; + InternalTaskOptions internalOptions; + CreationOptionsFromContinuationOptions( + continuationOptions, + out creationOptions, + out internalOptions); + + Task<TNewResult> continuationFuture = new ContinuationResultTaskFromResultTask<TResult,TNewResult>( + this, continuationFunction, state, + creationOptions, internalOptions, + ref stackMark + ); + + // Register the continuation. If synchronous execution is requested, this may + // actually invoke the continuation before returning. + ContinueWithCore(continuationFuture, scheduler, cancellationToken, continuationOptions); + + return continuationFuture; + } + + #endregion + + #endregion + + /// <summary> + /// Subscribes an <see cref="IObserver{TResult}"/> to receive notification of the final state of this <see cref="Task{TResult}"/>. + /// </summary> + /// <param name="observer"> + /// The <see cref="IObserver{TResult}"/> to call on task completion. If this Task throws an exception, + /// observer.OnError is called with this Task's AggregateException. If this Task RanToCompletion, + /// observer.OnNext is called with this Task's result, followed by a call to observer.OnCompleted. + /// If this Task is Canceled, observer.OnError is called with a TaskCanceledException + /// containing this Task's CancellationToken + /// </param> + /// <returns>An IDisposable object <see cref="Task"/>.</returns> + /// <exception cref="T:System.ArgumentNullException"> + /// The <paramref name="observer"/> argument is null. + /// </exception> +#if SUPPORT_IOBSERVABLE + IDisposable IObservable<TResult>.Subscribe(IObserver<TResult> observer) + { + if (observer == null) + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.observer); + + + var continuationTask = + this.ContinueWith(delegate(Task<TResult> observedTask, object taskObserverObject) + { + IObserver<TResult> taskObserver = (IObserver<TResult>)taskObserverObject; + if (observedTask.IsFaulted) + taskObserver.OnError(observedTask.Exception); + else if (observedTask.IsCanceled) + taskObserver.OnError(new TaskCanceledException(observedTask)); + else + { + taskObserver.OnNext(observedTask.Result); + taskObserver.OnCompleted(); + } + + }, observer, TaskScheduler.Default); + + return new DisposableSubscription(this, continuationTask); + } +#endif + } + +#if SUPPORT_IOBSERVABLE + // Class that calls RemoveContinuation if Dispose() is called before task completion + internal class DisposableSubscription : IDisposable + { + private Task _notifyObserverContinuationTask; + private Task _observedTask; + + internal DisposableSubscription(Task observedTask, Task notifyObserverContinuationTask) + { + _observedTask = observedTask; + _notifyObserverContinuationTask = notifyObserverContinuationTask; + } + void IDisposable.Dispose() + { + Task localObservedTask = _observedTask; + Task localNotifyingContinuationTask = _notifyObserverContinuationTask; + if (localObservedTask != null && localNotifyingContinuationTask != null && !localObservedTask.IsCompleted) + { + localObservedTask.RemoveContinuation(localNotifyingContinuationTask); + } + _observedTask = null; + _notifyObserverContinuationTask = null; + } + } +#endif + + // Proxy class for better debugging experience + internal class SystemThreadingTasks_FutureDebugView<TResult> + { + private Task<TResult> m_task; + + public SystemThreadingTasks_FutureDebugView(Task<TResult> task) + { + m_task = task; + } + + public TResult Result { get { return m_task.Status == TaskStatus.RanToCompletion ? m_task.Result : default(TResult); } } + public object AsyncState { get { return m_task.AsyncState; } } + public TaskCreationOptions CreationOptions { get { return m_task.CreationOptions; } } + public Exception Exception { get { return m_task.Exception; } } + public int Id { get { return m_task.Id; } } + public bool CancellationPending { get { return (m_task.Status == TaskStatus.WaitingToRun) && m_task.CancellationToken.IsCancellationRequested; } } + public TaskStatus Status { get { return m_task.Status; } } + + + } +} diff --git a/src/mscorlib/src/System/Threading/Thread.cs b/src/mscorlib/src/System/Threading/Thread.cs new file mode 100644 index 0000000000..e62cfae9fe --- /dev/null +++ b/src/mscorlib/src/System/Threading/Thread.cs @@ -0,0 +1,1756 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +// +/*============================================================================= +** +** +** +** Purpose: Class for creating and managing a thread. +** +** +=============================================================================*/ + +namespace System.Threading { + using System.Threading; + using System.Runtime; + using System.Runtime.InteropServices; +#if FEATURE_REMOTING + using System.Runtime.Remoting.Contexts; + using System.Runtime.Remoting.Messaging; +#endif + using System; + using System.Diagnostics; + using System.Security.Permissions; + using System.Security.Principal; + using System.Globalization; + using System.Collections.Generic; + using System.Runtime.Serialization; + using System.Runtime.CompilerServices; + using System.Runtime.ConstrainedExecution; + using System.Security; + using System.Runtime.Versioning; + using System.Diagnostics.Contracts; + + internal delegate Object InternalCrossContextDelegate(Object[] args); + + internal class ThreadHelper + { + [System.Security.SecuritySafeCritical] + static ThreadHelper() {} + + Delegate _start; + Object _startArg = null; + ExecutionContext _executionContext = null; + internal ThreadHelper(Delegate start) + { + _start = start; + } + + internal void SetExecutionContextHelper(ExecutionContext ec) + { + _executionContext = ec; + } + + [System.Security.SecurityCritical] + static internal ContextCallback _ccb = new ContextCallback(ThreadStart_Context); + + [System.Security.SecurityCritical] + static private void ThreadStart_Context(Object state) + { + ThreadHelper t = (ThreadHelper)state; + if (t._start is ThreadStart) + { + ((ThreadStart)t._start)(); + } + else + { + ((ParameterizedThreadStart)t._start)(t._startArg); + } + } + + // call back helper + #if FEATURE_CORECLR + [System.Security.SecuritySafeCritical] // auto-generated + #else + [System.Security.SecurityCritical] + #endif + internal void ThreadStart(object obj) + { + _startArg = obj; + if (_executionContext != null) + { + ExecutionContext.Run(_executionContext, _ccb, (Object)this); + } + else + { + ((ParameterizedThreadStart)_start)(obj); + } + } + + // call back helper + #if FEATURE_CORECLR + [System.Security.SecuritySafeCritical] // auto-generated + #else + [System.Security.SecurityCritical] + #endif + internal void ThreadStart() + { + if (_executionContext != null) + { + ExecutionContext.Run(_executionContext, _ccb, (Object)this); + } + else + { + ((ThreadStart)_start)(); + } + } + } + + internal struct ThreadHandle + { + private IntPtr m_ptr; + + internal ThreadHandle(IntPtr pThread) + { + m_ptr = pThread; + } + } + + // deliberately not [serializable] + [ClassInterface(ClassInterfaceType.None)] + [ComDefaultInterface(typeof(_Thread))] +[System.Runtime.InteropServices.ComVisible(true)] + public sealed class Thread : CriticalFinalizerObject, _Thread + { + /*========================================================================= + ** Data accessed from managed code that needs to be defined in + ** ThreadBaseObject to maintain alignment between the two classes. + ** DON'T CHANGE THESE UNLESS YOU MODIFY ThreadBaseObject in vm\object.h + =========================================================================*/ +#if FEATURE_REMOTING + private Context m_Context; +#endif + private ExecutionContext m_ExecutionContext; // this call context follows the logical thread +#if FEATURE_CORECLR + private SynchronizationContext m_SynchronizationContext; // On CoreCLR, this is maintained separately from ExecutionContext +#endif + + private String m_Name; + private Delegate m_Delegate; // Delegate + +#if FEATURE_LEAK_CULTURE_INFO + private CultureInfo m_CurrentCulture; + private CultureInfo m_CurrentUICulture; +#endif + private Object m_ThreadStartArg; + + /*========================================================================= + ** The base implementation of Thread is all native. The following fields + ** should never be used in the C# code. They are here to define the proper + ** space so the thread object may be allocated. DON'T CHANGE THESE UNLESS + ** YOU MODIFY ThreadBaseObject in vm\object.h + =========================================================================*/ +#pragma warning disable 169 +#pragma warning disable 414 // These fields are not used from managed. + // IntPtrs need to be together, and before ints, because IntPtrs are 64-bit + // fields on 64-bit platforms, where they will be sorted together. + + private IntPtr DONT_USE_InternalThread; // Pointer + private int m_Priority; // INT32 + private int m_ManagedThreadId; // INT32 + +#pragma warning restore 414 +#pragma warning restore 169 + + private bool m_ExecutionContextBelongsToOuterScope; +#if DEBUG + private bool m_ForbidExecutionContextMutation; +#endif + + /*========================================================================= + ** This manager is responsible for storing the global data that is + ** shared amongst all the thread local stores. + =========================================================================*/ + static private LocalDataStoreMgr s_LocalDataStoreMgr; + + /*========================================================================= + ** Thread-local data store + =========================================================================*/ + [ThreadStatic] + static private LocalDataStoreHolder s_LocalDataStore; + + // Do not move! Order of above fields needs to be preserved for alignment + // with native code + // See code:#threadCultureInfo +#if !FEATURE_LEAK_CULTURE_INFO + [ThreadStatic] + internal static CultureInfo m_CurrentCulture; + [ThreadStatic] + internal static CultureInfo m_CurrentUICulture; +#endif + + static AsyncLocal<CultureInfo> s_asyncLocalCurrentCulture; + static AsyncLocal<CultureInfo> s_asyncLocalCurrentUICulture; + + static void AsyncLocalSetCurrentCulture(AsyncLocalValueChangedArgs<CultureInfo> args) + { +#if FEATURE_LEAK_CULTURE_INFO + Thread.CurrentThread.m_CurrentCulture = args.CurrentValue; +#else + m_CurrentCulture = args.CurrentValue; +#endif // FEATURE_LEAK_CULTURE_INFO + } + + static void AsyncLocalSetCurrentUICulture(AsyncLocalValueChangedArgs<CultureInfo> args) + { +#if FEATURE_LEAK_CULTURE_INFO + Thread.CurrentThread.m_CurrentUICulture = args.CurrentValue; +#else + m_CurrentUICulture = args.CurrentValue; +#endif // FEATURE_LEAK_CULTURE_INFO + } + +#if FEATURE_CORECLR + // Adding an empty default ctor for annotation purposes + [System.Security.SecuritySafeCritical] // auto-generated + internal Thread(){} +#endif // FEATURE_CORECLR + + /*========================================================================= + ** Creates a new Thread object which will begin execution at + ** start.ThreadStart on a new thread when the Start method is called. + ** + ** Exceptions: ArgumentNullException if start == null. + =========================================================================*/ + [System.Security.SecuritySafeCritical] // auto-generated + public Thread(ThreadStart start) { + if (start == null) { + throw new ArgumentNullException("start"); + } + Contract.EndContractBlock(); + SetStartHelper((Delegate)start,0); //0 will setup Thread with default stackSize + } + + [System.Security.SecuritySafeCritical] // auto-generated + public Thread(ThreadStart start, int maxStackSize) { + if (start == null) { + throw new ArgumentNullException("start"); + } + if (0 > maxStackSize) + throw new ArgumentOutOfRangeException("maxStackSize",Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum")); + Contract.EndContractBlock(); + SetStartHelper((Delegate)start, maxStackSize); + } + [System.Security.SecuritySafeCritical] // auto-generated + public Thread(ParameterizedThreadStart start) { + if (start == null) { + throw new ArgumentNullException("start"); + } + Contract.EndContractBlock(); + SetStartHelper((Delegate)start, 0); + } + + [System.Security.SecuritySafeCritical] // auto-generated + public Thread(ParameterizedThreadStart start, int maxStackSize) { + if (start == null) { + throw new ArgumentNullException("start"); + } + if (0 > maxStackSize) + throw new ArgumentOutOfRangeException("maxStackSize",Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum")); + Contract.EndContractBlock(); + SetStartHelper((Delegate)start, maxStackSize); + } + + [ComVisible(false)] + public override int GetHashCode() + { + return m_ManagedThreadId; + } + + extern public int ManagedThreadId + { + [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] + [MethodImplAttribute(MethodImplOptions.InternalCall)] + [System.Security.SecuritySafeCritical] // auto-generated + get; + } + + // Returns handle for interop with EE. The handle is guaranteed to be non-null. + internal unsafe ThreadHandle GetNativeHandle() + { + IntPtr thread = DONT_USE_InternalThread; + + // This should never happen under normal circumstances. m_assembly is always assigned before it is handed out to the user. + // There are ways how to create an unitialized objects through remoting, etc. Avoid AVing in the EE by throwing a nice + // exception here. + if (thread.IsNull()) + throw new ArgumentException(null, Environment.GetResourceString("Argument_InvalidHandle")); + + return new ThreadHandle(thread); + } + + + /*========================================================================= + ** Spawns off a new thread which will begin executing at the ThreadStart + ** method on the IThreadable interface passed in the constructor. Once the + ** thread is dead, it cannot be restarted with another call to Start. + ** + ** Exceptions: ThreadStateException if the thread has already been started. + =========================================================================*/ + [HostProtection(Synchronization=true,ExternalThreading=true)] + [MethodImplAttribute(MethodImplOptions.NoInlining)] // Methods containing StackCrawlMark local var has to be marked non-inlineable + public void Start() + { + StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller; + Start(ref stackMark); + } + + [HostProtection(Synchronization=true,ExternalThreading=true)] + [MethodImplAttribute(MethodImplOptions.NoInlining)] // Methods containing StackCrawlMark local var has to be marked non-inlineable + public void Start(object parameter) + { + //In the case of a null delegate (second call to start on same thread) + // StartInternal method will take care of the error reporting + if(m_Delegate is ThreadStart) + { + //We expect the thread to be setup with a ParameterizedThreadStart + // if this constructor is called. + //If we got here then that wasn't the case + throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_ThreadWrongThreadStart")); + } + m_ThreadStartArg = parameter; + StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller; + Start(ref stackMark); + } + + [System.Security.SecuritySafeCritical] + private void Start(ref StackCrawlMark stackMark) + { +#if FEATURE_COMINTEROP_APARTMENT_SUPPORT + // Eagerly initialize the COM Apartment state of the thread if we're allowed to. + StartupSetApartmentStateInternal(); +#endif // FEATURE_COMINTEROP_APARTMENT_SUPPORT + + // Attach current thread's security principal object to the new + // thread. Be careful not to bind the current thread to a principal + // if it's not already bound. + if (m_Delegate != null) + { + // If we reach here with a null delegate, something is broken. But we'll let the StartInternal method take care of + // reporting an error. Just make sure we dont try to dereference a null delegate. + ThreadHelper t = (ThreadHelper)(m_Delegate.Target); + ExecutionContext ec = ExecutionContext.Capture( + ref stackMark, + ExecutionContext.CaptureOptions.IgnoreSyncCtx); + t.SetExecutionContextHelper(ec); + } +#if FEATURE_IMPERSONATION + IPrincipal principal = (IPrincipal)CallContext.Principal; +#else + IPrincipal principal = null; +#endif + StartInternal(principal, ref stackMark); + } + + +#if FEATURE_CORECLR + internal ExecutionContext ExecutionContext + { + get { return m_ExecutionContext; } + set { m_ExecutionContext = value; } + } + + internal SynchronizationContext SynchronizationContext + { + get { return m_SynchronizationContext; } + set { m_SynchronizationContext = value; } + } +#else // !FEATURE_CORECLR + [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] + internal ExecutionContext.Reader GetExecutionContextReader() + { + return new ExecutionContext.Reader(m_ExecutionContext); + } + + internal bool ExecutionContextBelongsToCurrentScope + { + get { return !m_ExecutionContextBelongsToOuterScope; } + set { m_ExecutionContextBelongsToOuterScope = !value; } + } + +#if DEBUG + internal bool ForbidExecutionContextMutation + { + set { m_ForbidExecutionContextMutation = value; } + } +#endif + + // note: please don't access this directly from mscorlib. Use GetMutableExecutionContext or GetExecutionContextReader instead. + public ExecutionContext ExecutionContext + { + [SecuritySafeCritical] + [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)] + get + { + ExecutionContext result; + if (this == Thread.CurrentThread) + result = GetMutableExecutionContext(); + else + result = m_ExecutionContext; + + return result; + } + } + + [SecurityCritical] + [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)] + internal ExecutionContext GetMutableExecutionContext() + { + Contract.Assert(Thread.CurrentThread == this); +#if DEBUG + Contract.Assert(!m_ForbidExecutionContextMutation); +#endif + if (m_ExecutionContext == null) + { + m_ExecutionContext = new ExecutionContext(); + } + else if (!ExecutionContextBelongsToCurrentScope) + { + ExecutionContext copy = m_ExecutionContext.CreateMutableCopy(); + m_ExecutionContext = copy; + } + + ExecutionContextBelongsToCurrentScope = true; + return m_ExecutionContext; + } + + [SecurityCritical] + [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] + internal void SetExecutionContext(ExecutionContext value, bool belongsToCurrentScope) + { + m_ExecutionContext = value; + ExecutionContextBelongsToCurrentScope = belongsToCurrentScope; + } + + [SecurityCritical] + [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] + internal void SetExecutionContext(ExecutionContext.Reader value, bool belongsToCurrentScope) + { + m_ExecutionContext = value.DangerousGetRawExecutionContext(); + ExecutionContextBelongsToCurrentScope = belongsToCurrentScope; + } +#endif //!FEATURE_CORECLR + + [System.Security.SecurityCritical] // auto-generated + [MethodImplAttribute(MethodImplOptions.InternalCall)] + private extern void StartInternal(IPrincipal principal, ref StackCrawlMark stackMark); +#if FEATURE_COMPRESSEDSTACK + /// <internalonly/> + [System.Security.SecurityCritical] // auto-generated_required + [DynamicSecurityMethodAttribute()] + [Obsolete("Thread.SetCompressedStack is no longer supported. Please use the System.Threading.CompressedStack class")] + public void SetCompressedStack( CompressedStack stack ) + { + throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_ThreadAPIsNotSupported")); + } + + [System.Security.SecurityCritical] // auto-generated + [MethodImplAttribute(MethodImplOptions.InternalCall), ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] + internal extern IntPtr SetAppDomainStack( SafeCompressedStackHandle csHandle); + + [System.Security.SecurityCritical] // auto-generated + [MethodImplAttribute(MethodImplOptions.InternalCall), ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] + internal extern void RestoreAppDomainStack( IntPtr appDomainStack); + + + /// <internalonly/> + [System.Security.SecurityCritical] // auto-generated_required + [Obsolete("Thread.GetCompressedStack is no longer supported. Please use the System.Threading.CompressedStack class")] + public CompressedStack GetCompressedStack() + { + throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_ThreadAPIsNotSupported")); + } +#endif // #if FEATURE_COMPRESSEDSTACK + + + // Helper method to get a logical thread ID for StringBuilder (for + // correctness) and for FileStream's async code path (for perf, to + // avoid creating a Thread instance). + [System.Security.SecurityCritical] // auto-generated + [MethodImplAttribute(MethodImplOptions.InternalCall)] + internal extern static IntPtr InternalGetCurrentThread(); + + /*========================================================================= + ** Raises a ThreadAbortException in the thread, which usually + ** results in the thread's death. The ThreadAbortException is a special + ** exception that is not catchable. The finally clauses of all try + ** statements will be executed before the thread dies. This includes the + ** finally that a thread might be executing at the moment the Abort is raised. + ** The thread is not stopped immediately--you must Join on the + ** thread to guarantee it has stopped. + ** It is possible for a thread to do an unbounded amount of computation in + ** the finally's and thus indefinitely delay the threads death. + ** If Abort() is called on a thread that has not been started, the thread + ** will abort when Start() is called. + ** If Abort is called twice on the same thread, a DuplicateThreadAbort + ** exception is thrown. + =========================================================================*/ + +#if !FEATURE_CORECLR + [System.Security.SecuritySafeCritical] // auto-generated + [SecurityPermissionAttribute(SecurityAction.Demand, ControlThread=true)] + public void Abort(Object stateInfo) + { + // If two aborts come at the same time, it is possible that the state info + // gets set by one, and the actual abort gets delivered by another. But this + // is not distinguishable by an application. + // The accessor helper will only set the value if it isn't already set, + // and that particular bit of native code can test much faster than this + // code could, because testing might cause a cross-appdomain marshalling. + AbortReason = stateInfo; + + // Note: we demand ControlThread permission, then call AbortInternal directly + // rather than delegating to the Abort() function below. We do this to ensure + // that only callers with ControlThread are allowed to change the AbortReason + // of the thread. We call AbortInternal directly to avoid demanding the same + // permission twice. + AbortInternal(); + } +#endif + + #if FEATURE_CORECLR + [System.Security.SecurityCritical] // auto-generated + #else + [System.Security.SecuritySafeCritical] + #endif +#pragma warning disable 618 + [SecurityPermissionAttribute(SecurityAction.Demand, ControlThread = true)] +#pragma warning restore 618 + public void Abort() + { + AbortInternal(); + } + + // Internal helper (since we can't place security demands on + // ecalls/fcalls). + [System.Security.SecurityCritical] // auto-generated + [MethodImplAttribute(MethodImplOptions.InternalCall)] + private extern void AbortInternal(); + +#if !FEATURE_CORECLR + /*========================================================================= + ** Resets a thread abort. + ** Should be called by trusted code only + =========================================================================*/ + [System.Security.SecuritySafeCritical] // auto-generated + [SecurityPermissionAttribute(SecurityAction.Demand, ControlThread=true)] + public static void ResetAbort() + { + Thread thread = Thread.CurrentThread; + if ((thread.ThreadState & ThreadState.AbortRequested) == 0) + throw new ThreadStateException(Environment.GetResourceString("ThreadState_NoAbortRequested")); + thread.ResetAbortNative(); + thread.ClearAbortReason(); + } + + [System.Security.SecurityCritical] // auto-generated + [MethodImplAttribute(MethodImplOptions.InternalCall)] + private extern void ResetAbortNative(); + + /*========================================================================= + ** Suspends the thread. If the thread is already suspended, this call has + ** no effect. + ** + ** Exceptions: ThreadStateException if the thread has not been started or + ** it is dead. + =========================================================================*/ + [System.Security.SecuritySafeCritical] // auto-generated + [Obsolete("Thread.Suspend has been deprecated. Please use other classes in System.Threading, such as Monitor, Mutex, Event, and Semaphore, to synchronize Threads or protect resources. http://go.microsoft.com/fwlink/?linkid=14202", false)][SecurityPermission(SecurityAction.Demand, ControlThread=true)] + [SecurityPermission(SecurityAction.Demand, ControlThread=true)] + public void Suspend() { SuspendInternal(); } + + // Internal helper (since we can't place security demands on + // ecalls/fcalls). + [System.Security.SecurityCritical] // auto-generated + [MethodImplAttribute(MethodImplOptions.InternalCall)] + private extern void SuspendInternal(); + + /*========================================================================= + ** Resumes a thread that has been suspended. + ** + ** Exceptions: ThreadStateException if the thread has not been started or + ** it is dead or it isn't in the suspended state. + =========================================================================*/ + [System.Security.SecuritySafeCritical] // auto-generated + [Obsolete("Thread.Resume has been deprecated. Please use other classes in System.Threading, such as Monitor, Mutex, Event, and Semaphore, to synchronize Threads or protect resources. http://go.microsoft.com/fwlink/?linkid=14202", false)] + [SecurityPermission(SecurityAction.Demand, ControlThread=true)] + public void Resume() { ResumeInternal(); } + + // Internal helper (since we can't place security demands on + // ecalls/fcalls). + [System.Security.SecurityCritical] // auto-generated + [MethodImplAttribute(MethodImplOptions.InternalCall)] + private extern void ResumeInternal(); + + /*========================================================================= + ** Interrupts a thread that is inside a Wait(), Sleep() or Join(). If that + ** thread is not currently blocked in that manner, it will be interrupted + ** when it next begins to block. + =========================================================================*/ + [System.Security.SecuritySafeCritical] // auto-generated + [SecurityPermission(SecurityAction.Demand, ControlThread=true)] + public void Interrupt() { InterruptInternal(); } + + // Internal helper (since we can't place security demands on + // ecalls/fcalls). + [System.Security.SecurityCritical] // auto-generated + [MethodImplAttribute(MethodImplOptions.InternalCall)] + private extern void InterruptInternal(); +#endif + + /*========================================================================= + ** Returns the priority of the thread. + ** + ** Exceptions: ThreadStateException if the thread is dead. + =========================================================================*/ + + public ThreadPriority Priority { + [System.Security.SecuritySafeCritical] // auto-generated + get { return (ThreadPriority)GetPriorityNative(); } + [System.Security.SecuritySafeCritical] // auto-generated + [HostProtection(SelfAffectingThreading=true)] + set { SetPriorityNative((int)value); } + } + [System.Security.SecurityCritical] // auto-generated + [MethodImplAttribute(MethodImplOptions.InternalCall)] + private extern int GetPriorityNative(); + [System.Security.SecurityCritical] // auto-generated + [MethodImplAttribute(MethodImplOptions.InternalCall)] + private extern void SetPriorityNative(int priority); + + /*========================================================================= + ** Returns true if the thread has been started and is not dead. + =========================================================================*/ + public extern bool IsAlive { + [System.Security.SecuritySafeCritical] // auto-generated + [MethodImpl(MethodImplOptions.InternalCall)] + get; + } + + /*========================================================================= + ** Returns true if the thread is a threadpool thread. + =========================================================================*/ + public extern bool IsThreadPoolThread { + [System.Security.SecuritySafeCritical] // auto-generated + [MethodImpl(MethodImplOptions.InternalCall)] + get; + } + + /*========================================================================= + ** Waits for the thread to die or for timeout milliseconds to elapse. + ** Returns true if the thread died, or false if the wait timed out. If + ** Timeout.Infinite is given as the parameter, no timeout will occur. + ** + ** Exceptions: ArgumentException if timeout < 0. + ** ThreadInterruptedException if the thread is interrupted while waiting. + ** ThreadStateException if the thread has not been started yet. + =========================================================================*/ + [System.Security.SecurityCritical] + [MethodImplAttribute(MethodImplOptions.InternalCall)] + private extern bool JoinInternal(int millisecondsTimeout); + + [System.Security.SecuritySafeCritical] + [HostProtection(Synchronization=true, ExternalThreading=true)] + public void Join() + { + JoinInternal(Timeout.Infinite); + } + + [System.Security.SecuritySafeCritical] + [HostProtection(Synchronization=true, ExternalThreading=true)] + public bool Join(int millisecondsTimeout) + { + return JoinInternal(millisecondsTimeout); + } + + [HostProtection(Synchronization=true, ExternalThreading=true)] + public bool Join(TimeSpan timeout) + { + long tm = (long)timeout.TotalMilliseconds; + if (tm < -1 || tm > (long) Int32.MaxValue) + throw new ArgumentOutOfRangeException("timeout", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegOrNegative1")); + + return Join((int)tm); + } + + /*========================================================================= + ** Suspends the current thread for timeout milliseconds. If timeout == 0, + ** forces the thread to give up the remainer of its timeslice. If timeout + ** == Timeout.Infinite, no timeout will occur. + ** + ** Exceptions: ArgumentException if timeout < 0. + ** ThreadInterruptedException if the thread is interrupted while sleeping. + =========================================================================*/ + [System.Security.SecurityCritical] // auto-generated + [MethodImplAttribute(MethodImplOptions.InternalCall)] + private static extern void SleepInternal(int millisecondsTimeout); + + [System.Security.SecuritySafeCritical] // auto-generated + public static void Sleep(int millisecondsTimeout) + { + SleepInternal(millisecondsTimeout); + // Ensure we don't return to app code when the pause is underway + if(AppDomainPauseManager.IsPaused) + AppDomainPauseManager.ResumeEvent.WaitOneWithoutFAS(); + } + + public static void Sleep(TimeSpan timeout) + { + long tm = (long)timeout.TotalMilliseconds; + if (tm < -1 || tm > (long) Int32.MaxValue) + throw new ArgumentOutOfRangeException("timeout", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegOrNegative1")); + Sleep((int)tm); + } + + + /* wait for a length of time proportial to 'iterations'. Each iteration is should + only take a few machine instructions. Calling this API is preferable to coding + a explict busy loop because the hardware can be informed that it is busy waiting. */ + + [System.Security.SecurityCritical] // auto-generated + [MethodImplAttribute(MethodImplOptions.InternalCall)] + [HostProtection(Synchronization=true,ExternalThreading=true)] + [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] + private static extern void SpinWaitInternal(int iterations); + + [System.Security.SecuritySafeCritical] // auto-generated + [HostProtection(Synchronization=true,ExternalThreading=true)] + [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] + public static void SpinWait(int iterations) + { + SpinWaitInternal(iterations); + } + + [System.Security.SecurityCritical] // auto-generated + [DllImport(JitHelpers.QCall, CharSet = CharSet.Unicode)] + [SuppressUnmanagedCodeSecurity] + [HostProtection(Synchronization = true, ExternalThreading = true)] + [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] + private static extern bool YieldInternal(); + + [System.Security.SecuritySafeCritical] // auto-generated + [HostProtection(Synchronization = true, ExternalThreading = true)] + [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] + public static bool Yield() + { + return YieldInternal(); + } + + public static Thread CurrentThread { + [System.Security.SecuritySafeCritical] // auto-generated + [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)] + get { + Contract.Ensures(Contract.Result<Thread>() != null); + return GetCurrentThreadNative(); + } + } + [System.Security.SecurityCritical] // auto-generated + [MethodImplAttribute(MethodImplOptions.InternalCall), ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)] + private static extern Thread GetCurrentThreadNative(); + + [System.Security.SecurityCritical] // auto-generated + private void SetStartHelper(Delegate start, int maxStackSize) + { +#if FEATURE_CORECLR + // We only support default stacks in CoreCLR + Contract.Assert(maxStackSize == 0); +#else + // Only fully-trusted code is allowed to create "large" stacks. Partial-trust falls back to + // the default stack size. + ulong defaultStackSize = GetProcessDefaultStackSize(); + if ((ulong)(uint)maxStackSize > defaultStackSize) + { + try + { + SecurityPermission.Demand(PermissionType.FullTrust); + } + catch (SecurityException) + { + maxStackSize = (int)Math.Min(defaultStackSize, (ulong)(uint)int.MaxValue); + } + } +#endif + + ThreadHelper threadStartCallBack = new ThreadHelper(start); + if(start is ThreadStart) + { + SetStart(new ThreadStart(threadStartCallBack.ThreadStart), maxStackSize); + } + else + { + SetStart(new ParameterizedThreadStart(threadStartCallBack.ThreadStart), maxStackSize); + } + } + + [SecurityCritical] + [DllImport(JitHelpers.QCall, CharSet = CharSet.Unicode)] + [SuppressUnmanagedCodeSecurity] + private static extern ulong GetProcessDefaultStackSize(); + + /*========================================================================= + ** PRIVATE Sets the IThreadable interface for the thread. Assumes that + ** start != null. + =========================================================================*/ + [System.Security.SecurityCritical] // auto-generated + [MethodImplAttribute(MethodImplOptions.InternalCall)] + private extern void SetStart(Delegate start, int maxStackSize); + + /*========================================================================= + ** Clean up the thread when it goes away. + =========================================================================*/ + [System.Security.SecuritySafeCritical] // auto-generated + [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] + ~Thread() + { + // Delegate to the unmanaged portion. + InternalFinalize(); + } + + [System.Security.SecurityCritical] // auto-generated + [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] + [MethodImplAttribute(MethodImplOptions.InternalCall)] + private extern void InternalFinalize(); + +#if FEATURE_COMINTEROP + [System.Security.SecurityCritical] // auto-generated + [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] + [MethodImplAttribute(MethodImplOptions.InternalCall)] + public extern void DisableComObjectEagerCleanup(); +#endif //FEATURE_COMINTEROP + + /*========================================================================= + ** Return whether or not this thread is a background thread. Background + ** threads do not affect when the Execution Engine shuts down. + ** + ** Exceptions: ThreadStateException if the thread is dead. + =========================================================================*/ + public bool IsBackground { + [System.Security.SecuritySafeCritical] // auto-generated + get { return IsBackgroundNative(); } + [System.Security.SecuritySafeCritical] // auto-generated + [HostProtection(SelfAffectingThreading=true)] + set { SetBackgroundNative(value); } + } + [System.Security.SecurityCritical] // auto-generated + [MethodImplAttribute(MethodImplOptions.InternalCall)] + private extern bool IsBackgroundNative(); + [System.Security.SecurityCritical] // auto-generated + [MethodImplAttribute(MethodImplOptions.InternalCall)] + private extern void SetBackgroundNative(bool isBackground); + + + /*========================================================================= + ** Return the thread state as a consistent set of bits. This is more + ** general then IsAlive or IsBackground. + =========================================================================*/ + public ThreadState ThreadState { + [System.Security.SecuritySafeCritical] // auto-generated + get { return (ThreadState)GetThreadStateNative(); } + } + + [System.Security.SecurityCritical] // auto-generated + [MethodImplAttribute(MethodImplOptions.InternalCall)] + private extern int GetThreadStateNative(); + +#if FEATURE_COMINTEROP_APARTMENT_SUPPORT + /*========================================================================= + ** An unstarted thread can be marked to indicate that it will host a + ** single-threaded or multi-threaded apartment. + ** + ** Exceptions: ArgumentException if state is not a valid apartment state + ** (ApartmentSTA or ApartmentMTA). + =========================================================================*/ + [Obsolete("The ApartmentState property has been deprecated. Use GetApartmentState, SetApartmentState or TrySetApartmentState instead.", false)] + public ApartmentState ApartmentState + { + [System.Security.SecuritySafeCritical] // auto-generated + get + { + return (ApartmentState)GetApartmentStateNative(); + } + + [System.Security.SecuritySafeCritical] // auto-generated + [HostProtection(Synchronization=true, SelfAffectingThreading=true)] + set + { + SetApartmentStateNative((int)value, true); + } + } + + [System.Security.SecuritySafeCritical] // auto-generated + public ApartmentState GetApartmentState() + { + return (ApartmentState)GetApartmentStateNative(); + } + + [System.Security.SecuritySafeCritical] // auto-generated + [HostProtection(Synchronization=true, SelfAffectingThreading=true)] + public bool TrySetApartmentState(ApartmentState state) + { + return SetApartmentStateHelper(state, false); + } + + [System.Security.SecuritySafeCritical] // auto-generated + [HostProtection(Synchronization=true, SelfAffectingThreading=true)] + public void SetApartmentState(ApartmentState state) + { + bool result = SetApartmentStateHelper(state, true); + if (!result) + throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_ApartmentStateSwitchFailed")); + } + + [System.Security.SecurityCritical] // auto-generated + private bool SetApartmentStateHelper(ApartmentState state, bool fireMDAOnMismatch) + { + ApartmentState retState = (ApartmentState)SetApartmentStateNative((int)state, fireMDAOnMismatch); + + // Special case where we pass in Unknown and get back MTA. + // Once we CoUninitialize the thread, the OS will still + // report the thread as implicitly in the MTA if any + // other thread in the process is CoInitialized. + if ((state == System.Threading.ApartmentState.Unknown) && (retState == System.Threading.ApartmentState.MTA)) + return true; + + if (retState != state) + return false; + + return true; + } + + [System.Security.SecurityCritical] // auto-generated + [MethodImplAttribute(MethodImplOptions.InternalCall)] + private extern int GetApartmentStateNative(); + [System.Security.SecurityCritical] // auto-generated + [MethodImplAttribute(MethodImplOptions.InternalCall)] + private extern int SetApartmentStateNative(int state, bool fireMDAOnMismatch); + [System.Security.SecurityCritical] // auto-generated + [MethodImplAttribute(MethodImplOptions.InternalCall)] + private extern void StartupSetApartmentStateInternal(); +#endif // FEATURE_COMINTEROP_APARTMENT_SUPPORT + + /*========================================================================= + ** Allocates an un-named data slot. The slot is allocated on ALL the + ** threads. + =========================================================================*/ + [HostProtection(SharedState=true, ExternalThreading=true)] + public static LocalDataStoreSlot AllocateDataSlot() + { + return LocalDataStoreManager.AllocateDataSlot(); + } + + /*========================================================================= + ** Allocates a named data slot. The slot is allocated on ALL the + ** threads. Named data slots are "public" and can be manipulated by + ** anyone. + =========================================================================*/ + [HostProtection(SharedState=true, ExternalThreading=true)] + public static LocalDataStoreSlot AllocateNamedDataSlot(String name) + { + return LocalDataStoreManager.AllocateNamedDataSlot(name); + } + + /*========================================================================= + ** Looks up a named data slot. If the name has not been used, a new slot is + ** allocated. Named data slots are "public" and can be manipulated by + ** anyone. + =========================================================================*/ + [HostProtection(SharedState=true, ExternalThreading=true)] + public static LocalDataStoreSlot GetNamedDataSlot(String name) + { + return LocalDataStoreManager.GetNamedDataSlot(name); + } + + /*========================================================================= + ** Frees a named data slot. The slot is allocated on ALL the + ** threads. Named data slots are "public" and can be manipulated by + ** anyone. + =========================================================================*/ + [HostProtection(SharedState=true, ExternalThreading=true)] + public static void FreeNamedDataSlot(String name) + { + LocalDataStoreManager.FreeNamedDataSlot(name); + } + + /*========================================================================= + ** Retrieves the value from the specified slot on the current thread, for that thread's current domain. + =========================================================================*/ + [HostProtection(SharedState=true, ExternalThreading=true)] + public static Object GetData(LocalDataStoreSlot slot) + { + LocalDataStoreHolder dls = s_LocalDataStore; + if (dls == null) + { + // Make sure to validate the slot even if we take the quick path + LocalDataStoreManager.ValidateSlot(slot); + return null; + } + + return dls.Store.GetData(slot); + } + + /*========================================================================= + ** Sets the data in the specified slot on the currently running thread, for that thread's current domain. + =========================================================================*/ + [HostProtection(SharedState=true, ExternalThreading=true)] + public static void SetData(LocalDataStoreSlot slot, Object data) + { + LocalDataStoreHolder dls = s_LocalDataStore; + + // Create new DLS if one hasn't been created for this domain for this thread + if (dls == null) { + dls = LocalDataStoreManager.CreateLocalDataStore(); + s_LocalDataStore = dls; + } + + dls.Store.SetData(slot, data); + } + + + // #threadCultureInfo + // + // Background: + // In the desktop runtime, we allow a thread's cultures to travel with the thread + // across AppDomain boundaries. Furthermore we update the native thread with the + // culture of the managed thread. Because of security concerns and potential SxS + // effects, in Silverlight we are making the changes listed below. + // + // Silverlight Changes: + // - thread instance member cultures (CurrentCulture and CurrentUICulture) + // confined within AppDomains + // - changes to these properties don't affect the underlying native thread + // + // Ifdef: + // FEATURE_LEAK_CULTURE_INFO : CultureInfos can leak across AppDomains, not + // enabled in Silverlight + // + // Implementation notes: + // In Silverlight, culture members thread static (per Thread, per AppDomain). + // + // Quirks: + // An interesting side-effect of isolating cultures within an AppDomain is that we + // now need to special case resource lookup for mscorlib, which transitions to the + // default domain to lookup resources. See Environment.cs for more details. + // +#if FEATURE_LEAK_CULTURE_INFO + [System.Security.SecurityCritical] // auto-generated + [MethodImplAttribute(MethodImplOptions.InternalCall)] + static extern private bool nativeGetSafeCulture(Thread t, int appDomainId, bool isUI, ref CultureInfo safeCulture); +#endif // FEATURE_LEAK_CULTURE_INFO + + // As the culture can be customized object then we cannot hold any + // reference to it before we check if it is safe because the app domain + // owning this customized culture may get unloaded while executing this + // code. To achieve that we have to do the check using nativeGetSafeCulture + // as the thread cannot get interrupted during the FCALL. + // If the culture is safe (not customized or created in current app domain) + // then the FCALL will return a reference to that culture otherwise the + // FCALL will return failure. In case of failure we'll return the default culture. + // If the app domain owning a customized culture that is set to the thread and this + // app domain get unloaded there is a code to clean up the culture from the thread + // using the code in AppDomain::ReleaseDomainStores. + + public CultureInfo CurrentUICulture { + get { + Contract.Ensures(Contract.Result<CultureInfo>() != null); +#if FEATURE_APPX && !FEATURE_COREFX_GLOBALIZATION + if(AppDomain.IsAppXModel()) { + return CultureInfo.GetCultureInfoForUserPreferredLanguageInAppX() ?? GetCurrentUICultureNoAppX(); + } + else +#endif + { + return GetCurrentUICultureNoAppX(); + } + } + + [System.Security.SecuritySafeCritical] // auto-generated + [HostProtection(ExternalThreading=true)] + set { + if (value == null) { + throw new ArgumentNullException("value"); + } + Contract.EndContractBlock(); + + //If they're trying to use a Culture with a name that we can't use in resource lookup, + //don't even let them set it on the thread. + CultureInfo.VerifyCultureName(value, true); + + // If you add more pre-conditions to this method, check to see if you also need to + // add them to CultureInfo.DefaultThreadCurrentUICulture.set. + +#if FEATURE_LEAK_CULTURE_INFO + if (nativeSetThreadUILocale(value.SortName) == false) + { + throw new ArgumentException(Environment.GetResourceString("Argument_InvalidResourceCultureName", value.Name)); + } + value.StartCrossDomainTracking(); +#else + if (m_CurrentUICulture == null && m_CurrentCulture == null) + nativeInitCultureAccessors(); +#endif + + if (!AppContextSwitches.NoAsyncCurrentCulture) + { + if (s_asyncLocalCurrentUICulture == null) + { + Interlocked.CompareExchange(ref s_asyncLocalCurrentUICulture, new AsyncLocal<CultureInfo>(AsyncLocalSetCurrentUICulture), null); + } + + // this one will set m_CurrentUICulture too + s_asyncLocalCurrentUICulture.Value = value; + } + else + { + m_CurrentUICulture = value; + } + } + } + +#if FEATURE_LEAK_CULTURE_INFO + [System.Security.SecuritySafeCritical] // auto-generated +#endif + internal CultureInfo GetCurrentUICultureNoAppX() { + + Contract.Ensures(Contract.Result<CultureInfo>() != null); + +#if FEATURE_COREFX_GLOBALIZATION + return CultureInfo.CurrentUICulture; +#else + + // Fetch a local copy of m_CurrentUICulture to + // avoid race conditions that malicious user can introduce + if (m_CurrentUICulture == null) { + CultureInfo appDomainDefaultUICulture = CultureInfo.DefaultThreadCurrentUICulture; + return (appDomainDefaultUICulture != null ? appDomainDefaultUICulture : CultureInfo.UserDefaultUICulture); + } + +#if FEATURE_LEAK_CULTURE_INFO + CultureInfo culture = null; + + if (!nativeGetSafeCulture(this, GetDomainID(), true, ref culture) || culture == null) { + return CultureInfo.UserDefaultUICulture; + } + + return culture; +#else + return m_CurrentUICulture; +#endif +#endif + } + + // This returns the exposed context for a given context ID. +#if FEATURE_LEAK_CULTURE_INFO + [System.Security.SecurityCritical] // auto-generated + [MethodImplAttribute(MethodImplOptions.InternalCall)] + static extern private bool nativeSetThreadUILocale(String locale); +#endif + + // As the culture can be customized object then we cannot hold any + // reference to it before we check if it is safe because the app domain + // owning this customized culture may get unloaded while executing this + // code. To achieve that we have to do the check using nativeGetSafeCulture + // as the thread cannot get interrupted during the FCALL. + // If the culture is safe (not customized or created in current app domain) + // then the FCALL will return a reference to that culture otherwise the + // FCALL will return failure. In case of failure we'll return the default culture. + // If the app domain owning a customized culture that is set to the thread and this + // app domain get unloaded there is a code to clean up the culture from the thread + // using the code in AppDomain::ReleaseDomainStores. + + public CultureInfo CurrentCulture { + get { + Contract.Ensures(Contract.Result<CultureInfo>() != null); + +#if FEATURE_APPX && !FEATURE_COREFX_GLOBALIZATION + if(AppDomain.IsAppXModel()) { + return CultureInfo.GetCultureInfoForUserPreferredLanguageInAppX() ?? GetCurrentCultureNoAppX(); + } + else +#endif + { + return GetCurrentCultureNoAppX(); + } + } + + [System.Security.SecuritySafeCritical] // auto-generated +#if FEATURE_LEAK_CULTURE_INFO + [SecurityPermission(SecurityAction.Demand, ControlThread = true)] +#endif + set { + if (null==value) { + throw new ArgumentNullException("value"); + } + Contract.EndContractBlock(); + + // If you add more pre-conditions to this method, check to see if you also need to + // add them to CultureInfo.DefaultThreadCurrentCulture.set. + +#if FEATURE_LEAK_CULTURE_INFO + //If we can't set the nativeThreadLocale, we'll just let it stay + //at whatever value it had before. This allows people who use + //just managed code not to be limited by the underlying OS. + CultureInfo.nativeSetThreadLocale(value.SortName); + value.StartCrossDomainTracking(); +#else + if (m_CurrentCulture == null && m_CurrentUICulture == null) + nativeInitCultureAccessors(); +#endif + + if (!AppContextSwitches.NoAsyncCurrentCulture) + { + if (s_asyncLocalCurrentCulture == null) + { + Interlocked.CompareExchange(ref s_asyncLocalCurrentCulture, new AsyncLocal<CultureInfo>(AsyncLocalSetCurrentCulture), null); + } + // this one will set m_CurrentCulture too + s_asyncLocalCurrentCulture.Value = value; + } + else + { + m_CurrentCulture = value; + } + } + } + +#if FEATURE_LEAK_CULTURE_INFO + [System.Security.SecuritySafeCritical] // auto-generated +#endif + private CultureInfo GetCurrentCultureNoAppX() { + +#if FEATURE_COREFX_GLOBALIZATION + return CultureInfo.CurrentCulture; +#else + Contract.Ensures(Contract.Result<CultureInfo>() != null); + + // Fetch a local copy of m_CurrentCulture to + // avoid race conditions that malicious user can introduce + if (m_CurrentCulture == null) { + CultureInfo appDomainDefaultCulture = CultureInfo.DefaultThreadCurrentCulture; + return (appDomainDefaultCulture != null ? appDomainDefaultCulture : CultureInfo.UserDefaultCulture); + } + +#if FEATURE_LEAK_CULTURE_INFO + CultureInfo culture = null; + + if (!nativeGetSafeCulture(this, GetDomainID(), false, ref culture) || culture == null) { + return CultureInfo.UserDefaultCulture; + } + + return culture; +#else + return m_CurrentCulture; +#endif +#endif + } + +#if! FEATURE_LEAK_CULTURE_INFO + [System.Security.SecurityCritical] // auto-generated + [DllImport(JitHelpers.QCall, CharSet = CharSet.Unicode)] + [SuppressUnmanagedCodeSecurity] + private static extern void nativeInitCultureAccessors(); +#endif + + /*=============================================================*/ + + /*====================================================================== + ** Current thread context is stored in a slot in the thread local store + ** CurrentContext gets the Context from the slot. + ======================================================================*/ +#if FEATURE_REMOTING + public static Context CurrentContext + { + [System.Security.SecurityCritical] // auto-generated_required + get + { + return CurrentThread.GetCurrentContextInternal(); + } + } + + [System.Security.SecurityCritical] // auto-generated + internal Context GetCurrentContextInternal() + { + if (m_Context == null) + { + m_Context = Context.DefaultContext; + } + return m_Context; + } +#endif + + +#if FEATURE_IMPERSONATION + // Get and set thread's current principal (for role based security). + public static IPrincipal CurrentPrincipal + { + [System.Security.SecuritySafeCritical] // auto-generated + get + { + lock (CurrentThread) + { + IPrincipal principal = (IPrincipal) + CallContext.Principal; + if (principal == null) + { + principal = GetDomain().GetThreadPrincipal(); + CallContext.Principal = principal; + } + return principal; + } + } + + [System.Security.SecuritySafeCritical] // auto-generated + [SecurityPermissionAttribute(SecurityAction.Demand, Flags=SecurityPermissionFlag.ControlPrincipal)] + set + { + CallContext.Principal = value; + } + } + + // Private routine called from unmanaged code to set an initial + // principal for a newly created thread. + [System.Security.SecurityCritical] // auto-generated + private void SetPrincipalInternal(IPrincipal principal) + { + GetMutableExecutionContext().LogicalCallContext.SecurityData.Principal = principal; + } +#endif // FEATURE_IMPERSONATION + +#if FEATURE_REMOTING + + // This returns the exposed context for a given context ID. + [System.Security.SecurityCritical] // auto-generated + [MethodImplAttribute(MethodImplOptions.InternalCall)] + internal static extern Context GetContextInternal(IntPtr id); + + [System.Security.SecurityCritical] // auto-generated + [MethodImplAttribute(MethodImplOptions.InternalCall)] + internal extern Object InternalCrossContextCallback(Context ctx, IntPtr ctxID, Int32 appDomainID, InternalCrossContextDelegate ftnToCall, Object[] args); + + [System.Security.SecurityCritical] // auto-generated + internal Object InternalCrossContextCallback(Context ctx, InternalCrossContextDelegate ftnToCall, Object[] args) + { + return InternalCrossContextCallback(ctx, ctx.InternalContextID, 0, ftnToCall, args); + } + + // CompleteCrossContextCallback is called by the EE after transitioning to the requested context + private static Object CompleteCrossContextCallback(InternalCrossContextDelegate ftnToCall, Object[] args) + { + return ftnToCall(args); + } +#endif // FEATURE_REMOTING + + /*====================================================================== + ** Returns the current domain in which current thread is running. + ======================================================================*/ + + [System.Security.SecurityCritical] // auto-generated + [MethodImplAttribute(MethodImplOptions.InternalCall)] + private static extern AppDomain GetDomainInternal(); + [System.Security.SecurityCritical] // auto-generated + [MethodImplAttribute(MethodImplOptions.InternalCall)] + private static extern AppDomain GetFastDomainInternal(); + + [System.Security.SecuritySafeCritical] // auto-generated + public static AppDomain GetDomain() + { + Contract.Ensures(Contract.Result<AppDomain>() != null); + + + AppDomain ad; + ad = GetFastDomainInternal(); + if (ad == null) + ad = GetDomainInternal(); + +#if FEATURE_REMOTING + Contract.Assert(CurrentThread.m_Context == null || CurrentThread.m_Context.AppDomain == ad, "AppDomains on the managed & unmanaged threads should match"); +#endif + return ad; + } + + + /* + * This returns a unique id to identify an appdomain. + */ + public static int GetDomainID() + { + return GetDomain().GetId(); + } + + + // Retrieves the name of the thread. + // + public String Name { + get { + return m_Name; + + } + [System.Security.SecuritySafeCritical] // auto-generated + [HostProtection(ExternalThreading=true)] + set { + lock(this) { + if (m_Name != null) + throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_WriteOnce")); + m_Name = value; + + InformThreadNameChange(GetNativeHandle(), value, (value != null) ? value.Length : 0); + } + } + } + + [System.Security.SecurityCritical] // auto-generated + [DllImport(JitHelpers.QCall, CharSet = CharSet.Unicode)] + [SuppressUnmanagedCodeSecurity] + private static extern void InformThreadNameChange(ThreadHandle t, String name, int len); + + internal Object AbortReason { + [System.Security.SecurityCritical] // auto-generated + get { + object result = null; + try + { + result = GetAbortReason(); + } + catch (Exception e) + { + throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_ExceptionStateCrossAppDomain"), e); + } + return result; + } + [System.Security.SecurityCritical] // auto-generated + set { SetAbortReason(value); } + } + +#if !FEATURE_CORECLR + /* + * This marks the beginning of a critical code region. + */ + [System.Security.SecuritySafeCritical] // auto-generated + [HostProtection(Synchronization=true, ExternalThreading=true)] + [MethodImplAttribute(MethodImplOptions.InternalCall)] + [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)] + public static extern void BeginCriticalRegion(); + + /* + * This marks the end of a critical code region. + */ + [System.Security.SecuritySafeCritical] // auto-generated + [HostProtection(Synchronization=true, ExternalThreading=true)] + [MethodImplAttribute(MethodImplOptions.InternalCall)] + [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] + public static extern void EndCriticalRegion(); + + /* + * This marks the beginning of a code region that requires thread affinity. + */ + [System.Security.SecurityCritical] // auto-generated_required + [MethodImplAttribute(MethodImplOptions.InternalCall)] + [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)] + public static extern void BeginThreadAffinity(); + + /* + * This marks the end of a code region that requires thread affinity. + */ + [System.Security.SecurityCritical] // auto-generated_required + [MethodImplAttribute(MethodImplOptions.InternalCall)] + [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)] + public static extern void EndThreadAffinity(); +#endif // !FEATURE_CORECLR + + /*========================================================================= + ** Volatile Read & Write and MemoryBarrier methods. + ** Provides the ability to read and write values ensuring that the values + ** are read/written each time they are accessed. + =========================================================================*/ + + [MethodImplAttribute(MethodImplOptions.NoInlining)] // disable optimizations + public static byte VolatileRead(ref byte address) + { + byte ret = address; + MemoryBarrier(); // Call MemoryBarrier to ensure the proper semantic in a portable way. + return ret; + } + + [MethodImplAttribute(MethodImplOptions.NoInlining)] // disable optimizations + public static short VolatileRead(ref short address) + { + short ret = address; + MemoryBarrier(); // Call MemoryBarrier to ensure the proper semantic in a portable way. + return ret; + } + + [MethodImplAttribute(MethodImplOptions.NoInlining)] // disable optimizations + public static int VolatileRead(ref int address) + { + int ret = address; + MemoryBarrier(); // Call MemoryBarrier to ensure the proper semantic in a portable way. + return ret; + } + + [MethodImplAttribute(MethodImplOptions.NoInlining)] // disable optimizations + public static long VolatileRead(ref long address) + { + long ret = address; + MemoryBarrier(); // Call MemoryBarrier to ensure the proper semantic in a portable way. + return ret; + } + + [CLSCompliant(false)] + [MethodImplAttribute(MethodImplOptions.NoInlining)] // disable optimizations + public static sbyte VolatileRead(ref sbyte address) + { + sbyte ret = address; + MemoryBarrier(); // Call MemoryBarrier to ensure the proper semantic in a portable way. + return ret; + } + + [CLSCompliant(false)] + [MethodImplAttribute(MethodImplOptions.NoInlining)] // disable optimizations + public static ushort VolatileRead(ref ushort address) + { + ushort ret = address; + MemoryBarrier(); // Call MemoryBarrier to ensure the proper semantic in a portable way. + return ret; + } + + [CLSCompliant(false)] + [MethodImplAttribute(MethodImplOptions.NoInlining)] // disable optimizations + public static uint VolatileRead(ref uint address) + { + uint ret = address; + MemoryBarrier(); // Call MemoryBarrier to ensure the proper semantic in a portable way. + return ret; + } + + [MethodImplAttribute(MethodImplOptions.NoInlining)] // disable optimizations + public static IntPtr VolatileRead(ref IntPtr address) + { + IntPtr ret = address; + MemoryBarrier(); // Call MemoryBarrier to ensure the proper semantic in a portable way. + return ret; + } + + [CLSCompliant(false)] + [MethodImplAttribute(MethodImplOptions.NoInlining)] // disable optimizations + public static UIntPtr VolatileRead(ref UIntPtr address) + { + UIntPtr ret = address; + MemoryBarrier(); // Call MemoryBarrier to ensure the proper semantic in a portable way. + return ret; + } + + [CLSCompliant(false)] + [MethodImplAttribute(MethodImplOptions.NoInlining)] // disable optimizations + public static ulong VolatileRead(ref ulong address) + { + ulong ret = address; + MemoryBarrier(); // Call MemoryBarrier to ensure the proper semantic in a portable way. + return ret; + } + + [MethodImplAttribute(MethodImplOptions.NoInlining)] // disable optimizations + public static float VolatileRead(ref float address) + { + float ret = address; + MemoryBarrier(); // Call MemoryBarrier to ensure the proper semantic in a portable way. + return ret; + } + + [MethodImplAttribute(MethodImplOptions.NoInlining)] // disable optimizations + public static double VolatileRead(ref double address) + { + double ret = address; + MemoryBarrier(); // Call MemoryBarrier to ensure the proper semantic in a portable way. + return ret; + } + + [MethodImplAttribute(MethodImplOptions.NoInlining)] // disable optimizations + public static Object VolatileRead(ref Object address) + { + Object ret = address; + MemoryBarrier(); // Call MemoryBarrier to ensure the proper semantic in a portable way. + return ret; + } + + [MethodImplAttribute(MethodImplOptions.NoInlining)] // disable optimizations + public static void VolatileWrite(ref byte address, byte value) + { + MemoryBarrier(); // Call MemoryBarrier to ensure the proper semantic in a portable way. + address = value; + } + + [MethodImplAttribute(MethodImplOptions.NoInlining)] // disable optimizations + public static void VolatileWrite(ref short address, short value) + { + MemoryBarrier(); // Call MemoryBarrier to ensure the proper semantic in a portable way. + address = value; + } + + [MethodImplAttribute(MethodImplOptions.NoInlining)] // disable optimizations + public static void VolatileWrite(ref int address, int value) + { + MemoryBarrier(); // Call MemoryBarrier to ensure the proper semantic in a portable way. + address = value; + } + + [MethodImplAttribute(MethodImplOptions.NoInlining)] // disable optimizations + public static void VolatileWrite(ref long address, long value) + { + MemoryBarrier(); // Call MemoryBarrier to ensure the proper semantic in a portable way. + address = value; + } + + [CLSCompliant(false)] + [MethodImplAttribute(MethodImplOptions.NoInlining)] // disable optimizations + public static void VolatileWrite(ref sbyte address, sbyte value) + { + MemoryBarrier(); // Call MemoryBarrier to ensure the proper semantic in a portable way. + address = value; + } + + [CLSCompliant(false)] + [MethodImplAttribute(MethodImplOptions.NoInlining)] // disable optimizations + public static void VolatileWrite(ref ushort address, ushort value) + { + MemoryBarrier(); // Call MemoryBarrier to ensure the proper semantic in a portable way. + address = value; + } + + [CLSCompliant(false)] + [MethodImplAttribute(MethodImplOptions.NoInlining)] // disable optimizations + public static void VolatileWrite(ref uint address, uint value) + { + MemoryBarrier(); // Call MemoryBarrier to ensure the proper semantic in a portable way. + address = value; + } + + [MethodImplAttribute(MethodImplOptions.NoInlining)] // disable optimizations + public static void VolatileWrite(ref IntPtr address, IntPtr value) + { + MemoryBarrier(); // Call MemoryBarrier to ensure the proper semantic in a portable way. + address = value; + } + + [CLSCompliant(false)] + [MethodImplAttribute(MethodImplOptions.NoInlining)] // disable optimizations + public static void VolatileWrite(ref UIntPtr address, UIntPtr value) + { + MemoryBarrier(); // Call MemoryBarrier to ensure the proper semantic in a portable way. + address = value; + } + + [CLSCompliant(false)] + [MethodImplAttribute(MethodImplOptions.NoInlining)] // disable optimizations + public static void VolatileWrite(ref ulong address, ulong value) + { + MemoryBarrier(); // Call MemoryBarrier to ensure the proper semantic in a portable way. + address = value; + } + + [MethodImplAttribute(MethodImplOptions.NoInlining)] // disable optimizations + public static void VolatileWrite(ref float address, float value) + { + MemoryBarrier(); // Call MemoryBarrier to ensure the proper semantic in a portable way. + address = value; + } + + [MethodImplAttribute(MethodImplOptions.NoInlining)] // disable optimizations + public static void VolatileWrite(ref double address, double value) + { + MemoryBarrier(); // Call MemoryBarrier to ensure the proper semantic in a portable way. + address = value; + } + + [MethodImplAttribute(MethodImplOptions.NoInlining)] // disable optimizations + public static void VolatileWrite(ref Object address, Object value) + { + MemoryBarrier(); // Call MemoryBarrier to ensure the proper semantic in a portable way. + address = value; + } + + [System.Security.SecuritySafeCritical] // auto-generated + [MethodImplAttribute(MethodImplOptions.InternalCall)] + public static extern void MemoryBarrier(); + + private static LocalDataStoreMgr LocalDataStoreManager + { + get + { + if (s_LocalDataStoreMgr == null) + { + Interlocked.CompareExchange(ref s_LocalDataStoreMgr, new LocalDataStoreMgr(), null); + } + + return s_LocalDataStoreMgr; + } + } + +#if !FEATURE_CORECLR + void _Thread.GetTypeInfoCount(out uint pcTInfo) + { + throw new NotImplementedException(); + } + + void _Thread.GetTypeInfo(uint iTInfo, uint lcid, IntPtr ppTInfo) + { + throw new NotImplementedException(); + } + + void _Thread.GetIDsOfNames([In] ref Guid riid, IntPtr rgszNames, uint cNames, uint lcid, IntPtr rgDispId) + { + throw new NotImplementedException(); + } + + void _Thread.Invoke(uint dispIdMember, [In] ref Guid riid, uint lcid, short wFlags, IntPtr pDispParams, IntPtr pVarResult, IntPtr pExcepInfo, IntPtr puArgErr) + { + throw new NotImplementedException(); + } +#endif + + // Helper function to set the AbortReason for a thread abort. + // Checks that they're not alredy set, and then atomically updates + // the reason info (object + ADID). + [System.Security.SecurityCritical] // auto-generated + [MethodImplAttribute(MethodImplOptions.InternalCall)] + internal extern void SetAbortReason(Object o); + + // Helper function to retrieve the AbortReason from a thread + // abort. Will perform cross-AppDomain marshalling if the object + // lives in a different AppDomain from the requester. + [System.Security.SecurityCritical] // auto-generated + [MethodImplAttribute(MethodImplOptions.InternalCall)] + internal extern Object GetAbortReason(); + + // Helper function to clear the AbortReason. Takes care of + // AppDomain related cleanup if required. + [System.Security.SecurityCritical] // auto-generated + [MethodImplAttribute(MethodImplOptions.InternalCall)] + internal extern void ClearAbortReason(); + + + } // End of class Thread + + // declaring a local var of this enum type and passing it by ref into a function that needs to do a + // stack crawl will both prevent inlining of the calle and pass an ESP point to stack crawl to + // Declaring these in EH clauses is illegal; they must declared in the main method body + [Serializable] + internal enum StackCrawlMark + { + LookForMe = 0, + LookForMyCaller = 1, + LookForMyCallersCaller = 2, + LookForThread = 3 + } + +} diff --git a/src/mscorlib/src/System/Threading/ThreadAbortException.cs b/src/mscorlib/src/System/Threading/ThreadAbortException.cs new file mode 100644 index 0000000000..11c8744c72 --- /dev/null +++ b/src/mscorlib/src/System/Threading/ThreadAbortException.cs @@ -0,0 +1,46 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +// +/*============================================================================= +** +** +** +** Purpose: An exception class which is thrown into a thread to cause it to +** abort. This is a special non-catchable exception and results in +** the thread's death. This is thrown by the VM only and can NOT be +** thrown by any user thread, and subclassing this is useless. +** +** +=============================================================================*/ + +namespace System.Threading +{ + using System; + using System.Runtime.Serialization; + using System.Runtime.CompilerServices; + + [System.Runtime.InteropServices.ComVisible(true)] + [Serializable] + public sealed class ThreadAbortException : SystemException + { + private ThreadAbortException() + : base(GetMessageFromNativeResources(ExceptionMessageKind.ThreadAbort)) + { + SetErrorCode(__HResults.COR_E_THREADABORTED); + } + + //required for serialization + internal ThreadAbortException(SerializationInfo info, StreamingContext context) + : base(info, context) + { + } + + public Object ExceptionState + { + [System.Security.SecuritySafeCritical] // auto-generated + get {return Thread.CurrentThread.AbortReason;} + } + } +} diff --git a/src/mscorlib/src/System/Threading/ThreadInterruptedException.cs b/src/mscorlib/src/System/Threading/ThreadInterruptedException.cs new file mode 100644 index 0000000000..0056611955 --- /dev/null +++ b/src/mscorlib/src/System/Threading/ThreadInterruptedException.cs @@ -0,0 +1,41 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +// +/*============================================================================= +** +** +** +** Purpose: An exception class to indicate that the thread was interrupted +** from a waiting state. +** +** +=============================================================================*/ +namespace System.Threading { + using System.Threading; + using System; + using System.Runtime.Serialization; + + [System.Runtime.InteropServices.ComVisible(true)] + [Serializable] + public class ThreadInterruptedException : SystemException { + public ThreadInterruptedException() + : base(GetMessageFromNativeResources(ExceptionMessageKind.ThreadInterrupted)) { + SetErrorCode(__HResults.COR_E_THREADINTERRUPTED); + } + + public ThreadInterruptedException(String message) + : base(message) { + SetErrorCode(__HResults.COR_E_THREADINTERRUPTED); + } + + public ThreadInterruptedException(String message, Exception innerException) + : base(message, innerException) { + SetErrorCode(__HResults.COR_E_THREADINTERRUPTED); + } + + protected ThreadInterruptedException(SerializationInfo info, StreamingContext context) : base (info, context) { + } + } +} diff --git a/src/mscorlib/src/System/Threading/ThreadLocal.cs b/src/mscorlib/src/System/Threading/ThreadLocal.cs new file mode 100644 index 0000000000..b4cf12ab7c --- /dev/null +++ b/src/mscorlib/src/System/Threading/ThreadLocal.cs @@ -0,0 +1,815 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. +#pragma warning disable 0420 + +// =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ +// +// +// +// A class that provides a simple, lightweight implementation of thread-local lazy-initialization, where a value is initialized once per accessing +// thread; this provides an alternative to using a ThreadStatic static variable and having +// to check the variable prior to every access to see if it's been initialized. +// +// +// +// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + +using System.Diagnostics; +using System.Collections.Generic; +using System.Security.Permissions; +using System.Diagnostics.Contracts; + +namespace System.Threading +{ + /// <summary> + /// Provides thread-local storage of data. + /// </summary> + /// <typeparam name="T">Specifies the type of data stored per-thread.</typeparam> + /// <remarks> + /// <para> + /// With the exception of <see cref="Dispose()"/>, all public and protected members of + /// <see cref="ThreadLocal{T}"/> are thread-safe and may be used + /// concurrently from multiple threads. + /// </para> + /// </remarks> + [DebuggerTypeProxy(typeof(SystemThreading_ThreadLocalDebugView<>))] + [DebuggerDisplay("IsValueCreated={IsValueCreated}, Value={ValueForDebugDisplay}, Count={ValuesCountForDebugDisplay}")] + [HostProtection(Synchronization = true, ExternalThreading = true)] + public class ThreadLocal<T> : IDisposable + { + + // a delegate that returns the created value, if null the created value will be default(T) + private Func<T> m_valueFactory; + + // + // ts_slotArray is a table of thread-local values for all ThreadLocal<T> instances + // + // So, when a thread reads ts_slotArray, it gets back an array of *all* ThreadLocal<T> values for this thread and this T. + // The slot relevant to this particular ThreadLocal<T> instance is determined by the m_idComplement instance field stored in + // the ThreadLocal<T> instance. + // + [ThreadStatic] + static LinkedSlotVolatile[] ts_slotArray; + + [ThreadStatic] + static FinalizationHelper ts_finalizationHelper; + + // Slot ID of this ThreadLocal<> instance. We store a bitwise complement of the ID (that is ~ID), which allows us to distinguish + // between the case when ID is 0 and an incompletely initialized object, either due to a thread abort in the constructor, or + // possibly due to a memory model issue in user code. + private int m_idComplement; + + // This field is set to true when the constructor completes. That is helpful for recognizing whether a constructor + // threw an exception - either due to invalid argument or due to a thread abort. Finally, the field is set to false + // when the instance is disposed. + private volatile bool m_initialized; + + // IdManager assigns and reuses slot IDs. Additionally, the object is also used as a global lock. + private static IdManager s_idManager = new IdManager(); + + // A linked list of all values associated with this ThreadLocal<T> instance. + // We create a dummy head node. That allows us to remove any (non-dummy) node without having to locate the m_linkedSlot field. + private LinkedSlot m_linkedSlot = new LinkedSlot(null); + + // Whether the Values property is supported + private bool m_trackAllValues; + + /// <summary> + /// Initializes the <see cref="System.Threading.ThreadLocal{T}"/> instance. + /// </summary> + public ThreadLocal() + { + Initialize(null, false); + } + + /// <summary> + /// Initializes the <see cref="System.Threading.ThreadLocal{T}"/> instance. + /// </summary> + /// <param name="trackAllValues">Whether to track all values set on the instance and expose them through the Values property.</param> + public ThreadLocal(bool trackAllValues) + { + Initialize(null, trackAllValues); + } + + + /// <summary> + /// Initializes the <see cref="System.Threading.ThreadLocal{T}"/> instance with the + /// specified <paramref name="valueFactory"/> function. + /// </summary> + /// <param name="valueFactory"> + /// The <see cref="T:System.Func{T}"/> invoked to produce a lazily-initialized value when + /// an attempt is made to retrieve <see cref="Value"/> without it having been previously initialized. + /// </param> + /// <exception cref="T:System.ArgumentNullException"> + /// <paramref name="valueFactory"/> is a null reference (Nothing in Visual Basic). + /// </exception> + public ThreadLocal(Func<T> valueFactory) + { + if (valueFactory == null) + throw new ArgumentNullException("valueFactory"); + + Initialize(valueFactory, false); + } + + /// <summary> + /// Initializes the <see cref="System.Threading.ThreadLocal{T}"/> instance with the + /// specified <paramref name="valueFactory"/> function. + /// </summary> + /// <param name="valueFactory"> + /// The <see cref="T:System.Func{T}"/> invoked to produce a lazily-initialized value when + /// an attempt is made to retrieve <see cref="Value"/> without it having been previously initialized. + /// </param> + /// <param name="trackAllValues">Whether to track all values set on the instance and expose them via the Values property.</param> + /// <exception cref="T:System.ArgumentNullException"> + /// <paramref name="valueFactory"/> is a null reference (Nothing in Visual Basic). + /// </exception> + public ThreadLocal(Func<T> valueFactory, bool trackAllValues) + { + if (valueFactory == null) + throw new ArgumentNullException("valueFactory"); + + Initialize(valueFactory, trackAllValues); + } + + private void Initialize(Func<T> valueFactory, bool trackAllValues) + { + m_valueFactory = valueFactory; + m_trackAllValues = trackAllValues; + + // Assign the ID and mark the instance as initialized. To avoid leaking IDs, we assign the ID and set m_initialized + // in a finally block, to avoid a thread abort in between the two statements. + try { } + finally + { + m_idComplement = ~s_idManager.GetId(); + + // As the last step, mark the instance as fully initialized. (Otherwise, if m_initialized=false, we know that an exception + // occurred in the constructor.) + m_initialized = true; + } + } + + /// <summary> + /// Releases the resources used by this <see cref="T:System.Threading.ThreadLocal{T}" /> instance. + /// </summary> + ~ThreadLocal() + { + // finalizer to return the type combination index to the pool + Dispose(false); + } + + #region IDisposable Members + + /// <summary> + /// Releases the resources used by this <see cref="T:System.Threading.ThreadLocal{T}" /> instance. + /// </summary> + /// <remarks> + /// Unlike most of the members of <see cref="T:System.Threading.ThreadLocal{T}"/>, this method is not thread-safe. + /// </remarks> + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + /// <summary> + /// Releases the resources used by this <see cref="T:System.Threading.ThreadLocal{T}" /> instance. + /// </summary> + /// <param name="disposing"> + /// A Boolean value that indicates whether this method is being called due to a call to <see cref="Dispose()"/>. + /// </param> + /// <remarks> + /// Unlike most of the members of <see cref="T:System.Threading.ThreadLocal{T}"/>, this method is not thread-safe. + /// </remarks> + protected virtual void Dispose(bool disposing) + { + int id; + + lock (s_idManager) + { + id = ~m_idComplement; + m_idComplement = 0; + + if (id < 0 || !m_initialized) + { + Contract.Assert(id >= 0 || !m_initialized, "expected id >= 0 if initialized"); + + // Handle double Dispose calls or disposal of an instance whose constructor threw an exception. + return; + } + m_initialized = false; + + for (LinkedSlot linkedSlot = m_linkedSlot.Next; linkedSlot != null; linkedSlot = linkedSlot.Next) + { + LinkedSlotVolatile[] slotArray = linkedSlot.SlotArray; + + if (slotArray == null) + { + // The thread that owns this slotArray has already finished. + continue; + } + + // Remove the reference from the LinkedSlot to the slot table. + linkedSlot.SlotArray = null; + + // And clear the references from the slot table to the linked slot and the value so that + // both can get garbage collected. + slotArray[id].Value.Value = default(T); + slotArray[id].Value = null; + } + } + m_linkedSlot = null; + s_idManager.ReturnId(id); + } + + #endregion + + /// <summary>Creates and returns a string representation of this instance for the current thread.</summary> + /// <returns>The result of calling <see cref="System.Object.ToString"/> on the <see cref="Value"/>.</returns> + /// <exception cref="T:System.NullReferenceException"> + /// The <see cref="Value"/> for the current thread is a null reference (Nothing in Visual Basic). + /// </exception> + /// <exception cref="T:System.InvalidOperationException"> + /// The initialization function referenced <see cref="Value"/> in an improper manner. + /// </exception> + /// <exception cref="T:System.ObjectDisposedException"> + /// The <see cref="ThreadLocal{T}"/> instance has been disposed. + /// </exception> + /// <remarks> + /// Calling this method forces initialization for the current thread, as is the + /// case with accessing <see cref="Value"/> directly. + /// </remarks> + public override string ToString() + { + return Value.ToString(); + } + + /// <summary> + /// Gets or sets the value of this instance for the current thread. + /// </summary> + /// <exception cref="T:System.InvalidOperationException"> + /// The initialization function referenced <see cref="Value"/> in an improper manner. + /// </exception> + /// <exception cref="T:System.ObjectDisposedException"> + /// The <see cref="ThreadLocal{T}"/> instance has been disposed. + /// </exception> + /// <remarks> + /// If this instance was not previously initialized for the current thread, + /// accessing <see cref="Value"/> will attempt to initialize it. If an initialization function was + /// supplied during the construction, that initialization will happen by invoking the function + /// to retrieve the initial value for <see cref="Value"/>. Otherwise, the default value of + /// <typeparamref name="T"/> will be used. + /// </remarks> + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + public T Value + { + get + { + LinkedSlotVolatile[] slotArray = ts_slotArray; + LinkedSlot slot; + int id = ~m_idComplement; + + // + // Attempt to get the value using the fast path + // + if (slotArray != null // Has the slot array been initialized? + && id >= 0 // Is the ID non-negative (i.e., instance is not disposed)? + && id < slotArray.Length // Is the table large enough? + && (slot = slotArray[id].Value) != null // Has a LinkedSlot object has been allocated for this ID? + && m_initialized // Has the instance *still* not been disposed (important for a race condition with Dispose)? + ) + { + // We verified that the instance has not been disposed *after* we got a reference to the slot. + // This guarantees that we have a reference to the right slot. + // + // Volatile read of the LinkedSlotVolatile.Value property ensures that the m_initialized read + // will not be reordered before the read of slotArray[id]. + return slot.Value; + } + + return GetValueSlow(); + } + set + { + LinkedSlotVolatile[] slotArray = ts_slotArray; + LinkedSlot slot; + int id = ~m_idComplement; + + // + // Attempt to set the value using the fast path + // + if (slotArray != null // Has the slot array been initialized? + && id >= 0 // Is the ID non-negative (i.e., instance is not disposed)? + && id < slotArray.Length // Is the table large enough? + && (slot = slotArray[id].Value) != null // Has a LinkedSlot object has been allocated for this ID? + && m_initialized // Has the instance *still* not been disposed (important for a race condition with Dispose)? + ) + { + // We verified that the instance has not been disposed *after* we got a reference to the slot. + // This guarantees that we have a reference to the right slot. + // + // Volatile read of the LinkedSlotVolatile.Value property ensures that the m_initialized read + // will not be reordered before the read of slotArray[id]. + slot.Value = value; + } + else + { + SetValueSlow(value, slotArray); + } + } + } + + private T GetValueSlow() + { + // If the object has been disposed, the id will be -1. + int id = ~m_idComplement; + if (id < 0) + { + throw new ObjectDisposedException(Environment.GetResourceString("ThreadLocal_Disposed")); + } + + Debugger.NotifyOfCrossThreadDependency(); + + // Determine the initial value + T value; + if (m_valueFactory == null) + { + value = default(T); + } + else + { + value = m_valueFactory(); + + if (IsValueCreated) + { + throw new InvalidOperationException(Environment.GetResourceString("ThreadLocal_Value_RecursiveCallsToValue")); + } + } + + // Since the value has been previously uninitialized, we also need to set it (according to the ThreadLocal semantics). + Value = value; + return value; + } + + private void SetValueSlow(T value, LinkedSlotVolatile[] slotArray) + { + int id = ~m_idComplement; + + // If the object has been disposed, id will be -1. + if (id < 0) + { + throw new ObjectDisposedException(Environment.GetResourceString("ThreadLocal_Disposed")); + } + + // If a slot array has not been created on this thread yet, create it. + if (slotArray == null) + { + slotArray = new LinkedSlotVolatile[GetNewTableSize(id + 1)]; + ts_finalizationHelper = new FinalizationHelper(slotArray, m_trackAllValues); + ts_slotArray = slotArray; + } + + // If the slot array is not big enough to hold this ID, increase the table size. + if (id >= slotArray.Length) + { + GrowTable(ref slotArray, id + 1); + ts_finalizationHelper.SlotArray = slotArray; + ts_slotArray = slotArray; + } + + // If we are using the slot in this table for the first time, create a new LinkedSlot and add it into + // the linked list for this ThreadLocal instance. + if (slotArray[id].Value == null) + { + CreateLinkedSlot(slotArray, id, value); + } + else + { + // Volatile read of the LinkedSlotVolatile.Value property ensures that the m_initialized read + // that follows will not be reordered before the read of slotArray[id]. + LinkedSlot slot = slotArray[id].Value; + + // It is important to verify that the ThreadLocal instance has not been disposed. The check must come + // after capturing slotArray[id], but before assigning the value into the slot. This ensures that + // if this ThreadLocal instance was disposed on another thread and another ThreadLocal instance was + // created, we definitely won't assign the value into the wrong instance. + + if (!m_initialized) + { + throw new ObjectDisposedException(Environment.GetResourceString("ThreadLocal_Disposed")); + } + + slot.Value = value; + } + } + + /// <summary> + /// Creates a LinkedSlot and inserts it into the linked list for this ThreadLocal instance. + /// </summary> + private void CreateLinkedSlot(LinkedSlotVolatile[] slotArray, int id, T value) + { + // Create a LinkedSlot + var linkedSlot = new LinkedSlot(slotArray); + + // Insert the LinkedSlot into the linked list maintained by this ThreadLocal<> instance and into the slot array + lock (s_idManager) + { + // Check that the instance has not been disposed. It is important to check this under a lock, since + // Dispose also executes under a lock. + if (!m_initialized) + { + throw new ObjectDisposedException(Environment.GetResourceString("ThreadLocal_Disposed")); + } + + LinkedSlot firstRealNode = m_linkedSlot.Next; + + // + // Insert linkedSlot between nodes m_linkedSlot and firstRealNode. + // (m_linkedSlot is the dummy head node that should always be in the front.) + // + linkedSlot.Next = firstRealNode; + linkedSlot.Previous = m_linkedSlot; + linkedSlot.Value = value; + + if (firstRealNode != null) + { + firstRealNode.Previous = linkedSlot; + } + m_linkedSlot.Next = linkedSlot; + + // Assigning the slot under a lock prevents a race condition with Dispose (dispose also acquires the lock). + // Otherwise, it would be possible that the ThreadLocal instance is disposed, another one gets created + // with the same ID, and the write would go to the wrong instance. + slotArray[id].Value = linkedSlot; + } + } + + /// <summary> + /// Gets a list for all of the values currently stored by all of the threads that have accessed this instance. + /// </summary> + /// <exception cref="T:System.ObjectDisposedException"> + /// The <see cref="ThreadLocal{T}"/> instance has been disposed. + /// </exception> + public IList<T> Values + { + get + { + if (!m_trackAllValues) + { + throw new InvalidOperationException(Environment.GetResourceString("ThreadLocal_ValuesNotAvailable")); + } + + var list = GetValuesAsList(); // returns null if disposed + if (list == null) throw new ObjectDisposedException(Environment.GetResourceString("ThreadLocal_Disposed")); + return list; + } + } + + /// <summary>Gets all of the threads' values in a list.</summary> + private List<T> GetValuesAsList() + { + List<T> valueList = new List<T>(); + int id = ~m_idComplement; + if (id == -1) + { + return null; + } + + // Walk over the linked list of slots and gather the values associated with this ThreadLocal instance. + for (LinkedSlot linkedSlot = m_linkedSlot.Next; linkedSlot != null; linkedSlot = linkedSlot.Next) + { + // We can safely read linkedSlot.Value. Even if this ThreadLocal has been disposed in the meantime, the LinkedSlot + // objects will never be assigned to another ThreadLocal instance. + valueList.Add(linkedSlot.Value); + } + + return valueList; + } + + /// <summary>Gets the number of threads that have data in this instance.</summary> + private int ValuesCountForDebugDisplay + { + get + { + int count = 0; + for (LinkedSlot linkedSlot = m_linkedSlot.Next; linkedSlot != null; linkedSlot = linkedSlot.Next) + { + count++; + } + return count; + } + } + + /// <summary> + /// Gets whether <see cref="Value"/> is initialized on the current thread. + /// </summary> + /// <exception cref="T:System.ObjectDisposedException"> + /// The <see cref="ThreadLocal{T}"/> instance has been disposed. + /// </exception> + public bool IsValueCreated + { + get + { + int id = ~m_idComplement; + if (id < 0) + { + throw new ObjectDisposedException(Environment.GetResourceString("ThreadLocal_Disposed")); + } + + LinkedSlotVolatile[] slotArray = ts_slotArray; + return slotArray != null && id < slotArray.Length && slotArray[id].Value != null; + } + } + + + /// <summary>Gets the value of the ThreadLocal<T> for debugging display purposes. It takes care of getting + /// the value for the current thread in the ThreadLocal mode.</summary> + internal T ValueForDebugDisplay + { + get + { + LinkedSlotVolatile[] slotArray = ts_slotArray; + int id = ~m_idComplement; + + LinkedSlot slot; + if (slotArray == null || id >= slotArray.Length || (slot = slotArray[id].Value) == null || !m_initialized) + return default(T); + return slot.Value; + } + } + + /// <summary>Gets the values of all threads that accessed the ThreadLocal<T>.</summary> + internal List<T> ValuesForDebugDisplay // same as Values property, but doesn't throw if disposed + { + get { return GetValuesAsList(); } + } + + /// <summary> + /// Resizes a table to a certain length (or larger). + /// </summary> + private void GrowTable(ref LinkedSlotVolatile[] table, int minLength) + { + Contract.Assert(table.Length < minLength); + + // Determine the size of the new table and allocate it. + int newLen = GetNewTableSize(minLength); + LinkedSlotVolatile[] newTable = new LinkedSlotVolatile[newLen]; + + // + // The lock is necessary to avoid a race with ThreadLocal.Dispose. GrowTable has to point all + // LinkedSlot instances referenced in the old table to reference the new table. Without locking, + // Dispose could use a stale SlotArray reference and clear out a slot in the old array only, while + // the value continues to be referenced from the new (larger) array. + // + lock (s_idManager) + { + for (int i = 0; i < table.Length; i++) + { + LinkedSlot linkedSlot = table[i].Value; + if (linkedSlot != null && linkedSlot.SlotArray != null) + { + linkedSlot.SlotArray = newTable; + newTable[i] = table[i]; + } + } + } + + table = newTable; + } + + /// <summary> + /// Chooses the next larger table size + /// </summary> + private static int GetNewTableSize(int minSize) + { + if ((uint)minSize > Array.MaxArrayLength) + { + // Intentionally return a value that will result in an OutOfMemoryException + return int.MaxValue; + } + Contract.Assert(minSize > 0); + + // + // Round up the size to the next power of 2 + // + // The algorithm takes three steps: + // input -> subtract one -> propagate 1-bits to the right -> add one + // + // Let's take a look at the 3 steps in both interesting cases: where the input + // is (Example 1) and isn't (Example 2) a power of 2. + // + // Example 1: 100000 -> 011111 -> 011111 -> 100000 + // Example 2: 011010 -> 011001 -> 011111 -> 100000 + // + int newSize = minSize; + + // Step 1: Decrement + newSize--; + + // Step 2: Propagate 1-bits to the right. + newSize |= newSize >> 1; + newSize |= newSize >> 2; + newSize |= newSize >> 4; + newSize |= newSize >> 8; + newSize |= newSize >> 16; + + // Step 3: Increment + newSize++; + + // Don't set newSize to more than Array.MaxArrayLength + if ((uint)newSize > Array.MaxArrayLength) + { + newSize = Array.MaxArrayLength; + } + + return newSize; + } + + /// <summary> + /// A wrapper struct used as LinkedSlotVolatile[] - an array of LinkedSlot instances, but with volatile semantics + /// on array accesses. + /// </summary> + private struct LinkedSlotVolatile + { + internal volatile LinkedSlot Value; + } + + /// <summary> + /// A node in the doubly-linked list stored in the ThreadLocal instance. + /// + /// The value is stored in one of two places: + /// + /// 1. If SlotArray is not null, the value is in SlotArray.Table[id] + /// 2. If SlotArray is null, the value is in FinalValue. + /// </summary> + private sealed class LinkedSlot + { + internal LinkedSlot(LinkedSlotVolatile[] slotArray) + { + SlotArray = slotArray; + } + + // The next LinkedSlot for this ThreadLocal<> instance + internal volatile LinkedSlot Next; + + // The previous LinkedSlot for this ThreadLocal<> instance + internal volatile LinkedSlot Previous; + + // The SlotArray that stores this LinkedSlot at SlotArray.Table[id]. + internal volatile LinkedSlotVolatile[] SlotArray; + + // The value for this slot. + internal T Value; + } + + /// <summary> + /// A manager class that assigns IDs to ThreadLocal instances + /// </summary> + private class IdManager + { + // The next ID to try + private int m_nextIdToTry = 0; + + // Stores whether each ID is free or not. Additionally, the object is also used as a lock for the IdManager. + private List<bool> m_freeIds = new List<bool>(); + + internal int GetId() + { + lock (m_freeIds) + { + int availableId = m_nextIdToTry; + while (availableId < m_freeIds.Count) + { + if (m_freeIds[availableId]) { break; } + availableId++; + } + + if (availableId == m_freeIds.Count) + { + m_freeIds.Add(false); + } + else + { + m_freeIds[availableId] = false; + } + + m_nextIdToTry = availableId + 1; + + return availableId; + } + } + + // Return an ID to the pool + internal void ReturnId(int id) + { + lock (m_freeIds) + { + m_freeIds[id] = true; + if (id < m_nextIdToTry) m_nextIdToTry = id; + } + } + } + + /// <summary> + /// A class that facilitates ThreadLocal cleanup after a thread exits. + /// + /// After a thread with an associated thread-local table has exited, the FinalizationHelper + /// is reponsible for removing back-references to the table. Since an instance of FinalizationHelper + /// is only referenced from a single thread-local slot, the FinalizationHelper will be GC'd once + /// the thread has exited. + /// + /// The FinalizationHelper then locates all LinkedSlot instances with back-references to the table + /// (all those LinkedSlot instances can be found by following references from the table slots) and + /// releases the table so that it can get GC'd. + /// </summary> + private class FinalizationHelper + { + internal LinkedSlotVolatile[] SlotArray; + private bool m_trackAllValues; + + internal FinalizationHelper(LinkedSlotVolatile[] slotArray, bool trackAllValues) + { + SlotArray = slotArray; + m_trackAllValues = trackAllValues; + } + + ~FinalizationHelper() + { + LinkedSlotVolatile[] slotArray = SlotArray; + Contract.Assert(slotArray != null); + + for (int i = 0; i < slotArray.Length; i++) + { + LinkedSlot linkedSlot = slotArray[i].Value; + if (linkedSlot == null) + { + // This slot in the table is empty + continue; + } + + if (m_trackAllValues) + { + // Set the SlotArray field to null to release the slot array. + linkedSlot.SlotArray = null; + } + else + { + // Remove the LinkedSlot from the linked list. Once the FinalizationHelper is done, all back-references to + // the table will be have been removed, and so the table can get GC'd. + lock (s_idManager) + { + if (linkedSlot.Next != null) + { + linkedSlot.Next.Previous = linkedSlot.Previous; + } + + // Since the list uses a dummy head node, the Previous reference should never be null. + Contract.Assert(linkedSlot.Previous != null); + linkedSlot.Previous.Next = linkedSlot.Next; + } + } + } + } + } + } + + /// <summary>A debugger view of the ThreadLocal<T> to surface additional debugging properties and + /// to ensure that the ThreadLocal<T> does not become initialized if it was not already.</summary> + internal sealed class SystemThreading_ThreadLocalDebugView<T> + { + //The ThreadLocal object being viewed. + private readonly ThreadLocal<T> m_tlocal; + + /// <summary>Constructs a new debugger view object for the provided ThreadLocal object.</summary> + /// <param name="tlocal">A ThreadLocal object to browse in the debugger.</param> + public SystemThreading_ThreadLocalDebugView(ThreadLocal<T> tlocal) + { + m_tlocal = tlocal; + } + + /// <summary>Returns whether the ThreadLocal object is initialized or not.</summary> + public bool IsValueCreated + { + get { return m_tlocal.IsValueCreated; } + } + + /// <summary>Returns the value of the ThreadLocal object.</summary> + public T Value + { + get + { + return m_tlocal.ValueForDebugDisplay; + } + } + + /// <summary>Return all values for all threads that have accessed this instance.</summary> + public List<T> Values + { + get + { + return m_tlocal.ValuesForDebugDisplay; + } + } + } +} diff --git a/src/mscorlib/src/System/Threading/ThreadPool.cs b/src/mscorlib/src/System/Threading/ThreadPool.cs new file mode 100644 index 0000000000..09fe93c682 --- /dev/null +++ b/src/mscorlib/src/System/Threading/ThreadPool.cs @@ -0,0 +1,1954 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +/*============================================================================= +** +** +** +** Purpose: Class for creating and managing a threadpool +** +** +=============================================================================*/ + +#pragma warning disable 0420 + +/* + * Below you'll notice two sets of APIs that are separated by the + * use of 'Unsafe' in their names. The unsafe versions are called + * that because they do not propagate the calling stack onto the + * worker thread. This allows code to lose the calling stack and + * thereby elevate its security privileges. Note that this operation + * is much akin to the combined ability to control security policy + * and control security evidence. With these privileges, a person + * can gain the right to load assemblies that are fully trusted which + * then assert full trust and can call any code they want regardless + * of the previous stack information. + */ + +namespace System.Threading +{ + using System.Security; + using System.Runtime.Remoting; + using System.Security.Permissions; + using System; + using Microsoft.Win32; + using System.Runtime.CompilerServices; + using System.Runtime.ConstrainedExecution; + using System.Runtime.InteropServices; + using System.Runtime.Versioning; + using System.Collections.Generic; + using System.Diagnostics.Contracts; + using System.Diagnostics.CodeAnalysis; + using System.Diagnostics.Tracing; + + internal static class ThreadPoolGlobals + { + //Per-appDomain quantum (in ms) for which the thread keeps processing + //requests in the current domain. + public static uint tpQuantum = 30U; + + public static int processorCount = Environment.ProcessorCount; + + public static bool tpHosted = ThreadPool.IsThreadPoolHosted(); + + public static volatile bool vmTpInitialized; + public static bool enableWorkerTracking; + + [SecurityCritical] + public static ThreadPoolWorkQueue workQueue = new ThreadPoolWorkQueue(); + + [System.Security.SecuritySafeCritical] // static constructors should be safe to call + static ThreadPoolGlobals() + { + } + } + + internal sealed class ThreadPoolWorkQueue + { + // Simple sparsely populated array to allow lock-free reading. + internal class SparseArray<T> where T : class + { + private volatile T[] m_array; + + internal SparseArray(int initialSize) + { + m_array = new T[initialSize]; + } + + internal T[] Current + { + get { return m_array; } + } + + internal int Add(T e) + { + while (true) + { + T[] array = m_array; + lock (array) + { + for (int i = 0; i < array.Length; i++) + { + if (array[i] == null) + { + Volatile.Write(ref array[i], e); + return i; + } + else if (i == array.Length - 1) + { + // Must resize. If there was a race condition, we start over again. + if (array != m_array) + continue; + + T[] newArray = new T[array.Length * 2]; + Array.Copy(array, newArray, i + 1); + newArray[i + 1] = e; + m_array = newArray; + return i + 1; + } + } + } + } + } + + internal void Remove(T e) + { + T[] array = m_array; + lock (array) + { + for (int i = 0; i < m_array.Length; i++) + { + if (m_array[i] == e) + { + Volatile.Write(ref m_array[i], null); + break; + } + } + } + } + } + + internal class WorkStealingQueue + { + private const int INITIAL_SIZE = 32; + internal volatile IThreadPoolWorkItem[] m_array = new IThreadPoolWorkItem[INITIAL_SIZE]; + private volatile int m_mask = INITIAL_SIZE - 1; + +#if DEBUG + // in debug builds, start at the end so we exercise the index reset logic. + private const int START_INDEX = int.MaxValue; +#else + private const int START_INDEX = 0; +#endif + + private volatile int m_headIndex = START_INDEX; + private volatile int m_tailIndex = START_INDEX; + + private SpinLock m_foreignLock = new SpinLock(false); + + public void LocalPush(IThreadPoolWorkItem obj) + { + int tail = m_tailIndex; + + // We're going to increment the tail; if we'll overflow, then we need to reset our counts + if (tail == int.MaxValue) + { + bool lockTaken = false; + try + { + m_foreignLock.Enter(ref lockTaken); + + if (m_tailIndex == int.MaxValue) + { + // + // Rather than resetting to zero, we'll just mask off the bits we don't care about. + // This way we don't need to rearrange the items already in the queue; they'll be found + // correctly exactly where they are. One subtlety here is that we need to make sure that + // if head is currently < tail, it remains that way. This happens to just fall out from + // the bit-masking, because we only do this if tail == int.MaxValue, meaning that all + // bits are set, so all of the bits we're keeping will also be set. Thus it's impossible + // for the head to end up > than the tail, since you can't set any more bits than all of + // them. + // + m_headIndex = m_headIndex & m_mask; + m_tailIndex = tail = m_tailIndex & m_mask; + Contract.Assert(m_headIndex <= m_tailIndex); + } + } + finally + { + if (lockTaken) + m_foreignLock.Exit(true); + } + } + + // When there are at least 2 elements' worth of space, we can take the fast path. + if (tail < m_headIndex + m_mask) + { + Volatile.Write(ref m_array[tail & m_mask], obj); + m_tailIndex = tail + 1; + } + else + { + // We need to contend with foreign pops, so we lock. + bool lockTaken = false; + try + { + m_foreignLock.Enter(ref lockTaken); + + int head = m_headIndex; + int count = m_tailIndex - m_headIndex; + + // If there is still space (one left), just add the element. + if (count >= m_mask) + { + // We're full; expand the queue by doubling its size. + IThreadPoolWorkItem[] newArray = new IThreadPoolWorkItem[m_array.Length << 1]; + for (int i = 0; i < m_array.Length; i++) + newArray[i] = m_array[(i + head) & m_mask]; + + // Reset the field values, incl. the mask. + m_array = newArray; + m_headIndex = 0; + m_tailIndex = tail = count; + m_mask = (m_mask << 1) | 1; + } + + Volatile.Write(ref m_array[tail & m_mask], obj); + m_tailIndex = tail + 1; + } + finally + { + if (lockTaken) + m_foreignLock.Exit(false); + } + } + } + + [SuppressMessage("Microsoft.Concurrency", "CA8001", Justification = "Reviewed for thread safety")] + public bool LocalFindAndPop(IThreadPoolWorkItem obj) + { + // Fast path: check the tail. If equal, we can skip the lock. + if (m_array[(m_tailIndex - 1) & m_mask] == obj) + { + IThreadPoolWorkItem unused; + if (LocalPop(out unused)) + { + Contract.Assert(unused == obj); + return true; + } + return false; + } + + // Else, do an O(N) search for the work item. The theory of work stealing and our + // inlining logic is that most waits will happen on recently queued work. And + // since recently queued work will be close to the tail end (which is where we + // begin our search), we will likely find it quickly. In the worst case, we + // will traverse the whole local queue; this is typically not going to be a + // problem (although degenerate cases are clearly an issue) because local work + // queues tend to be somewhat shallow in length, and because if we fail to find + // the work item, we are about to block anyway (which is very expensive). + for (int i = m_tailIndex - 2; i >= m_headIndex; i--) + { + if (m_array[i & m_mask] == obj) + { + // If we found the element, block out steals to avoid interference. + bool lockTaken = false; + try + { + m_foreignLock.Enter(ref lockTaken); + + // If we encountered a race condition, bail. + if (m_array[i & m_mask] == null) + return false; + + // Otherwise, null out the element. + Volatile.Write(ref m_array[i & m_mask], null); + + // And then check to see if we can fix up the indexes (if we're at + // the edge). If we can't, we just leave nulls in the array and they'll + // get filtered out eventually (but may lead to superflous resizing). + if (i == m_tailIndex) + m_tailIndex -= 1; + else if (i == m_headIndex) + m_headIndex += 1; + + return true; + } + finally + { + if (lockTaken) + m_foreignLock.Exit(false); + } + } + } + + return false; + } + + [SuppressMessage("Microsoft.Concurrency", "CA8001", Justification = "Reviewed for thread safety")] + public bool LocalPop(out IThreadPoolWorkItem obj) + { + while (true) + { + // Decrement the tail using a fence to ensure subsequent read doesn't come before. + int tail = m_tailIndex; + if (m_headIndex >= tail) + { + obj = null; + return false; + } + + tail -= 1; + Interlocked.Exchange(ref m_tailIndex, tail); + + // If there is no interaction with a take, we can head down the fast path. + if (m_headIndex <= tail) + { + int idx = tail & m_mask; + obj = Volatile.Read(ref m_array[idx]); + + // Check for nulls in the array. + if (obj == null) continue; + + m_array[idx] = null; + return true; + } + else + { + // Interaction with takes: 0 or 1 elements left. + bool lockTaken = false; + try + { + m_foreignLock.Enter(ref lockTaken); + + if (m_headIndex <= tail) + { + // Element still available. Take it. + int idx = tail & m_mask; + obj = Volatile.Read(ref m_array[idx]); + + // Check for nulls in the array. + if (obj == null) continue; + + m_array[idx] = null; + return true; + } + else + { + // If we encountered a race condition and element was stolen, restore the tail. + m_tailIndex = tail + 1; + obj = null; + return false; + } + } + finally + { + if (lockTaken) + m_foreignLock.Exit(false); + } + } + } + } + + public bool TrySteal(out IThreadPoolWorkItem obj, ref bool missedSteal) + { + return TrySteal(out obj, ref missedSteal, 0); // no blocking by default. + } + + private bool TrySteal(out IThreadPoolWorkItem obj, ref bool missedSteal, int millisecondsTimeout) + { + obj = null; + + while (true) + { + if (m_headIndex >= m_tailIndex) + return false; + + bool taken = false; + try + { + m_foreignLock.TryEnter(millisecondsTimeout, ref taken); + if (taken) + { + // Increment head, and ensure read of tail doesn't move before it (fence). + int head = m_headIndex; + Interlocked.Exchange(ref m_headIndex, head + 1); + + if (head < m_tailIndex) + { + int idx = head & m_mask; + obj = Volatile.Read(ref m_array[idx]); + + // Check for nulls in the array. + if (obj == null) continue; + + m_array[idx] = null; + return true; + } + else + { + // Failed, restore head. + m_headIndex = head; + obj = null; + missedSteal = true; + } + } + else + { + missedSteal = true; + } + } + finally + { + if (taken) + m_foreignLock.Exit(false); + } + + return false; + } + } + } + + internal class QueueSegment + { + // Holds a segment of the queue. Enqueues/Dequeues start at element 0, and work their way up. + internal readonly IThreadPoolWorkItem[] nodes; + private const int QueueSegmentLength = 256; + + // Holds the indexes of the lowest and highest valid elements of the nodes array. + // The low index is in the lower 16 bits, high index is in the upper 16 bits. + // Use GetIndexes and CompareExchangeIndexes to manipulate this. + private volatile int indexes; + + // The next segment in the queue. + public volatile QueueSegment Next; + + + const int SixteenBits = 0xffff; + + void GetIndexes(out int upper, out int lower) + { + int i = indexes; + upper = (i >> 16) & SixteenBits; + lower = i & SixteenBits; + + Contract.Assert(upper >= lower); + Contract.Assert(upper <= nodes.Length); + Contract.Assert(lower <= nodes.Length); + Contract.Assert(upper >= 0); + Contract.Assert(lower >= 0); + } + + bool CompareExchangeIndexes(ref int prevUpper, int newUpper, ref int prevLower, int newLower) + { + Contract.Assert(newUpper >= newLower); + Contract.Assert(newUpper <= nodes.Length); + Contract.Assert(newLower <= nodes.Length); + Contract.Assert(newUpper >= 0); + Contract.Assert(newLower >= 0); + Contract.Assert(newUpper >= prevUpper); + Contract.Assert(newLower >= prevLower); + Contract.Assert(newUpper == prevUpper ^ newLower == prevLower); + + int oldIndexes = (prevUpper << 16) | (prevLower & SixteenBits); + int newIndexes = (newUpper << 16) | (newLower & SixteenBits); + int prevIndexes = Interlocked.CompareExchange(ref indexes, newIndexes, oldIndexes); + prevUpper = (prevIndexes >> 16) & SixteenBits; + prevLower = prevIndexes & SixteenBits; + return prevIndexes == oldIndexes; + } + + [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)] + public QueueSegment() + { + Contract.Assert(QueueSegmentLength <= SixteenBits); + nodes = new IThreadPoolWorkItem[QueueSegmentLength]; + } + + + public bool IsUsedUp() + { + int upper, lower; + GetIndexes(out upper, out lower); + return (upper == nodes.Length) && + (lower == nodes.Length); + } + + public bool TryEnqueue(IThreadPoolWorkItem node) + { + // + // If there's room in this segment, atomically increment the upper count (to reserve + // space for this node), then store the node. + // Note that this leaves a window where it will look like there is data in that + // array slot, but it hasn't been written yet. This is taken care of in TryDequeue + // with a busy-wait loop, waiting for the element to become non-null. This implies + // that we can never store null nodes in this data structure. + // + Contract.Assert(null != node); + + int upper, lower; + GetIndexes(out upper, out lower); + + while (true) + { + if (upper == nodes.Length) + return false; + + if (CompareExchangeIndexes(ref upper, upper + 1, ref lower, lower)) + { + Contract.Assert(Volatile.Read(ref nodes[upper]) == null); + Volatile.Write(ref nodes[upper], node); + return true; + } + } + } + + [SuppressMessage("Microsoft.Concurrency", "CA8001", Justification = "Reviewed for thread safety")] + public bool TryDequeue(out IThreadPoolWorkItem node) + { + // + // If there are nodes in this segment, increment the lower count, then take the + // element we find there. + // + int upper, lower; + GetIndexes(out upper, out lower); + + while(true) + { + if (lower == upper) + { + node = null; + return false; + } + + if (CompareExchangeIndexes(ref upper, upper, ref lower, lower + 1)) + { + // It's possible that a concurrent call to Enqueue hasn't yet + // written the node reference to the array. We need to spin until + // it shows up. + SpinWait spinner = new SpinWait(); + while ((node = Volatile.Read(ref nodes[lower])) == null) + spinner.SpinOnce(); + + // Null-out the reference so the object can be GC'd earlier. + nodes[lower] = null; + + return true; + } + } + } + } + + // The head and tail of the queue. We enqueue to the head, and dequeue from the tail. + internal volatile QueueSegment queueHead; + internal volatile QueueSegment queueTail; + internal bool loggingEnabled; + + internal static SparseArray<WorkStealingQueue> allThreadQueues = new SparseArray<WorkStealingQueue>(16); + + private volatile int numOutstandingThreadRequests = 0; + + public ThreadPoolWorkQueue() + { + queueTail = queueHead = new QueueSegment(); + loggingEnabled = FrameworkEventSource.Log.IsEnabled(EventLevel.Verbose, FrameworkEventSource.Keywords.ThreadPool|FrameworkEventSource.Keywords.ThreadTransfer); + } + + [SecurityCritical] + public ThreadPoolWorkQueueThreadLocals EnsureCurrentThreadHasQueue() + { + if (null == ThreadPoolWorkQueueThreadLocals.threadLocals) + ThreadPoolWorkQueueThreadLocals.threadLocals = new ThreadPoolWorkQueueThreadLocals(this); + return ThreadPoolWorkQueueThreadLocals.threadLocals; + } + + [SecurityCritical] + internal void EnsureThreadRequested() + { + // + // If we have not yet requested #procs threads from the VM, then request a new thread. + // Note that there is a separate count in the VM which will also be incremented in this case, + // which is handled by RequestWorkerThread. + // + int count = numOutstandingThreadRequests; + while (count < ThreadPoolGlobals.processorCount) + { + int prev = Interlocked.CompareExchange(ref numOutstandingThreadRequests, count+1, count); + if (prev == count) + { + ThreadPool.RequestWorkerThread(); + break; + } + count = prev; + } + } + + [SecurityCritical] + internal void MarkThreadRequestSatisfied() + { + // + // The VM has called us, so one of our outstanding thread requests has been satisfied. + // Decrement the count so that future calls to EnsureThreadRequested will succeed. + // Note that there is a separate count in the VM which has already been decremented by the VM + // by the time we reach this point. + // + int count = numOutstandingThreadRequests; + while (count > 0) + { + int prev = Interlocked.CompareExchange(ref numOutstandingThreadRequests, count - 1, count); + if (prev == count) + { + break; + } + count = prev; + } + } + + [SecurityCritical] + public void Enqueue(IThreadPoolWorkItem callback, bool forceGlobal) + { + ThreadPoolWorkQueueThreadLocals tl = null; + if (!forceGlobal) + tl = ThreadPoolWorkQueueThreadLocals.threadLocals; + + if (loggingEnabled) + System.Diagnostics.Tracing.FrameworkEventSource.Log.ThreadPoolEnqueueWorkObject(callback); + + if (null != tl) + { + tl.workStealingQueue.LocalPush(callback); + } + else + { + QueueSegment head = queueHead; + + while (!head.TryEnqueue(callback)) + { + Interlocked.CompareExchange(ref head.Next, new QueueSegment(), null); + + while (head.Next != null) + { + Interlocked.CompareExchange(ref queueHead, head.Next, head); + head = queueHead; + } + } + } + + EnsureThreadRequested(); + } + + [SecurityCritical] + internal bool LocalFindAndPop(IThreadPoolWorkItem callback) + { + ThreadPoolWorkQueueThreadLocals tl = ThreadPoolWorkQueueThreadLocals.threadLocals; + if (null == tl) + return false; + + return tl.workStealingQueue.LocalFindAndPop(callback); + } + + [SecurityCritical] + public void Dequeue(ThreadPoolWorkQueueThreadLocals tl, out IThreadPoolWorkItem callback, out bool missedSteal) + { + callback = null; + missedSteal = false; + WorkStealingQueue wsq = tl.workStealingQueue; + + if (wsq.LocalPop(out callback)) + Contract.Assert(null != callback); + + if (null == callback) + { + QueueSegment tail = queueTail; + while (true) + { + if (tail.TryDequeue(out callback)) + { + Contract.Assert(null != callback); + break; + } + + if (null == tail.Next || !tail.IsUsedUp()) + { + break; + } + else + { + Interlocked.CompareExchange(ref queueTail, tail.Next, tail); + tail = queueTail; + } + } + } + + if (null == callback) + { + WorkStealingQueue[] otherQueues = allThreadQueues.Current; + int c = otherQueues.Length; + int maxIndex = c - 1; + int i = tl.random.Next(c); + while (c > 0) + { + i = (i < maxIndex) ? i + 1 : 0; + WorkStealingQueue otherQueue = Volatile.Read(ref otherQueues[i]); + if (otherQueue != null && + otherQueue != wsq && + otherQueue.TrySteal(out callback, ref missedSteal)) + { + Contract.Assert(null != callback); + break; + } + c--; + } + } + } + + [SecurityCritical] + static internal bool Dispatch() + { + var workQueue = ThreadPoolGlobals.workQueue; + // + // The clock is ticking! We have ThreadPoolGlobals.tpQuantum milliseconds to get some work done, and then + // we need to return to the VM. + // + int quantumStartTime = Environment.TickCount; + + // + // Update our records to indicate that an outstanding request for a thread has now been fulfilled. + // From this point on, we are responsible for requesting another thread if we stop working for any + // reason, and we believe there might still be work in the queue. + // + // Note that if this thread is aborted before we get a chance to request another one, the VM will + // record a thread request on our behalf. So we don't need to worry about getting aborted right here. + // + workQueue.MarkThreadRequestSatisfied(); + + // Has the desire for logging changed since the last time we entered? + workQueue.loggingEnabled = FrameworkEventSource.Log.IsEnabled(EventLevel.Verbose, FrameworkEventSource.Keywords.ThreadPool|FrameworkEventSource.Keywords.ThreadTransfer); + + // + // Assume that we're going to need another thread if this one returns to the VM. We'll set this to + // false later, but only if we're absolutely certain that the queue is empty. + // + bool needAnotherThread = true; + IThreadPoolWorkItem workItem = null; + try + { + // + // Set up our thread-local data + // + ThreadPoolWorkQueueThreadLocals tl = workQueue.EnsureCurrentThreadHasQueue(); + + // + // Loop until our quantum expires. + // + while ((Environment.TickCount - quantumStartTime) < ThreadPoolGlobals.tpQuantum) + { + // + // Dequeue and EnsureThreadRequested must be protected from ThreadAbortException. + // These are fast, so this will not delay aborts/AD-unloads for very long. + // + try { } + finally + { + bool missedSteal = false; + workQueue.Dequeue(tl, out workItem, out missedSteal); + + if (workItem == null) + { + // + // No work. We're going to return to the VM once we leave this protected region. + // If we missed a steal, though, there may be more work in the queue. + // Instead of looping around and trying again, we'll just request another thread. This way + // we won't starve other AppDomains while we spin trying to get locks, and hopefully the thread + // that owns the contended work-stealing queue will pick up its own workitems in the meantime, + // which will be more efficient than this thread doing it anyway. + // + needAnotherThread = missedSteal; + } + else + { + // + // If we found work, there may be more work. Ask for another thread so that the other work can be processed + // in parallel. Note that this will only ask for a max of #procs threads, so it's safe to call it for every dequeue. + // + workQueue.EnsureThreadRequested(); + } + } + + if (workItem == null) + { + // Tell the VM we're returning normally, not because Hill Climbing asked us to return. + return true; + } + else + { + if (workQueue.loggingEnabled) + System.Diagnostics.Tracing.FrameworkEventSource.Log.ThreadPoolDequeueWorkObject(workItem); + + // + // Execute the workitem outside of any finally blocks, so that it can be aborted if needed. + // + if (ThreadPoolGlobals.enableWorkerTracking) + { + bool reportedStatus = false; + try + { + try { } + finally + { + ThreadPool.ReportThreadStatus(true); + reportedStatus = true; + } + workItem.ExecuteWorkItem(); + workItem = null; + } + finally + { + if (reportedStatus) + ThreadPool.ReportThreadStatus(false); + } + } + else + { + workItem.ExecuteWorkItem(); + workItem = null; + } + + // + // Notify the VM that we executed this workitem. This is also our opportunity to ask whether Hill Climbing wants + // us to return the thread to the pool or not. + // + if (!ThreadPool.NotifyWorkItemComplete()) + return false; + } + } + // If we get here, it's because our quantum expired. Tell the VM we're returning normally. + return true; + } + catch (ThreadAbortException tae) + { + // + // This is here to catch the case where this thread is aborted between the time we exit the finally block in the dispatch + // loop, and the time we execute the work item. QueueUserWorkItemCallback uses this to update its accounting of whether + // it was executed or not (in debug builds only). Task uses this to communicate the ThreadAbortException to anyone + // who waits for the task to complete. + // + if (workItem != null) + workItem.MarkAborted(tae); + + // + // In this case, the VM is going to request another thread on our behalf. No need to do it twice. + // + needAnotherThread = false; + // throw; //no need to explicitly rethrow a ThreadAbortException, and doing so causes allocations on amd64. + } + finally + { + // + // If we are exiting for any reason other than that the queue is definitely empty, ask for another + // thread to pick up where we left off. + // + if (needAnotherThread) + workQueue.EnsureThreadRequested(); + } + + // we can never reach this point, but the C# compiler doesn't know that, because it doesn't know the ThreadAbortException will be reraised above. + Contract.Assert(false); + return true; + } + } + + // Holds a WorkStealingQueue, and remmoves it from the list when this object is no longer referened. + internal sealed class ThreadPoolWorkQueueThreadLocals + { + [ThreadStatic] + [SecurityCritical] + public static ThreadPoolWorkQueueThreadLocals threadLocals; + + public readonly ThreadPoolWorkQueue workQueue; + public readonly ThreadPoolWorkQueue.WorkStealingQueue workStealingQueue; + public readonly Random random = new Random(Thread.CurrentThread.ManagedThreadId); + + public ThreadPoolWorkQueueThreadLocals(ThreadPoolWorkQueue tpq) + { + workQueue = tpq; + workStealingQueue = new ThreadPoolWorkQueue.WorkStealingQueue(); + ThreadPoolWorkQueue.allThreadQueues.Add(workStealingQueue); + } + + [SecurityCritical] + private void CleanUp() + { + if (null != workStealingQueue) + { + if (null != workQueue) + { + bool done = false; + while (!done) + { + // Ensure that we won't be aborted between LocalPop and Enqueue. + try { } + finally + { + IThreadPoolWorkItem cb = null; + if (workStealingQueue.LocalPop(out cb)) + { + Contract.Assert(null != cb); + workQueue.Enqueue(cb, true); + } + else + { + done = true; + } + } + } + } + + ThreadPoolWorkQueue.allThreadQueues.Remove(workStealingQueue); + } + } + + [SecuritySafeCritical] + ~ThreadPoolWorkQueueThreadLocals() + { + // Since the purpose of calling CleanUp is to transfer any pending workitems into the global + // queue so that they will be executed by another thread, there's no point in doing this cleanup + // if we're in the process of shutting down or unloading the AD. In those cases, the work won't + // execute anyway. And there are subtle race conditions involved there that would lead us to do the wrong + // thing anyway. So we'll only clean up if this is a "normal" finalization. + if (!(Environment.HasShutdownStarted || AppDomain.CurrentDomain.IsFinalizingForUnload())) + CleanUp(); + } + } + + internal sealed class RegisteredWaitHandleSafe : CriticalFinalizerObject + { + private static IntPtr InvalidHandle + { + [System.Security.SecuritySafeCritical] // auto-generated + get + { + return Win32Native.INVALID_HANDLE_VALUE; + } + } + private IntPtr registeredWaitHandle; + private WaitHandle m_internalWaitObject; + private bool bReleaseNeeded = false; + private volatile int m_lock = 0; + + #if FEATURE_CORECLR + [System.Security.SecuritySafeCritical] // auto-generated + #endif + internal RegisteredWaitHandleSafe() + { + registeredWaitHandle = InvalidHandle; + } + + internal IntPtr GetHandle() + { + return registeredWaitHandle; + } + + internal void SetHandle(IntPtr handle) + { + registeredWaitHandle = handle; + } + + [System.Security.SecurityCritical] // auto-generated + [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)] + internal void SetWaitObject(WaitHandle waitObject) + { + // needed for DangerousAddRef + RuntimeHelpers.PrepareConstrainedRegions(); + try + { + } + finally + { + m_internalWaitObject = waitObject; + if (waitObject != null) + { + m_internalWaitObject.SafeWaitHandle.DangerousAddRef(ref bReleaseNeeded); + } + } + } + + [System.Security.SecurityCritical] // auto-generated + [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)] + internal bool Unregister( + WaitHandle waitObject // object to be notified when all callbacks to delegates have completed + ) + { + bool result = false; + // needed for DangerousRelease + RuntimeHelpers.PrepareConstrainedRegions(); + try + { + } + finally + { + // lock(this) cannot be used reliably in Cer since thin lock could be + // promoted to syncblock and that is not a guaranteed operation + bool bLockTaken = false; + do + { + if (Interlocked.CompareExchange(ref m_lock, 1, 0) == 0) + { + bLockTaken = true; + try + { + if (ValidHandle()) + { + result = UnregisterWaitNative(GetHandle(), waitObject == null ? null : waitObject.SafeWaitHandle); + if (result == true) + { + if (bReleaseNeeded) + { + m_internalWaitObject.SafeWaitHandle.DangerousRelease(); + bReleaseNeeded = false; + } + // if result not true don't release/suppress here so finalizer can make another attempt + SetHandle(InvalidHandle); + m_internalWaitObject = null; + GC.SuppressFinalize(this); + } + } + } + finally + { + m_lock = 0; + } + } + Thread.SpinWait(1); // yield to processor + } + while (!bLockTaken); + } + return result; + } + + private bool ValidHandle() + { + return (registeredWaitHandle != InvalidHandle && registeredWaitHandle != IntPtr.Zero); + } + + [System.Security.SecuritySafeCritical] // auto-generated + ~RegisteredWaitHandleSafe() + { + // if the app has already unregistered the wait, there is nothing to cleanup + // we can detect this by checking the handle. Normally, there is no race condition here + // so no need to protect reading of handle. However, if this object gets + // resurrected and then someone does an unregister, it would introduce a race condition + // + // PrepareConstrainedRegions call not needed since finalizer already in Cer + // + // lock(this) cannot be used reliably even in Cer since thin lock could be + // promoted to syncblock and that is not a guaranteed operation + // + // Note that we will not "spin" to get this lock. We make only a single attempt; + // if we can't get the lock, it means some other thread is in the middle of a call + // to Unregister, which will do the work of the finalizer anyway. + // + // Further, it's actually critical that we *not* wait for the lock here, because + // the other thread that's in the middle of Unregister may be suspended for shutdown. + // Then, during the live-object finalization phase of shutdown, this thread would + // end up spinning forever, as the other thread would never release the lock. + // This will result in a "leak" of sorts (since the handle will not be cleaned up) + // but the process is exiting anyway. + // + // During AD-unload, we don’t finalize live objects until all threads have been + // aborted out of the AD. Since these locked regions are CERs, we won’t abort them + // while the lock is held. So there should be no leak on AD-unload. + // + if (Interlocked.CompareExchange(ref m_lock, 1, 0) == 0) + { + try + { + if (ValidHandle()) + { + WaitHandleCleanupNative(registeredWaitHandle); + if (bReleaseNeeded) + { + m_internalWaitObject.SafeWaitHandle.DangerousRelease(); + bReleaseNeeded = false; + } + SetHandle(InvalidHandle); + m_internalWaitObject = null; + } + } + finally + { + m_lock = 0; + } + } + } + + [System.Security.SecurityCritical] // auto-generated + [MethodImplAttribute(MethodImplOptions.InternalCall)] + private static extern void WaitHandleCleanupNative(IntPtr handle); + + [System.Security.SecurityCritical] // auto-generated + [MethodImplAttribute(MethodImplOptions.InternalCall)] + private static extern bool UnregisterWaitNative(IntPtr handle, SafeHandle waitObject); + } + +[System.Runtime.InteropServices.ComVisible(true)] +#if FEATURE_REMOTING + public sealed class RegisteredWaitHandle : MarshalByRefObject { +#else // FEATURE_REMOTING + public sealed class RegisteredWaitHandle { +#endif // FEATURE_REMOTING + private RegisteredWaitHandleSafe internalRegisteredWait; + + internal RegisteredWaitHandle() + { + internalRegisteredWait = new RegisteredWaitHandleSafe(); + } + + internal void SetHandle(IntPtr handle) + { + internalRegisteredWait.SetHandle(handle); + } + + [System.Security.SecurityCritical] // auto-generated + internal void SetWaitObject(WaitHandle waitObject) + { + internalRegisteredWait.SetWaitObject(waitObject); + } + + +[System.Security.SecuritySafeCritical] // auto-generated +[System.Runtime.InteropServices.ComVisible(true)] + // This is the only public method on this class + public bool Unregister( + WaitHandle waitObject // object to be notified when all callbacks to delegates have completed + ) + { + return internalRegisteredWait.Unregister(waitObject); + } + } + + [System.Runtime.InteropServices.ComVisible(true)] + public delegate void WaitCallback(Object state); + + [System.Runtime.InteropServices.ComVisible(true)] + public delegate void WaitOrTimerCallback(Object state, bool timedOut); // signalled or timed out + + // + // This type is necessary because VS 2010's debugger looks for a method named _ThreadPoolWaitCallbacck.PerformWaitCallback + // on the stack to determine if a thread is a ThreadPool thread or not. We have a better way to do this for .NET 4.5, but + // still need to maintain compatibility with VS 2010. When compat with VS 2010 is no longer an issue, this type may be + // removed. + // + internal static class _ThreadPoolWaitCallback + { + [System.Security.SecurityCritical] + static internal bool PerformWaitCallback() + { + return ThreadPoolWorkQueue.Dispatch(); + } + } + + // + // Interface to something that can be queued to the TP. This is implemented by + // QueueUserWorkItemCallback, Task, and potentially other internal types. + // For example, SemaphoreSlim represents callbacks using its own type that + // implements IThreadPoolWorkItem. + // + // If we decide to expose some of the workstealing + // stuff, this is NOT the thing we want to expose to the public. + // + internal interface IThreadPoolWorkItem + { + [SecurityCritical] + void ExecuteWorkItem(); + [SecurityCritical] + void MarkAborted(ThreadAbortException tae); + } + + internal sealed class QueueUserWorkItemCallback : IThreadPoolWorkItem + { + [System.Security.SecuritySafeCritical] + static QueueUserWorkItemCallback() {} + + private WaitCallback callback; + private ExecutionContext context; + private Object state; + +#if DEBUG + volatile int executed; + + ~QueueUserWorkItemCallback() + { + Contract.Assert( + executed != 0 || Environment.HasShutdownStarted || AppDomain.CurrentDomain.IsFinalizingForUnload(), + "A QueueUserWorkItemCallback was never called!"); + } + + void MarkExecuted(bool aborted) + { + GC.SuppressFinalize(this); + Contract.Assert( + 0 == Interlocked.Exchange(ref executed, 1) || aborted, + "A QueueUserWorkItemCallback was called twice!"); + } +#endif + + [SecurityCritical] + internal QueueUserWorkItemCallback(WaitCallback waitCallback, Object stateObj, ExecutionContext ec) + { + callback = waitCallback; + state = stateObj; + context = ec; + } + + [SecurityCritical] + void IThreadPoolWorkItem.ExecuteWorkItem() + { +#if DEBUG + MarkExecuted(false); +#endif + // call directly if it is an unsafe call OR EC flow is suppressed + if (context == null) + { + WaitCallback cb = callback; + callback = null; + cb(state); + } + else + { + ExecutionContext.Run(context, ccb, this, true); + } + } + + [SecurityCritical] + void IThreadPoolWorkItem.MarkAborted(ThreadAbortException tae) + { +#if DEBUG + // this workitem didn't execute because we got a ThreadAbortException prior to the call to ExecuteWorkItem. + // This counts as being executed for our purposes. + MarkExecuted(true); +#endif + } + + [System.Security.SecurityCritical] + static internal ContextCallback ccb = new ContextCallback(WaitCallback_Context); + + [System.Security.SecurityCritical] + static private void WaitCallback_Context(Object state) + { + QueueUserWorkItemCallback obj = (QueueUserWorkItemCallback)state; + WaitCallback wc = obj.callback as WaitCallback; + Contract.Assert(null != wc); + wc(obj.state); + } + } + + internal sealed class QueueUserWorkItemCallbackDefaultContext : IThreadPoolWorkItem + { + [System.Security.SecuritySafeCritical] + static QueueUserWorkItemCallbackDefaultContext() { } + + private WaitCallback callback; + private Object state; + +#if DEBUG + private volatile int executed; + + ~QueueUserWorkItemCallbackDefaultContext() + { + Contract.Assert( + executed != 0 || Environment.HasShutdownStarted || AppDomain.CurrentDomain.IsFinalizingForUnload(), + "A QueueUserWorkItemCallbackDefaultContext was never called!"); + } + + void MarkExecuted(bool aborted) + { + GC.SuppressFinalize(this); + Contract.Assert( + 0 == Interlocked.Exchange(ref executed, 1) || aborted, + "A QueueUserWorkItemCallbackDefaultContext was called twice!"); + } +#endif + + [SecurityCritical] + internal QueueUserWorkItemCallbackDefaultContext(WaitCallback waitCallback, Object stateObj) + { + callback = waitCallback; + state = stateObj; + } + + [SecurityCritical] + void IThreadPoolWorkItem.ExecuteWorkItem() + { +#if DEBUG + MarkExecuted(false); +#endif + ExecutionContext.Run(ExecutionContext.PreAllocatedDefault, ccb, this, true); + } + + [SecurityCritical] + void IThreadPoolWorkItem.MarkAborted(ThreadAbortException tae) + { +#if DEBUG + // this workitem didn't execute because we got a ThreadAbortException prior to the call to ExecuteWorkItem. + // This counts as being executed for our purposes. + MarkExecuted(true); +#endif + } + + [System.Security.SecurityCritical] + static internal ContextCallback ccb = new ContextCallback(WaitCallback_Context); + + [System.Security.SecurityCritical] + static private void WaitCallback_Context(Object state) + { + QueueUserWorkItemCallbackDefaultContext obj = (QueueUserWorkItemCallbackDefaultContext)state; + WaitCallback wc = obj.callback as WaitCallback; + Contract.Assert(null != wc); + obj.callback = null; + wc(obj.state); + } + } + + internal class _ThreadPoolWaitOrTimerCallback + { + [System.Security.SecuritySafeCritical] + static _ThreadPoolWaitOrTimerCallback() {} + + WaitOrTimerCallback _waitOrTimerCallback; + ExecutionContext _executionContext; + Object _state; + [System.Security.SecurityCritical] + static private ContextCallback _ccbt = new ContextCallback(WaitOrTimerCallback_Context_t); + [System.Security.SecurityCritical] + static private ContextCallback _ccbf = new ContextCallback(WaitOrTimerCallback_Context_f); + + [System.Security.SecurityCritical] // auto-generated + internal _ThreadPoolWaitOrTimerCallback(WaitOrTimerCallback waitOrTimerCallback, Object state, bool compressStack, ref StackCrawlMark stackMark) + { + _waitOrTimerCallback = waitOrTimerCallback; + _state = state; + + if (compressStack && !ExecutionContext.IsFlowSuppressed()) + { + // capture the exection context + _executionContext = ExecutionContext.Capture( + ref stackMark, + ExecutionContext.CaptureOptions.IgnoreSyncCtx | ExecutionContext.CaptureOptions.OptimizeDefaultCase); + } + } + + [System.Security.SecurityCritical] + static private void WaitOrTimerCallback_Context_t(Object state) + { + WaitOrTimerCallback_Context(state, true); + } + + [System.Security.SecurityCritical] + static private void WaitOrTimerCallback_Context_f(Object state) + { + WaitOrTimerCallback_Context(state, false); + } + + static private void WaitOrTimerCallback_Context(Object state, bool timedOut) + { + _ThreadPoolWaitOrTimerCallback helper = (_ThreadPoolWaitOrTimerCallback)state; + helper._waitOrTimerCallback(helper._state, timedOut); + } + + // call back helper + [System.Security.SecurityCritical] // auto-generated + static internal void PerformWaitOrTimerCallback(Object state, bool timedOut) + { + _ThreadPoolWaitOrTimerCallback helper = (_ThreadPoolWaitOrTimerCallback)state; + Contract.Assert(helper != null, "Null state passed to PerformWaitOrTimerCallback!"); + // call directly if it is an unsafe call OR EC flow is suppressed + if (helper._executionContext == null) + { + WaitOrTimerCallback callback = helper._waitOrTimerCallback; + callback(helper._state, timedOut); + } + else + { + using (ExecutionContext executionContext = helper._executionContext.CreateCopy()) + { + if (timedOut) + ExecutionContext.Run(executionContext, _ccbt, helper, true); + else + ExecutionContext.Run(executionContext, _ccbf, helper, true); + } + } + } + + } + + [System.Security.SecurityCritical] + [CLSCompliant(false)] + [System.Runtime.InteropServices.ComVisible(true)] + unsafe public delegate void IOCompletionCallback(uint errorCode, // Error code + uint numBytes, // No. of bytes transferred + NativeOverlapped* pOVERLAP // ptr to OVERLAP structure + ); + + [HostProtection(Synchronization=true, ExternalThreading=true)] + public static class ThreadPool + { + + #if FEATURE_CORECLR + [System.Security.SecurityCritical] // auto-generated + #else + [System.Security.SecuritySafeCritical] + #endif +#pragma warning disable 618 + [SecurityPermissionAttribute(SecurityAction.Demand, ControlThread = true)] +#pragma warning restore 618 + public static bool SetMaxThreads(int workerThreads, int completionPortThreads) + { + return SetMaxThreadsNative(workerThreads, completionPortThreads); + } + + [System.Security.SecuritySafeCritical] // auto-generated + public static void GetMaxThreads(out int workerThreads, out int completionPortThreads) + { + GetMaxThreadsNative(out workerThreads, out completionPortThreads); + } + + #if FEATURE_CORECLR + [System.Security.SecurityCritical] // auto-generated + #else + [System.Security.SecuritySafeCritical] + #endif +#pragma warning disable 618 + [SecurityPermissionAttribute(SecurityAction.Demand, ControlThread = true)] +#pragma warning restore 618 + public static bool SetMinThreads(int workerThreads, int completionPortThreads) + { + return SetMinThreadsNative(workerThreads, completionPortThreads); + } + + [System.Security.SecuritySafeCritical] // auto-generated + public static void GetMinThreads(out int workerThreads, out int completionPortThreads) + { + GetMinThreadsNative(out workerThreads, out completionPortThreads); + } + + [System.Security.SecuritySafeCritical] // auto-generated + public static void GetAvailableThreads(out int workerThreads, out int completionPortThreads) + { + GetAvailableThreadsNative(out workerThreads, out completionPortThreads); + } + + [System.Security.SecuritySafeCritical] // auto-generated + [CLSCompliant(false)] + [MethodImplAttribute(MethodImplOptions.NoInlining)] // Methods containing StackCrawlMark local var has to be marked non-inlineable + public static RegisteredWaitHandle RegisterWaitForSingleObject( // throws RegisterWaitException + WaitHandle waitObject, + WaitOrTimerCallback callBack, + Object state, + uint millisecondsTimeOutInterval, + bool executeOnlyOnce // NOTE: we do not allow other options that allow the callback to be queued as an APC + ) + { + StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller; + return RegisterWaitForSingleObject(waitObject,callBack,state,millisecondsTimeOutInterval,executeOnlyOnce,ref stackMark,true); + } + + [System.Security.SecurityCritical] // auto-generated_required + [CLSCompliant(false)] + [MethodImplAttribute(MethodImplOptions.NoInlining)] // Methods containing StackCrawlMark local var has to be marked non-inlineable + public static RegisteredWaitHandle UnsafeRegisterWaitForSingleObject( // throws RegisterWaitException + WaitHandle waitObject, + WaitOrTimerCallback callBack, + Object state, + uint millisecondsTimeOutInterval, + bool executeOnlyOnce // NOTE: we do not allow other options that allow the callback to be queued as an APC + ) + { + StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller; + return RegisterWaitForSingleObject(waitObject,callBack,state,millisecondsTimeOutInterval,executeOnlyOnce,ref stackMark,false); + } + + + [System.Security.SecurityCritical] // auto-generated + private static RegisteredWaitHandle RegisterWaitForSingleObject( // throws RegisterWaitException + WaitHandle waitObject, + WaitOrTimerCallback callBack, + Object state, + uint millisecondsTimeOutInterval, + bool executeOnlyOnce, // NOTE: we do not allow other options that allow the callback to be queued as an APC + ref StackCrawlMark stackMark, + bool compressStack + ) + { +#if FEATURE_REMOTING + if (RemotingServices.IsTransparentProxy(waitObject)) + throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_WaitOnTransparentProxy")); + Contract.EndContractBlock(); +#endif + + RegisteredWaitHandle registeredWaitHandle = new RegisteredWaitHandle(); + + if (callBack != null) + { + _ThreadPoolWaitOrTimerCallback callBackHelper = new _ThreadPoolWaitOrTimerCallback(callBack, state, compressStack, ref stackMark); + state = (Object)callBackHelper; + // call SetWaitObject before native call so that waitObject won't be closed before threadpoolmgr registration + // this could occur if callback were to fire before SetWaitObject does its addref + registeredWaitHandle.SetWaitObject(waitObject); + IntPtr nativeRegisteredWaitHandle = RegisterWaitForSingleObjectNative(waitObject, + state, + millisecondsTimeOutInterval, + executeOnlyOnce, + registeredWaitHandle, + ref stackMark, + compressStack); + registeredWaitHandle.SetHandle(nativeRegisteredWaitHandle); + } + else + { + throw new ArgumentNullException("WaitOrTimerCallback"); + } + return registeredWaitHandle; + } + + + [System.Security.SecuritySafeCritical] // auto-generated + [MethodImplAttribute(MethodImplOptions.NoInlining)] // Methods containing StackCrawlMark local var has to be marked non-inlineable + public static RegisteredWaitHandle RegisterWaitForSingleObject( // throws RegisterWaitException + WaitHandle waitObject, + WaitOrTimerCallback callBack, + Object state, + int millisecondsTimeOutInterval, + bool executeOnlyOnce // NOTE: we do not allow other options that allow the callback to be queued as an APC + ) + { + if (millisecondsTimeOutInterval < -1) + throw new ArgumentOutOfRangeException("millisecondsTimeOutInterval", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegOrNegative1")); + Contract.EndContractBlock(); + StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller; + return RegisterWaitForSingleObject(waitObject,callBack,state,(UInt32)millisecondsTimeOutInterval,executeOnlyOnce,ref stackMark,true); + } + + [System.Security.SecurityCritical] // auto-generated_required + [MethodImplAttribute(MethodImplOptions.NoInlining)] // Methods containing StackCrawlMark local var has to be marked non-inlineable + public static RegisteredWaitHandle UnsafeRegisterWaitForSingleObject( // throws RegisterWaitException + WaitHandle waitObject, + WaitOrTimerCallback callBack, + Object state, + int millisecondsTimeOutInterval, + bool executeOnlyOnce // NOTE: we do not allow other options that allow the callback to be queued as an APC + ) + { + if (millisecondsTimeOutInterval < -1) + throw new ArgumentOutOfRangeException("millisecondsTimeOutInterval", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegOrNegative1")); + Contract.EndContractBlock(); + StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller; + return RegisterWaitForSingleObject(waitObject,callBack,state,(UInt32)millisecondsTimeOutInterval,executeOnlyOnce,ref stackMark,false); + } + + [System.Security.SecuritySafeCritical] // auto-generated + [MethodImplAttribute(MethodImplOptions.NoInlining)] // Methods containing StackCrawlMark local var has to be marked non-inlineable + public static RegisteredWaitHandle RegisterWaitForSingleObject( // throws RegisterWaitException + WaitHandle waitObject, + WaitOrTimerCallback callBack, + Object state, + long millisecondsTimeOutInterval, + bool executeOnlyOnce // NOTE: we do not allow other options that allow the callback to be queued as an APC + ) + { + if (millisecondsTimeOutInterval < -1) + throw new ArgumentOutOfRangeException("millisecondsTimeOutInterval", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegOrNegative1")); + Contract.EndContractBlock(); + StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller; + return RegisterWaitForSingleObject(waitObject,callBack,state,(UInt32)millisecondsTimeOutInterval,executeOnlyOnce,ref stackMark,true); + } + + [System.Security.SecurityCritical] // auto-generated_required + [MethodImplAttribute(MethodImplOptions.NoInlining)] // Methods containing StackCrawlMark local var has to be marked non-inlineable + public static RegisteredWaitHandle UnsafeRegisterWaitForSingleObject( // throws RegisterWaitException + WaitHandle waitObject, + WaitOrTimerCallback callBack, + Object state, + long millisecondsTimeOutInterval, + bool executeOnlyOnce // NOTE: we do not allow other options that allow the callback to be queued as an APC + ) + { + if (millisecondsTimeOutInterval < -1) + throw new ArgumentOutOfRangeException("millisecondsTimeOutInterval", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegOrNegative1")); + Contract.EndContractBlock(); + StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller; + return RegisterWaitForSingleObject(waitObject,callBack,state,(UInt32)millisecondsTimeOutInterval,executeOnlyOnce,ref stackMark,false); + } + + [System.Security.SecuritySafeCritical] // auto-generated + [MethodImplAttribute(MethodImplOptions.NoInlining)] // Methods containing StackCrawlMark local var has to be marked non-inlineable + public static RegisteredWaitHandle RegisterWaitForSingleObject( + WaitHandle waitObject, + WaitOrTimerCallback callBack, + Object state, + TimeSpan timeout, + bool executeOnlyOnce + ) + { + long tm = (long)timeout.TotalMilliseconds; + if (tm < -1) + throw new ArgumentOutOfRangeException("timeout", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegOrNegative1")); + if (tm > (long) Int32.MaxValue) + throw new ArgumentOutOfRangeException("timeout", Environment.GetResourceString("ArgumentOutOfRange_LessEqualToIntegerMaxVal")); + StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller; + return RegisterWaitForSingleObject(waitObject,callBack,state,(UInt32)tm,executeOnlyOnce,ref stackMark,true); + } + + [System.Security.SecurityCritical] // auto-generated_required + [MethodImplAttribute(MethodImplOptions.NoInlining)] // Methods containing StackCrawlMark local var has to be marked non-inlineable + public static RegisteredWaitHandle UnsafeRegisterWaitForSingleObject( + WaitHandle waitObject, + WaitOrTimerCallback callBack, + Object state, + TimeSpan timeout, + bool executeOnlyOnce + ) + { + long tm = (long)timeout.TotalMilliseconds; + if (tm < -1) + throw new ArgumentOutOfRangeException("timeout", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegOrNegative1")); + if (tm > (long) Int32.MaxValue) + throw new ArgumentOutOfRangeException("timeout", Environment.GetResourceString("ArgumentOutOfRange_LessEqualToIntegerMaxVal")); + StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller; + return RegisterWaitForSingleObject(waitObject,callBack,state,(UInt32)tm,executeOnlyOnce,ref stackMark,false); + } + + [System.Security.SecuritySafeCritical] // auto-generated + [MethodImplAttribute(MethodImplOptions.NoInlining)] // Methods containing StackCrawlMark local var has to be marked non-inlineable + public static bool QueueUserWorkItem( + WaitCallback callBack, // NOTE: we do not expose options that allow the callback to be queued as an APC + Object state + ) + { + StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller; + return QueueUserWorkItemHelper(callBack,state,ref stackMark,true); + } + + [System.Security.SecuritySafeCritical] // auto-generated + [MethodImplAttribute(MethodImplOptions.NoInlining)] // Methods containing StackCrawlMark local var has to be marked non-inlineable + public static bool QueueUserWorkItem( + WaitCallback callBack // NOTE: we do not expose options that allow the callback to be queued as an APC + ) + { + StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller; + return QueueUserWorkItemHelper(callBack,null,ref stackMark,true); + } + + [System.Security.SecurityCritical] // auto-generated_required + [MethodImplAttribute(MethodImplOptions.NoInlining)] // Methods containing StackCrawlMark local var has to be marked non-inlineable + public static bool UnsafeQueueUserWorkItem( + WaitCallback callBack, // NOTE: we do not expose options that allow the callback to be queued as an APC + Object state + ) + { + StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller; + return QueueUserWorkItemHelper(callBack,state,ref stackMark,false); + } + + //ThreadPool has per-appdomain managed queue of work-items. The VM is + //responsible for just scheduling threads into appdomains. After that + //work-items are dispatched from the managed queue. + [System.Security.SecurityCritical] // auto-generated + private static bool QueueUserWorkItemHelper(WaitCallback callBack, Object state, ref StackCrawlMark stackMark, bool compressStack ) + { + bool success = true; + + if (callBack != null) + { + //The thread pool maintains a per-appdomain managed work queue. + //New thread pool entries are added in the managed queue. + //The VM is responsible for the actual growing/shrinking of + //threads. + + EnsureVMInitialized(); + + // + // If we are able to create the workitem, we need to get it in the queue without being interrupted + // by a ThreadAbortException. + // + try { } + finally + { + ExecutionContext context = compressStack && !ExecutionContext.IsFlowSuppressed() ? + ExecutionContext.Capture(ref stackMark, ExecutionContext.CaptureOptions.IgnoreSyncCtx | ExecutionContext.CaptureOptions.OptimizeDefaultCase) : + null; + + IThreadPoolWorkItem tpcallBack = context == ExecutionContext.PreAllocatedDefault ? + new QueueUserWorkItemCallbackDefaultContext(callBack, state) : + (IThreadPoolWorkItem)new QueueUserWorkItemCallback(callBack, state, context); + + ThreadPoolGlobals.workQueue.Enqueue(tpcallBack, true); + success = true; + } + } + else + { + throw new ArgumentNullException("WaitCallback"); + } + return success; + } + + [SecurityCritical] + internal static void UnsafeQueueCustomWorkItem(IThreadPoolWorkItem workItem, bool forceGlobal) + { + Contract.Assert(null != workItem); + EnsureVMInitialized(); + + // + // Enqueue needs to be protected from ThreadAbort + // + try { } + finally + { + ThreadPoolGlobals.workQueue.Enqueue(workItem, forceGlobal); + } + } + + // This method tries to take the target callback out of the current thread's queue. + [SecurityCritical] + internal static bool TryPopCustomWorkItem(IThreadPoolWorkItem workItem) + { + Contract.Assert(null != workItem); + if (!ThreadPoolGlobals.vmTpInitialized) + return false; //Not initialized, so there's no way this workitem was ever queued. + return ThreadPoolGlobals.workQueue.LocalFindAndPop(workItem); + } + + // Get all workitems. Called by TaskScheduler in its debugger hooks. + [SecurityCritical] + internal static IEnumerable<IThreadPoolWorkItem> GetQueuedWorkItems() + { + return EnumerateQueuedWorkItems(ThreadPoolWorkQueue.allThreadQueues.Current, ThreadPoolGlobals.workQueue.queueTail); + } + + internal static IEnumerable<IThreadPoolWorkItem> EnumerateQueuedWorkItems(ThreadPoolWorkQueue.WorkStealingQueue[] wsQueues, ThreadPoolWorkQueue.QueueSegment globalQueueTail) + { + if (wsQueues != null) + { + // First, enumerate all workitems in thread-local queues. + foreach (ThreadPoolWorkQueue.WorkStealingQueue wsq in wsQueues) + { + if (wsq != null && wsq.m_array != null) + { + IThreadPoolWorkItem[] items = wsq.m_array; + for (int i = 0; i < items.Length; i++) + { + IThreadPoolWorkItem item = items[i]; + if (item != null) + yield return item; + } + } + } + } + + if (globalQueueTail != null) + { + // Now the global queue + for (ThreadPoolWorkQueue.QueueSegment segment = globalQueueTail; + segment != null; + segment = segment.Next) + { + IThreadPoolWorkItem[] items = segment.nodes; + for (int i = 0; i < items.Length; i++) + { + IThreadPoolWorkItem item = items[i]; + if (item != null) + yield return item; + } + } + } + } + + [SecurityCritical] + internal static IEnumerable<IThreadPoolWorkItem> GetLocallyQueuedWorkItems() + { + return EnumerateQueuedWorkItems(new ThreadPoolWorkQueue.WorkStealingQueue[] { ThreadPoolWorkQueueThreadLocals.threadLocals.workStealingQueue }, null); + } + + [SecurityCritical] + internal static IEnumerable<IThreadPoolWorkItem> GetGloballyQueuedWorkItems() + { + return EnumerateQueuedWorkItems(null, ThreadPoolGlobals.workQueue.queueTail); + } + + private static object[] ToObjectArray(IEnumerable<IThreadPoolWorkItem> workitems) + { + int i = 0; + foreach (IThreadPoolWorkItem item in workitems) + { + i++; + } + + object[] result = new object[i]; + i = 0; + foreach (IThreadPoolWorkItem item in workitems) + { + if (i < result.Length) //just in case someone calls us while the queues are in motion + result[i] = item; + i++; + } + + return result; + } + + // This is the method the debugger will actually call, if it ends up calling + // into ThreadPool directly. Tests can use this to simulate a debugger, as well. + [SecurityCritical] + internal static object[] GetQueuedWorkItemsForDebugger() + { + return ToObjectArray(GetQueuedWorkItems()); + } + + [SecurityCritical] + internal static object[] GetGloballyQueuedWorkItemsForDebugger() + { + return ToObjectArray(GetGloballyQueuedWorkItems()); + } + + [SecurityCritical] + internal static object[] GetLocallyQueuedWorkItemsForDebugger() + { + return ToObjectArray(GetLocallyQueuedWorkItems()); + } + + [System.Security.SecurityCritical] // auto-generated + [DllImport(JitHelpers.QCall, CharSet = CharSet.Unicode)] + [SuppressUnmanagedCodeSecurity] + internal static extern bool RequestWorkerThread(); + + [System.Security.SecurityCritical] // auto-generated + [MethodImplAttribute(MethodImplOptions.InternalCall)] + unsafe private static extern bool PostQueuedCompletionStatus(NativeOverlapped* overlapped); + + [System.Security.SecurityCritical] // auto-generated_required + [CLSCompliant(false)] + unsafe public static bool UnsafeQueueNativeOverlapped(NativeOverlapped* overlapped) + { + return PostQueuedCompletionStatus(overlapped); + } + + [SecurityCritical] + private static void EnsureVMInitialized() + { + if (!ThreadPoolGlobals.vmTpInitialized) + { + ThreadPool.InitializeVMTp(ref ThreadPoolGlobals.enableWorkerTracking); + ThreadPoolGlobals.vmTpInitialized = true; + } + } + + // Native methods: + + [System.Security.SecurityCritical] // auto-generated + [MethodImplAttribute(MethodImplOptions.InternalCall)] + private static extern bool SetMinThreadsNative(int workerThreads, int completionPortThreads); + + [System.Security.SecurityCritical] // auto-generated + [MethodImplAttribute(MethodImplOptions.InternalCall)] + private static extern bool SetMaxThreadsNative(int workerThreads, int completionPortThreads); + + [System.Security.SecurityCritical] // auto-generated + [MethodImplAttribute(MethodImplOptions.InternalCall)] + private static extern void GetMinThreadsNative(out int workerThreads, out int completionPortThreads); + + [System.Security.SecurityCritical] // auto-generated + [MethodImplAttribute(MethodImplOptions.InternalCall)] + private static extern void GetMaxThreadsNative(out int workerThreads, out int completionPortThreads); + + [System.Security.SecurityCritical] // auto-generated + [MethodImplAttribute(MethodImplOptions.InternalCall)] + private static extern void GetAvailableThreadsNative(out int workerThreads, out int completionPortThreads); + + [System.Security.SecurityCritical] // auto-generated + [MethodImplAttribute(MethodImplOptions.InternalCall)] + internal static extern bool NotifyWorkItemComplete(); + + [System.Security.SecurityCritical] + [MethodImplAttribute(MethodImplOptions.InternalCall)] + internal static extern void ReportThreadStatus(bool isWorking); + + [System.Security.SecuritySafeCritical] + internal static void NotifyWorkItemProgress() + { + if (!ThreadPoolGlobals.vmTpInitialized) + ThreadPool.InitializeVMTp(ref ThreadPoolGlobals.enableWorkerTracking); + NotifyWorkItemProgressNative(); + } + + [System.Security.SecurityCritical] // auto-generated + [MethodImplAttribute(MethodImplOptions.InternalCall)] + internal static extern void NotifyWorkItemProgressNative(); + + [System.Security.SecurityCritical] // auto-generated + [MethodImplAttribute(MethodImplOptions.InternalCall)] + internal static extern bool IsThreadPoolHosted(); + + [System.Security.SecurityCritical] // auto-generated + [DllImport(JitHelpers.QCall, CharSet = CharSet.Unicode)] + [SuppressUnmanagedCodeSecurity] + private static extern void InitializeVMTp(ref bool enableWorkerTracking); + + [System.Security.SecurityCritical] // auto-generated + [MethodImplAttribute(MethodImplOptions.InternalCall)] + private static extern IntPtr RegisterWaitForSingleObjectNative( + WaitHandle waitHandle, + Object state, + uint timeOutInterval, + bool executeOnlyOnce, + RegisteredWaitHandle registeredWaitHandle, + ref StackCrawlMark stackMark, + bool compressStack + ); + +#if !FEATURE_CORECLR + [System.Security.SecuritySafeCritical] // auto-generated + [Obsolete("ThreadPool.BindHandle(IntPtr) has been deprecated. Please use ThreadPool.BindHandle(SafeHandle) instead.", false)] + [SecurityPermissionAttribute( SecurityAction.Demand, Flags = SecurityPermissionFlag.UnmanagedCode)] + public static bool BindHandle( + IntPtr osHandle + ) + { + return BindIOCompletionCallbackNative(osHandle); + } +#endif + + #if FEATURE_CORECLR + [System.Security.SecurityCritical] // auto-generated + #else + [System.Security.SecuritySafeCritical] + #endif +#pragma warning disable 618 + [SecurityPermissionAttribute(SecurityAction.Demand, Flags = SecurityPermissionFlag.UnmanagedCode)] +#pragma warning restore 618 + public static bool BindHandle(SafeHandle osHandle) + { + if (osHandle == null) + throw new ArgumentNullException("osHandle"); + + bool ret = false; + bool mustReleaseSafeHandle = false; + RuntimeHelpers.PrepareConstrainedRegions(); + try { + osHandle.DangerousAddRef(ref mustReleaseSafeHandle); + ret = BindIOCompletionCallbackNative(osHandle.DangerousGetHandle()); + } + finally { + if (mustReleaseSafeHandle) + osHandle.DangerousRelease(); + } + return ret; + } + + [System.Security.SecurityCritical] // auto-generated + [MethodImplAttribute(MethodImplOptions.InternalCall)] + [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)] + private static extern bool BindIOCompletionCallbackNative(IntPtr fileHandle); + } +} diff --git a/src/mscorlib/src/System/Threading/ThreadPriority.cs b/src/mscorlib/src/System/Threading/ThreadPriority.cs new file mode 100644 index 0000000000..c56156eb89 --- /dev/null +++ b/src/mscorlib/src/System/Threading/ThreadPriority.cs @@ -0,0 +1,32 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +// +/*============================================================================= +** +** +** +** Purpose: Enums for the priorities of a Thread +** +** +=============================================================================*/ + +namespace System.Threading { + using System.Threading; + + [Serializable] +[System.Runtime.InteropServices.ComVisible(true)] + public enum ThreadPriority + { + /*========================================================================= + ** Constants for thread priorities. + =========================================================================*/ + Lowest = 0, + BelowNormal = 1, + Normal = 2, + AboveNormal = 3, + Highest = 4 + + } +} diff --git a/src/mscorlib/src/System/Threading/ThreadStart.cs b/src/mscorlib/src/System/Threading/ThreadStart.cs new file mode 100644 index 0000000000..b968117195 --- /dev/null +++ b/src/mscorlib/src/System/Threading/ThreadStart.cs @@ -0,0 +1,25 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +// +/*============================================================================= +** +** +** +** Purpose: This class is a Delegate which defines the start method +** for starting a thread. That method must match this delegate. +** +** +=============================================================================*/ + +namespace System.Threading { + using System.Security.Permissions; + using System.Threading; + + // Define the delegate + // NOTE: If you change the signature here, there is code in COMSynchronization + // that invokes this delegate in native. +[System.Runtime.InteropServices.ComVisible(true)] + public delegate void ThreadStart(); +} diff --git a/src/mscorlib/src/System/Threading/ThreadStartException.cs b/src/mscorlib/src/System/Threading/ThreadStartException.cs new file mode 100644 index 0000000000..33fb460b3d --- /dev/null +++ b/src/mscorlib/src/System/Threading/ThreadStartException.cs @@ -0,0 +1,37 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +// + +namespace System.Threading +{ + using System; + using System.Runtime.Serialization; + using System.Runtime.InteropServices; + + [Serializable] + public sealed class ThreadStartException : SystemException + { + private ThreadStartException() + : base(Environment.GetResourceString("Arg_ThreadStartException")) + { + SetErrorCode(__HResults.COR_E_THREADSTART); + } + + private ThreadStartException(Exception reason) + : base(Environment.GetResourceString("Arg_ThreadStartException"), reason) + { + SetErrorCode(__HResults.COR_E_THREADSTART); + } + + //required for serialization + internal ThreadStartException(SerializationInfo info, StreamingContext context) + : base(info, context) + { + } + + } +} + + diff --git a/src/mscorlib/src/System/Threading/ThreadState.cs b/src/mscorlib/src/System/Threading/ThreadState.cs new file mode 100644 index 0000000000..007e1bf6e9 --- /dev/null +++ b/src/mscorlib/src/System/Threading/ThreadState.cs @@ -0,0 +1,36 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +// +/*============================================================================= +** +** +** +** Purpose: Enum to represent the different thread states +** +** +=============================================================================*/ + +namespace System.Threading { + +[Serializable] +[Flags] +[System.Runtime.InteropServices.ComVisible(true)] + public enum ThreadState + { + /*========================================================================= + ** Constants for thread states. + =========================================================================*/ + Running = 0, + StopRequested = 1, + SuspendRequested = 2, + Background = 4, + Unstarted = 8, + Stopped = 16, + WaitSleepJoin = 32, + Suspended = 64, + AbortRequested = 128, + Aborted = 256 + } +} diff --git a/src/mscorlib/src/System/Threading/ThreadStateException.cs b/src/mscorlib/src/System/Threading/ThreadStateException.cs new file mode 100644 index 0000000000..535dffcdbf --- /dev/null +++ b/src/mscorlib/src/System/Threading/ThreadStateException.cs @@ -0,0 +1,41 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +// +/*============================================================================= +** +** +** +** Purpose: An exception class to indicate that the Thread class is in an +** invalid state for the method. +** +** +=============================================================================*/ + +namespace System.Threading { + using System; + using System.Runtime.Serialization; + [System.Runtime.InteropServices.ComVisible(true)] + [Serializable] + public class ThreadStateException : SystemException { + public ThreadStateException() + : base(Environment.GetResourceString("Arg_ThreadStateException")) { + SetErrorCode(__HResults.COR_E_THREADSTATE); + } + + public ThreadStateException(String message) + : base(message) { + SetErrorCode(__HResults.COR_E_THREADSTATE); + } + + public ThreadStateException(String message, Exception innerException) + : base(message, innerException) { + SetErrorCode(__HResults.COR_E_THREADSTATE); + } + + protected ThreadStateException(SerializationInfo info, StreamingContext context) : base (info, context) { + } + } + +} diff --git a/src/mscorlib/src/System/Threading/Timeout.cs b/src/mscorlib/src/System/Threading/Timeout.cs new file mode 100644 index 0000000000..99e24159b2 --- /dev/null +++ b/src/mscorlib/src/System/Threading/Timeout.cs @@ -0,0 +1,21 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace System.Threading { + using System.Threading; + using System; + // A constant used by methods that take a timeout (Object.Wait, Thread.Sleep + // etc) to indicate that no timeout should occur. + // + [System.Runtime.InteropServices.ComVisible(true)] + public static class Timeout + { + [System.Runtime.InteropServices.ComVisible(false)] + public static readonly TimeSpan InfiniteTimeSpan = new TimeSpan(0, 0, 0, 0, Timeout.Infinite); + + public const int Infinite = -1; + internal const uint UnsignedInfinite = unchecked((uint)-1); + } + +} diff --git a/src/mscorlib/src/System/Threading/Timer.cs b/src/mscorlib/src/System/Threading/Timer.cs new file mode 100644 index 0000000000..cb08c6e033 --- /dev/null +++ b/src/mscorlib/src/System/Threading/Timer.cs @@ -0,0 +1,954 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +// + +namespace System.Threading +{ + using System; + using System.Security; + using System.Security.Permissions; + using Microsoft.Win32; + using System.Runtime.CompilerServices; + using System.Runtime.InteropServices; + using System.Runtime.ConstrainedExecution; + using System.Runtime.Versioning; + using System.Diagnostics.Contracts; + using System.Diagnostics.Tracing; + using Microsoft.Win32.SafeHandles; + + + + [System.Runtime.InteropServices.ComVisible(true)] + public delegate void TimerCallback(Object state); + + // + // TimerQueue maintains a list of active timers in this AppDomain. We use a single native timer, supplied by the VM, + // to schedule all managed timers in the AppDomain. + // + // Perf assumptions: We assume that timers are created and destroyed frequently, but rarely actually fire. + // There are roughly two types of timer: + // + // - timeouts for operations. These are created and destroyed very frequently, but almost never fire, because + // the whole point is that the timer only fires if something has gone wrong. + // + // - scheduled background tasks. These typically do fire, but they usually have quite long durations. + // So the impact of spending a few extra cycles to fire these is negligible. + // + // Because of this, we want to choose a data structure with very fast insert and delete times, but we can live + // with linear traversal times when firing timers. + // + // The data structure we've chosen is an unordered doubly-linked list of active timers. This gives O(1) insertion + // and removal, and O(N) traversal when finding expired timers. + // + // Note that all instance methods of this class require that the caller hold a lock on TimerQueue.Instance. + // + class TimerQueue + { + #region singleton pattern implementation + + // The one-and-only TimerQueue for the AppDomain. + static TimerQueue s_queue = new TimerQueue(); + + public static TimerQueue Instance + { + get { return s_queue; } + } + + private TimerQueue() + { + // empty private constructor to ensure we remain a singleton. + } + + #endregion + + #region interface to native per-AppDomain timer + + // + // We need to keep our notion of time synchronized with the calls to SleepEx that drive + // the underlying native timer. In Win8, SleepEx does not count the time the machine spends + // sleeping/hibernating. Environment.TickCount (GetTickCount) *does* count that time, + // so we will get out of sync with SleepEx if we use that method. + // + // So, on Win8, we use QueryUnbiasedInterruptTime instead; this does not count time spent + // in sleep/hibernate mode. + // + private static int TickCount + { + [SecuritySafeCritical] + get + { +#if !FEATURE_PAL + if (Environment.IsWindows8OrAbove) + { + ulong time100ns; + + bool result = Win32Native.QueryUnbiasedInterruptTime(out time100ns); + if (!result) + throw Marshal.GetExceptionForHR(Marshal.GetLastWin32Error()); + + // convert to 100ns to milliseconds, and truncate to 32 bits. + return (int)(uint)(time100ns / 10000); + } + else +#endif + { + return Environment.TickCount; + } + } + } + + // + // We use a SafeHandle to ensure that the native timer is destroyed when the AppDomain is unloaded. + // + [SecurityCritical] + class AppDomainTimerSafeHandle : SafeHandleZeroOrMinusOneIsInvalid + { + public AppDomainTimerSafeHandle() + : base(true) + { + } + + [SecurityCritical] + [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] + protected override bool ReleaseHandle() + { + return DeleteAppDomainTimer(handle); + } + } + + [SecurityCritical] + AppDomainTimerSafeHandle m_appDomainTimer; + + bool m_isAppDomainTimerScheduled; + int m_currentAppDomainTimerStartTicks; + uint m_currentAppDomainTimerDuration; + + [SecuritySafeCritical] + private bool EnsureAppDomainTimerFiresBy(uint requestedDuration) + { + // + // The VM's timer implementation does not work well for very long-duration timers. + // See kb 950807. + // So we'll limit our native timer duration to a "small" value. + // This may cause us to attempt to fire timers early, but that's ok - + // we'll just see that none of our timers has actually reached its due time, + // and schedule the native timer again. + // + const uint maxPossibleDuration = 0x0fffffff; + uint actualDuration = Math.Min(requestedDuration, maxPossibleDuration); + + if (m_isAppDomainTimerScheduled) + { + uint elapsed = (uint)(TickCount - m_currentAppDomainTimerStartTicks); + if (elapsed >= m_currentAppDomainTimerDuration) + return true; //the timer's about to fire + + uint remainingDuration = m_currentAppDomainTimerDuration - elapsed; + if (actualDuration >= remainingDuration) + return true; //the timer will fire earlier than this request + } + + // If Pause is underway then do not schedule the timers + // A later update during resume will re-schedule + if(m_pauseTicks != 0) + { + Contract.Assert(!m_isAppDomainTimerScheduled); + Contract.Assert(m_appDomainTimer == null); + return true; + } + + if (m_appDomainTimer == null || m_appDomainTimer.IsInvalid) + { + Contract.Assert(!m_isAppDomainTimerScheduled); + + m_appDomainTimer = CreateAppDomainTimer(actualDuration); + if (!m_appDomainTimer.IsInvalid) + { + m_isAppDomainTimerScheduled = true; + m_currentAppDomainTimerStartTicks = TickCount; + m_currentAppDomainTimerDuration = actualDuration; + return true; + } + else + { + return false; + } + } + else + { + if (ChangeAppDomainTimer(m_appDomainTimer, actualDuration)) + { + m_isAppDomainTimerScheduled = true; + m_currentAppDomainTimerStartTicks = TickCount; + m_currentAppDomainTimerDuration = actualDuration; + return true; + } + else + { + return false; + } + } + } + + // + // The VM calls this when the native timer fires. + // + [SecuritySafeCritical] + internal static void AppDomainTimerCallback() + { + Instance.FireNextTimers(); + } + + [System.Security.SecurityCritical] + [DllImport(JitHelpers.QCall, CharSet = CharSet.Unicode)] + [SuppressUnmanagedCodeSecurity] + static extern AppDomainTimerSafeHandle CreateAppDomainTimer(uint dueTime); + + [System.Security.SecurityCritical] + [DllImport(JitHelpers.QCall, CharSet = CharSet.Unicode)] + [SuppressUnmanagedCodeSecurity] + static extern bool ChangeAppDomainTimer(AppDomainTimerSafeHandle handle, uint dueTime); + + [System.Security.SecurityCritical] + [DllImport(JitHelpers.QCall, CharSet = CharSet.Unicode)] + [SuppressUnmanagedCodeSecurity] + [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] + static extern bool DeleteAppDomainTimer(IntPtr handle); + + #endregion + + #region Firing timers + + // + // The list of timers + // + TimerQueueTimer m_timers; + + + volatile int m_pauseTicks = 0; // Time when Pause was called + + [SecurityCritical] + internal void Pause() + { + lock(this) + { + // Delete the native timer so that no timers are fired in the Pause zone + if(m_appDomainTimer != null && !m_appDomainTimer.IsInvalid) + { + m_appDomainTimer.Dispose(); + m_appDomainTimer = null; + m_isAppDomainTimerScheduled = false; + m_pauseTicks = TickCount; + } + } + } + + [SecurityCritical] + internal void Resume() + { + // + // Update timers to adjust their due-time to accomodate Pause/Resume + // + lock (this) + { + // prevent ThreadAbort while updating state + try { } + finally + { + int pauseTicks = m_pauseTicks; + m_pauseTicks = 0; // Set this to 0 so that now timers can be scheduled + + int resumedTicks = TickCount; + int pauseDuration = resumedTicks - pauseTicks; + + bool haveTimerToSchedule = false; + uint nextAppDomainTimerDuration = uint.MaxValue; + + TimerQueueTimer timer = m_timers; + while (timer != null) + { + Contract.Assert(timer.m_dueTime != Timeout.UnsignedInfinite); + Contract.Assert(resumedTicks >= timer.m_startTicks); + + uint elapsed; // How much of the timer dueTime has already elapsed + + // Timers started before the paused event has to be sufficiently delayed to accomodate + // for the Pause time. However, timers started after the Paused event shouldnt be adjusted. + // E.g. ones created by the app in its Activated event should fire when it was designated. + // The Resumed event which is where this routine is executing is after this Activated and hence + // shouldn't delay this timer + + if(timer.m_startTicks <= pauseTicks) + elapsed = (uint)(pauseTicks - timer.m_startTicks); + else + elapsed = (uint)(resumedTicks - timer.m_startTicks); + + // Handling the corner cases where a Timer was already due by the time Resume is happening, + // We shouldn't delay those timers. + // Example is a timer started in App's Activated event with a very small duration + timer.m_dueTime = (timer.m_dueTime > elapsed) ? timer.m_dueTime - elapsed : 0;; + timer.m_startTicks = resumedTicks; // re-baseline + + if (timer.m_dueTime < nextAppDomainTimerDuration) + { + haveTimerToSchedule = true; + nextAppDomainTimerDuration = timer.m_dueTime; + } + + timer = timer.m_next; + } + + if (haveTimerToSchedule) + { + EnsureAppDomainTimerFiresBy(nextAppDomainTimerDuration); + } + } + } + } + + + // + // Fire any timers that have expired, and update the native timer to schedule the rest of them. + // + private void FireNextTimers() + { + // + // we fire the first timer on this thread; any other timers that might have fired are queued + // to the ThreadPool. + // + TimerQueueTimer timerToFireOnThisThread = null; + + lock (this) + { + // prevent ThreadAbort while updating state + try { } + finally + { + // + // since we got here, that means our previous timer has fired. + // + m_isAppDomainTimerScheduled = false; + bool haveTimerToSchedule = false; + uint nextAppDomainTimerDuration = uint.MaxValue; + + int nowTicks = TickCount; + + // + // Sweep through all timers. The ones that have reached their due time + // will fire. We will calculate the next native timer due time from the + // other timers. + // + TimerQueueTimer timer = m_timers; + while (timer != null) + { + Contract.Assert(timer.m_dueTime != Timeout.UnsignedInfinite); + + uint elapsed = (uint)(nowTicks - timer.m_startTicks); + if (elapsed >= timer.m_dueTime) + { + // + // Remember the next timer in case we delete this one + // + TimerQueueTimer nextTimer = timer.m_next; + + if (timer.m_period != Timeout.UnsignedInfinite) + { + timer.m_startTicks = nowTicks; + timer.m_dueTime = timer.m_period; + + // + // This is a repeating timer; schedule it to run again. + // + if (timer.m_dueTime < nextAppDomainTimerDuration) + { + haveTimerToSchedule = true; + nextAppDomainTimerDuration = timer.m_dueTime; + } + } + else + { + // + // Not repeating; remove it from the queue + // + DeleteTimer(timer); + } + + // + // If this is the first timer, we'll fire it on this thread. Otherwise, queue it + // to the ThreadPool. + // + if (timerToFireOnThisThread == null) + timerToFireOnThisThread = timer; + else + QueueTimerCompletion(timer); + + timer = nextTimer; + } + else + { + // + // This timer hasn't fired yet. Just update the next time the native timer fires. + // + uint remaining = timer.m_dueTime - elapsed; + if (remaining < nextAppDomainTimerDuration) + { + haveTimerToSchedule = true; + nextAppDomainTimerDuration = remaining; + } + timer = timer.m_next; + } + } + + if (haveTimerToSchedule) + EnsureAppDomainTimerFiresBy(nextAppDomainTimerDuration); + } + } + + // + // Fire the user timer outside of the lock! + // + if (timerToFireOnThisThread != null) + timerToFireOnThisThread.Fire(); + } + + [SecuritySafeCritical] + private static void QueueTimerCompletion(TimerQueueTimer timer) + { + WaitCallback callback = s_fireQueuedTimerCompletion; + if (callback == null) + s_fireQueuedTimerCompletion = callback = new WaitCallback(FireQueuedTimerCompletion); + + // Can use "unsafe" variant because we take care of capturing and restoring + // the ExecutionContext. + ThreadPool.UnsafeQueueUserWorkItem(callback, timer); + } + + private static WaitCallback s_fireQueuedTimerCompletion; + + private static void FireQueuedTimerCompletion(object state) + { + ((TimerQueueTimer)state).Fire(); + } + + #endregion + + #region Queue implementation + + public bool UpdateTimer(TimerQueueTimer timer, uint dueTime, uint period) + { + if (timer.m_dueTime == Timeout.UnsignedInfinite) + { + // the timer is not in the list; add it (as the head of the list). + timer.m_next = m_timers; + timer.m_prev = null; + if (timer.m_next != null) + timer.m_next.m_prev = timer; + m_timers = timer; + } + timer.m_dueTime = dueTime; + timer.m_period = (period == 0) ? Timeout.UnsignedInfinite : period; + timer.m_startTicks = TickCount; + return EnsureAppDomainTimerFiresBy(dueTime); + } + + public void DeleteTimer(TimerQueueTimer timer) + { + if (timer.m_dueTime != Timeout.UnsignedInfinite) + { + if (timer.m_next != null) + timer.m_next.m_prev = timer.m_prev; + if (timer.m_prev != null) + timer.m_prev.m_next = timer.m_next; + if (m_timers == timer) + m_timers = timer.m_next; + + timer.m_dueTime = Timeout.UnsignedInfinite; + timer.m_period = Timeout.UnsignedInfinite; + timer.m_startTicks = 0; + timer.m_prev = null; + timer.m_next = null; + } + } + + #endregion + } + + // + // A timer in our TimerQueue. + // + sealed class TimerQueueTimer + { + // + // All fields of this class are protected by a lock on TimerQueue.Instance. + // + // The first four fields are maintained by TimerQueue itself. + // + internal TimerQueueTimer m_next; + internal TimerQueueTimer m_prev; + + // + // The time, according to TimerQueue.TickCount, when this timer's current interval started. + // + internal int m_startTicks; + + // + // Timeout.UnsignedInfinite if we are not going to fire. Otherwise, the offset from m_startTime when we will fire. + // + internal uint m_dueTime; + + // + // Timeout.UnsignedInfinite if we are a single-shot timer. Otherwise, the repeat interval. + // + internal uint m_period; + + // + // Info about the user's callback + // + readonly TimerCallback m_timerCallback; + readonly Object m_state; + readonly ExecutionContext m_executionContext; + + + // + // When Timer.Dispose(WaitHandle) is used, we need to signal the wait handle only + // after all pending callbacks are complete. We set m_canceled to prevent any callbacks that + // are already queued from running. We track the number of callbacks currently executing in + // m_callbacksRunning. We set m_notifyWhenNoCallbacksRunning only when m_callbacksRunning + // reaches zero. + // + int m_callbacksRunning; + volatile bool m_canceled; + volatile WaitHandle m_notifyWhenNoCallbacksRunning; + + + [SecurityCritical] + internal TimerQueueTimer(TimerCallback timerCallback, object state, uint dueTime, uint period, ref StackCrawlMark stackMark) + { + m_timerCallback = timerCallback; + m_state = state; + m_dueTime = Timeout.UnsignedInfinite; + m_period = Timeout.UnsignedInfinite; + + if (!ExecutionContext.IsFlowSuppressed()) + { + m_executionContext = ExecutionContext.Capture( + ref stackMark, + ExecutionContext.CaptureOptions.IgnoreSyncCtx | ExecutionContext.CaptureOptions.OptimizeDefaultCase); + } + + // + // After the following statement, the timer may fire. No more manipulation of timer state outside of + // the lock is permitted beyond this point! + // + if (dueTime != Timeout.UnsignedInfinite) + Change(dueTime, period); + } + + + internal bool Change(uint dueTime, uint period) + { + bool success; + + lock (TimerQueue.Instance) + { + if (m_canceled) + throw new ObjectDisposedException(null, Environment.GetResourceString("ObjectDisposed_Generic")); + + // prevent ThreadAbort while updating state + try { } + finally + { + m_period = period; + + if (dueTime == Timeout.UnsignedInfinite) + { + TimerQueue.Instance.DeleteTimer(this); + success = true; + } + else + { + if (FrameworkEventSource.IsInitialized && FrameworkEventSource.Log.IsEnabled(EventLevel.Informational, FrameworkEventSource.Keywords.ThreadTransfer)) + FrameworkEventSource.Log.ThreadTransferSendObj(this, 1, string.Empty, true); + + success = TimerQueue.Instance.UpdateTimer(this, dueTime, period); + } + } + } + + return success; + } + + + public void Close() + { + lock (TimerQueue.Instance) + { + // prevent ThreadAbort while updating state + try { } + finally + { + if (!m_canceled) + { + m_canceled = true; + TimerQueue.Instance.DeleteTimer(this); + } + } + } + } + + + public bool Close(WaitHandle toSignal) + { + bool success; + bool shouldSignal = false; + + lock (TimerQueue.Instance) + { + // prevent ThreadAbort while updating state + try { } + finally + { + if (m_canceled) + { + success = false; + } + else + { + m_canceled = true; + m_notifyWhenNoCallbacksRunning = toSignal; + TimerQueue.Instance.DeleteTimer(this); + + if (m_callbacksRunning == 0) + shouldSignal = true; + + success = true; + } + } + } + + if (shouldSignal) + SignalNoCallbacksRunning(); + + return success; + } + + + internal void Fire() + { + bool canceled = false; + + lock (TimerQueue.Instance) + { + // prevent ThreadAbort while updating state + try { } + finally + { + canceled = m_canceled; + if (!canceled) + m_callbacksRunning++; + } + } + + if (canceled) + return; + + CallCallback(); + + bool shouldSignal = false; + lock (TimerQueue.Instance) + { + // prevent ThreadAbort while updating state + try { } + finally + { + m_callbacksRunning--; + if (m_canceled && m_callbacksRunning == 0 && m_notifyWhenNoCallbacksRunning != null) + shouldSignal = true; + } + } + + if (shouldSignal) + SignalNoCallbacksRunning(); + } + + [SecuritySafeCritical] + internal void SignalNoCallbacksRunning() + { + Win32Native.SetEvent(m_notifyWhenNoCallbacksRunning.SafeWaitHandle); + } + + [SecuritySafeCritical] + internal void CallCallback() + { + if (FrameworkEventSource.IsInitialized && FrameworkEventSource.Log.IsEnabled(EventLevel.Informational, FrameworkEventSource.Keywords.ThreadTransfer)) + FrameworkEventSource.Log.ThreadTransferReceiveObj(this, 1, string.Empty); + + // call directly if EC flow is suppressed + if (m_executionContext == null) + { + m_timerCallback(m_state); + } + else + { + using (ExecutionContext executionContext = + m_executionContext.IsPreAllocatedDefault ? m_executionContext : m_executionContext.CreateCopy()) + { + ContextCallback callback = s_callCallbackInContext; + if (callback == null) + s_callCallbackInContext = callback = new ContextCallback(CallCallbackInContext); + + ExecutionContext.Run( + executionContext, + callback, + this, // state + true); // ignoreSyncCtx + } + } + } + + [SecurityCritical] + private static ContextCallback s_callCallbackInContext; + + [SecurityCritical] + private static void CallCallbackInContext(object state) + { + TimerQueueTimer t = (TimerQueueTimer)state; + t.m_timerCallback(t.m_state); + } + } + + // + // TimerHolder serves as an intermediary between Timer and TimerQueueTimer, releasing the TimerQueueTimer + // if the Timer is collected. + // This is necessary because Timer itself cannot use its finalizer for this purpose. If it did, + // then users could control timer lifetimes using GC.SuppressFinalize/ReRegisterForFinalize. + // You might ask, wouldn't that be a good thing? Maybe (though it would be even better to offer this + // via first-class APIs), but Timer has never offered this, and adding it now would be a breaking + // change, because any code that happened to be suppressing finalization of Timer objects would now + // unwittingly be changing the lifetime of those timers. + // + sealed class TimerHolder + { + internal TimerQueueTimer m_timer; + + public TimerHolder(TimerQueueTimer timer) + { + m_timer = timer; + } + + ~TimerHolder() + { + // + // If shutdown has started, another thread may be suspended while holding the timer lock. + // So we can't safely close the timer. + // + // Similarly, we should not close the timer during AD-unload's live-object finalization phase. + // A rude abort may have prevented us from releasing the lock. + // + // Note that in either case, the Timer still won't fire, because ThreadPool threads won't be + // allowed to run in this AppDomain. + // + if (Environment.HasShutdownStarted || AppDomain.CurrentDomain.IsFinalizingForUnload()) + return; + + m_timer.Close(); + } + + public void Close() + { + m_timer.Close(); + GC.SuppressFinalize(this); + } + + public bool Close(WaitHandle notifyObject) + { + bool result = m_timer.Close(notifyObject); + GC.SuppressFinalize(this); + return result; + } + + } + + + [HostProtection(Synchronization=true, ExternalThreading=true)] + [System.Runtime.InteropServices.ComVisible(true)] +#if FEATURE_REMOTING + public sealed class Timer : MarshalByRefObject, IDisposable +#else // FEATURE_REMOTING + public sealed class Timer : IDisposable +#endif // FEATURE_REMOTING + { + private const UInt32 MAX_SUPPORTED_TIMEOUT = (uint)0xfffffffe; + + private TimerHolder m_timer; + + [SecuritySafeCritical] + [MethodImplAttribute(MethodImplOptions.NoInlining)] // Methods containing StackCrawlMark local var has to be marked non-inlineable + public Timer(TimerCallback callback, + Object state, + int dueTime, + int period) + { + if (dueTime < -1) + throw new ArgumentOutOfRangeException("dueTime", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegOrNegative1")); + if (period < -1 ) + throw new ArgumentOutOfRangeException("period", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegOrNegative1")); + Contract.EndContractBlock(); + StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller; + + TimerSetup(callback,state,(UInt32)dueTime,(UInt32)period,ref stackMark); + } + + [SecuritySafeCritical] + [MethodImplAttribute(MethodImplOptions.NoInlining)] // Methods containing StackCrawlMark local var has to be marked non-inlineable + public Timer(TimerCallback callback, + Object state, + TimeSpan dueTime, + TimeSpan period) + { + long dueTm = (long)dueTime.TotalMilliseconds; + if (dueTm < -1) + throw new ArgumentOutOfRangeException("dueTm",Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegOrNegative1")); + if (dueTm > MAX_SUPPORTED_TIMEOUT) + throw new ArgumentOutOfRangeException("dueTm",Environment.GetResourceString("ArgumentOutOfRange_TimeoutTooLarge")); + + long periodTm = (long)period.TotalMilliseconds; + if (periodTm < -1) + throw new ArgumentOutOfRangeException("periodTm",Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegOrNegative1")); + if (periodTm > MAX_SUPPORTED_TIMEOUT) + throw new ArgumentOutOfRangeException("periodTm",Environment.GetResourceString("ArgumentOutOfRange_PeriodTooLarge")); + + StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller; + TimerSetup(callback,state,(UInt32)dueTm,(UInt32)periodTm,ref stackMark); + } + + [CLSCompliant(false)] + [SecuritySafeCritical] + [MethodImplAttribute(MethodImplOptions.NoInlining)] // Methods containing StackCrawlMark local var has to be marked non-inlineable + public Timer(TimerCallback callback, + Object state, + UInt32 dueTime, + UInt32 period) + { + StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller; + TimerSetup(callback,state,dueTime,period,ref stackMark); + } + + [SecuritySafeCritical] + [MethodImplAttribute(MethodImplOptions.NoInlining)] // Methods containing StackCrawlMark local var has to be marked non-inlineable + public Timer(TimerCallback callback, + Object state, + long dueTime, + long period) + { + if (dueTime < -1) + throw new ArgumentOutOfRangeException("dueTime",Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegOrNegative1")); + if (period < -1) + throw new ArgumentOutOfRangeException("period",Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegOrNegative1")); + if (dueTime > MAX_SUPPORTED_TIMEOUT) + throw new ArgumentOutOfRangeException("dueTime",Environment.GetResourceString("ArgumentOutOfRange_TimeoutTooLarge")); + if (period > MAX_SUPPORTED_TIMEOUT) + throw new ArgumentOutOfRangeException("period",Environment.GetResourceString("ArgumentOutOfRange_PeriodTooLarge")); + Contract.EndContractBlock(); + StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller; + TimerSetup(callback,state,(UInt32) dueTime, (UInt32) period,ref stackMark); + } + + [SecuritySafeCritical] + [MethodImplAttribute(MethodImplOptions.NoInlining)] // Methods containing StackCrawlMark local var has to be marked non-inlineable + public Timer(TimerCallback callback) + { + int dueTime = -1; // we want timer to be registered, but not activated. Requires caller to call + int period = -1; // Change after a timer instance is created. This is to avoid the potential + // for a timer to be fired before the returned value is assigned to the variable, + // potentially causing the callback to reference a bogus value (if passing the timer to the callback). + + StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller; + TimerSetup(callback, this, (UInt32)dueTime, (UInt32)period, ref stackMark); + } + + [SecurityCritical] + private void TimerSetup(TimerCallback callback, + Object state, + UInt32 dueTime, + UInt32 period, + ref StackCrawlMark stackMark) + { + if (callback == null) + throw new ArgumentNullException("TimerCallback"); + Contract.EndContractBlock(); + + m_timer = new TimerHolder(new TimerQueueTimer(callback, state, dueTime, period, ref stackMark)); + } + + [SecurityCritical] + internal static void Pause() + { + TimerQueue.Instance.Pause(); + } + + [SecurityCritical] + internal static void Resume() + { + TimerQueue.Instance.Resume(); + } + + public bool Change(int dueTime, int period) + { + if (dueTime < -1 ) + throw new ArgumentOutOfRangeException("dueTime",Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegOrNegative1")); + if (period < -1) + throw new ArgumentOutOfRangeException("period",Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegOrNegative1")); + Contract.EndContractBlock(); + + return m_timer.m_timer.Change((UInt32)dueTime, (UInt32)period); + } + + public bool Change(TimeSpan dueTime, TimeSpan period) + { + return Change((long) dueTime.TotalMilliseconds, (long) period.TotalMilliseconds); + } + + [CLSCompliant(false)] + public bool Change(UInt32 dueTime, UInt32 period) + { + return m_timer.m_timer.Change(dueTime, period); + } + + public bool Change(long dueTime, long period) + { + if (dueTime < -1 ) + throw new ArgumentOutOfRangeException("dueTime", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegOrNegative1")); + if (period < -1) + throw new ArgumentOutOfRangeException("period", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegOrNegative1")); + if (dueTime > MAX_SUPPORTED_TIMEOUT) + throw new ArgumentOutOfRangeException("dueTime", Environment.GetResourceString("ArgumentOutOfRange_TimeoutTooLarge")); + if (period > MAX_SUPPORTED_TIMEOUT) + throw new ArgumentOutOfRangeException("period", Environment.GetResourceString("ArgumentOutOfRange_PeriodTooLarge")); + Contract.EndContractBlock(); + + return m_timer.m_timer.Change((UInt32)dueTime, (UInt32)period); + } + + public bool Dispose(WaitHandle notifyObject) + { + if (notifyObject==null) + throw new ArgumentNullException("notifyObject"); + Contract.EndContractBlock(); + + return m_timer.Close(notifyObject); + } + + public void Dispose() + { + m_timer.Close(); + } + + internal void KeepRootedWhileScheduled() + { + GC.SuppressFinalize(m_timer); + } + } +} diff --git a/src/mscorlib/src/System/Threading/Volatile.cs b/src/mscorlib/src/System/Threading/Volatile.cs new file mode 100644 index 0000000000..af687fbae1 --- /dev/null +++ b/src/mscorlib/src/System/Threading/Volatile.cs @@ -0,0 +1,441 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +// +using System; +using System.Runtime.InteropServices; +using System.Runtime.Versioning; +using System.Runtime.ConstrainedExecution; +using System.Runtime.CompilerServices; +using System.Runtime; +using System.Security; + +namespace System.Threading +{ + // + // Methods for accessing memory with volatile semantics. These are preferred over Thread.VolatileRead + // and Thread.VolatileWrite, as these are implemented more efficiently. + // + // (We cannot change the implementations of Thread.VolatileRead/VolatileWrite without breaking code + // that relies on their overly-strong ordering guarantees.) + // + // The actual implementations of these methods are typically supplied by the VM at JIT-time, because C# does + // not allow us to express a volatile read/write from/to a byref arg. + // See getILIntrinsicImplementationForVolatile() in jitinterface.cpp. + // + public static class Volatile + { + [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] + [System.Runtime.Versioning.NonVersionable] + public static bool Read(ref bool location) + { + // + // The VM will replace this with a more efficient implementation. + // + var value = location; + Thread.MemoryBarrier(); + return value; + } + + [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] + [CLSCompliant(false)] + [System.Runtime.Versioning.NonVersionable] + public static sbyte Read(ref sbyte location) + { + // + // The VM will replace this with a more efficient implementation. + // + var value = location; + Thread.MemoryBarrier(); + return value; + } + + [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] + [System.Runtime.Versioning.NonVersionable] + public static byte Read(ref byte location) + { + // + // The VM will replace this with a more efficient implementation. + // + var value = location; + Thread.MemoryBarrier(); + return value; + } + + [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] + [System.Runtime.Versioning.NonVersionable] + public static short Read(ref short location) + { + // + // The VM will replace this with a more efficient implementation. + // + var value = location; + Thread.MemoryBarrier(); + return value; + } + + [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] + [CLSCompliant(false)] + [System.Runtime.Versioning.NonVersionable] + public static ushort Read(ref ushort location) + { + // + // The VM will replace this with a more efficient implementation. + // + var value = location; + Thread.MemoryBarrier(); + return value; + } + + [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] + [System.Runtime.Versioning.NonVersionable] + public static int Read(ref int location) + { + // + // The VM will replace this with a more efficient implementation. + // + var value = location; + Thread.MemoryBarrier(); + return value; + } + + [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] + [CLSCompliant(false)] + [System.Runtime.Versioning.NonVersionable] + public static uint Read(ref uint location) + { + // + // The VM will replace this with a more efficient implementation. + // + var value = location; + Thread.MemoryBarrier(); + return value; + } + +#if BIT64 + [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] + [System.Runtime.Versioning.NonVersionable] + public static long Read(ref long location) + { + // + // The VM will replace this with a more efficient implementation. + // + var value = location; + Thread.MemoryBarrier(); + return value; + } + + [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] + [CLSCompliant(false)] + [System.Runtime.Versioning.NonVersionable] + [SecuritySafeCritical] // to match 32-bit version + public static ulong Read(ref ulong location) + { + // + // The VM will replace this with a more efficient implementation. + // + var value = location; + Thread.MemoryBarrier(); + return value; + } +#else + [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] + public static long Read(ref long location) + { + // + // On 32-bit machines, we use this implementation, since an ordinary volatile read + // would not be atomic. + // + // On 64-bit machines, the VM will replace this with a more efficient implementation. + // + return Interlocked.CompareExchange(ref location, 0, 0); + } + + [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] + [CLSCompliant(false)] + [SecuritySafeCritical] // contains unsafe code + public static ulong Read(ref ulong location) + { + unsafe + { + // + // There is no overload of Interlocked.Exchange that accepts a ulong. So we have + // to do some pointer tricks to pass our arguments to the overload that takes a long. + // + fixed (ulong* pLocation = &location) + { + return (ulong)Interlocked.CompareExchange(ref *(long*)pLocation, 0, 0); + } + } + } +#endif + + [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] + [System.Runtime.Versioning.NonVersionable] + public static IntPtr Read(ref IntPtr location) + { + // + // The VM will replace this with a more efficient implementation. + // + var value = location; + Thread.MemoryBarrier(); + return value; + } + + [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] + [CLSCompliant(false)] + [System.Runtime.Versioning.NonVersionable] + public static UIntPtr Read(ref UIntPtr location) + { + // + // The VM will replace this with a more efficient implementation. + // + var value = location; + Thread.MemoryBarrier(); + return value; + } + + [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] + [System.Runtime.Versioning.NonVersionable] + public static float Read(ref float location) + { + // + // The VM will replace this with a more efficient implementation. + // + var value = location; + Thread.MemoryBarrier(); + return value; + } + + [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] + [System.Runtime.Versioning.NonVersionable] + public static double Read(ref double location) + { + // + // On 32-bit machines, we use this implementation, since an ordinary volatile read + // would not be atomic. + // + // On 64-bit machines, the VM will replace this with a more efficient implementation. + // + return Interlocked.CompareExchange(ref location, 0, 0); + } + + [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] + [SecuritySafeCritical] //the intrinsic implementation of this method contains unverifiable code + [System.Runtime.Versioning.NonVersionable] + public static T Read<T>(ref T location) where T : class + { + // + // The VM will replace this with a more efficient implementation. + // + var value = location; + Thread.MemoryBarrier(); + return value; + } + + + + + [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] + [System.Runtime.Versioning.NonVersionable] + public static void Write(ref bool location, bool value) + { + // + // The VM will replace this with a more efficient implementation. + // + Thread.MemoryBarrier(); + location = value; + } + + [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] + [CLSCompliant(false)] + [System.Runtime.Versioning.NonVersionable] + public static void Write(ref sbyte location, sbyte value) + { + // + // The VM will replace this with a more efficient implementation. + // + Thread.MemoryBarrier(); + location = value; + } + + [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] + [System.Runtime.Versioning.NonVersionable] + public static void Write(ref byte location, byte value) + { + // + // The VM will replace this with a more efficient implementation. + // + Thread.MemoryBarrier(); + location = value; + } + + [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] + [System.Runtime.Versioning.NonVersionable] + public static void Write(ref short location, short value) + { + // + // The VM will replace this with a more efficient implementation. + // + Thread.MemoryBarrier(); + location = value; + } + + [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] + [CLSCompliant(false)] + [System.Runtime.Versioning.NonVersionable] + public static void Write(ref ushort location, ushort value) + { + // + // The VM will replace this with a more efficient implementation. + // + Thread.MemoryBarrier(); + location = value; + } + + [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] + [System.Runtime.Versioning.NonVersionable] + public static void Write(ref int location, int value) + { + // + // The VM will replace this with a more efficient implementation. + // + Thread.MemoryBarrier(); + location = value; + } + + [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] + [CLSCompliant(false)] + [System.Runtime.Versioning.NonVersionable] + public static void Write(ref uint location, uint value) + { + // + // The VM will replace this with a more efficient implementation. + // + Thread.MemoryBarrier(); + location = value; + } + +#if BIT64 + [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] + [System.Runtime.Versioning.NonVersionable] + public static void Write(ref long location, long value) + { + // + // The VM will replace this with a more efficient implementation. + // + Thread.MemoryBarrier(); + location = value; + } + + [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] + [CLSCompliant(false)] + [System.Runtime.Versioning.NonVersionable] + [SecuritySafeCritical] // to match 32-bit version + public static void Write(ref ulong location, ulong value) + { + // + // The VM will replace this with a more efficient implementation. + // + Thread.MemoryBarrier(); + location = value; + } +#else + [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] + public static void Write(ref long location, long value) + { + // + // On 32-bit machines, we use this implementation, since an ordinary volatile write + // would not be atomic. + // + // On 64-bit machines, the VM will replace this with a more efficient implementation. + // + Interlocked.Exchange(ref location, value); + } + + [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] + [CLSCompliant(false)] + [SecuritySafeCritical] // contains unsafe code + public static void Write(ref ulong location, ulong value) + { + // + // On 32-bit machines, we use this implementation, since an ordinary volatile write + // would not be atomic. + // + // On 64-bit machines, the VM will replace this with a more efficient implementation. + // + unsafe + { + // + // There is no overload of Interlocked.Exchange that accepts a ulong. So we have + // to do some pointer tricks to pass our arguments to the overload that takes a long. + // + fixed (ulong* pLocation = &location) + { + Interlocked.Exchange(ref *(long*)pLocation, (long)value); + } + } + } +#endif + + [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] + [System.Runtime.Versioning.NonVersionable] + public static void Write(ref IntPtr location, IntPtr value) + { + // + // The VM will replace this with a more efficient implementation. + // + Thread.MemoryBarrier(); + location = value; + } + + [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] + [CLSCompliant(false)] + [System.Runtime.Versioning.NonVersionable] + public static void Write(ref UIntPtr location, UIntPtr value) + { + // + // The VM will replace this with a more efficient implementation. + // + Thread.MemoryBarrier(); + location = value; + } + + [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] + [System.Runtime.Versioning.NonVersionable] + public static void Write(ref float location, float value) + { + // + // The VM will replace this with a more efficient implementation. + // + Thread.MemoryBarrier(); + location = value; + } + + [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] + [System.Runtime.Versioning.NonVersionable] + public static void Write(ref double location, double value) + { + // + // On 32-bit machines, we use this implementation, since an ordinary volatile write + // would not be atomic. + // + // On 64-bit machines, the VM will replace this with a more efficient implementation. + // + Interlocked.Exchange(ref location, value); + } + + [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] + [SecuritySafeCritical] //the intrinsic implementation of this method contains unverifiable code + [System.Runtime.Versioning.NonVersionable] + public static void Write<T>(ref T location, T value) where T : class + { + // + // The VM will replace this with a more efficient implementation. + // + Thread.MemoryBarrier(); + location = value; + } + } +} diff --git a/src/mscorlib/src/System/Threading/WaitHandle.cs b/src/mscorlib/src/System/Threading/WaitHandle.cs new file mode 100644 index 0000000000..9980c822a6 --- /dev/null +++ b/src/mscorlib/src/System/Threading/WaitHandle.cs @@ -0,0 +1,617 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +// +/*============================================================================= +** +** +** +** Purpose: Class to represent all synchronization objects in the runtime (that allow multiple wait) +** +** +=============================================================================*/ + +namespace System.Threading +{ + using System.Threading; + using System.Runtime.Remoting; + using System; + using System.Security.Permissions; + using System.Runtime.CompilerServices; + using System.Runtime.InteropServices; + using Microsoft.Win32.SafeHandles; + using System.Runtime.Versioning; + using System.Runtime.ConstrainedExecution; + using System.Diagnostics.Contracts; + using System.Diagnostics.CodeAnalysis; + using Win32Native = Microsoft.Win32.Win32Native; + +[System.Runtime.InteropServices.ComVisible(true)] +#if FEATURE_REMOTING + public abstract class WaitHandle : MarshalByRefObject, IDisposable { +#else // FEATURE_REMOTING + public abstract class WaitHandle : IDisposable { +#endif // FEATURE_REMOTING + public const int WaitTimeout = 0x102; + + private const int MAX_WAITHANDLES = 64; + +#pragma warning disable 414 // Field is not used from managed. + private IntPtr waitHandle; // !!! DO NOT MOVE THIS FIELD. (See defn of WAITHANDLEREF in object.h - has hardcoded access to this field.) +#pragma warning restore 414 + + [System.Security.SecurityCritical] // auto-generated + internal volatile SafeWaitHandle safeWaitHandle; + + internal bool hasThreadAffinity; + + [System.Security.SecuritySafeCritical] // auto-generated + private static IntPtr GetInvalidHandle() + { + return Win32Native.INVALID_HANDLE_VALUE; + } + protected static readonly IntPtr InvalidHandle = GetInvalidHandle(); + private const int WAIT_OBJECT_0 = 0; + private const int WAIT_ABANDONED = 0x80; + private const int WAIT_FAILED = 0x7FFFFFFF; + private const int ERROR_TOO_MANY_POSTS = 0x12A; + + internal enum OpenExistingResult + { + Success, + NameNotFound, + PathNotFound, + NameInvalid + } + + protected WaitHandle() + { + Init(); + } + + [System.Security.SecuritySafeCritical] // auto-generated + private void Init() + { + safeWaitHandle = null; + waitHandle = InvalidHandle; + hasThreadAffinity = false; + } + + + [Obsolete("Use the SafeWaitHandle property instead.")] + public virtual IntPtr Handle + { + [System.Security.SecuritySafeCritical] // auto-generated + get { return safeWaitHandle == null ? InvalidHandle : safeWaitHandle.DangerousGetHandle();} + + [System.Security.SecurityCritical] // auto-generated_required +#if !FEATURE_CORECLR + [SecurityPermissionAttribute(SecurityAction.InheritanceDemand, Flags=SecurityPermissionFlag.UnmanagedCode)] +#endif + set + { + if (value == InvalidHandle) + { + // This line leaks a handle. However, it's currently + // not perfectly clear what the right behavior is here + // anyways. This preserves Everett behavior. We should + // ideally do these things: + // *) Expose a settable SafeHandle property on WaitHandle. + // *) Expose a settable OwnsHandle property on SafeHandle. + if (safeWaitHandle != null) + { + safeWaitHandle.SetHandleAsInvalid(); + safeWaitHandle = null; + } + } + else + { + safeWaitHandle = new SafeWaitHandle(value, true); + } + waitHandle = value; + } + } + + + public SafeWaitHandle SafeWaitHandle + { + [System.Security.SecurityCritical] // auto-generated_required +#if !FEATURE_CORECLR + [SecurityPermissionAttribute(SecurityAction.InheritanceDemand, Flags=SecurityPermissionFlag.UnmanagedCode)] +#endif + [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)] + get + { + if (safeWaitHandle == null) + { + safeWaitHandle = new SafeWaitHandle(InvalidHandle, false); + } + return safeWaitHandle; + } + + [System.Security.SecurityCritical] // auto-generated_required +#if !FEATURE_CORECLR + [SecurityPermissionAttribute(SecurityAction.InheritanceDemand, Flags=SecurityPermissionFlag.UnmanagedCode)] +#endif + [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] + set + { + // Set safeWaitHandle and waitHandle in a CER so we won't take + // a thread abort between the statements and leave the wait + // handle in an invalid state. Note this routine is not thread + // safe however. + RuntimeHelpers.PrepareConstrainedRegions(); + try { } + finally + { + if (value == null) + { + safeWaitHandle = null; + waitHandle = InvalidHandle; + } + else + { + safeWaitHandle = value; + waitHandle = safeWaitHandle.DangerousGetHandle(); + } + } + } + } + + // Assembly-private version that doesn't do a security check. Reduces the + // number of link-time security checks when reading & writing to a file, + // and helps avoid a link time check while initializing security (If you + // call a Serialization method that requires security before security + // has started up, the link time check will start up security, run + // serialization code for some security attribute stuff, call into + // FileStream, which will then call Sethandle, which requires a link time + // security check.). While security has fixed that problem, we still + // don't need to do a linktime check here. + [System.Security.SecurityCritical] // auto-generated + internal void SetHandleInternal(SafeWaitHandle handle) + { + safeWaitHandle = handle; + waitHandle = handle.DangerousGetHandle(); + } + + public virtual bool WaitOne (int millisecondsTimeout, bool exitContext) + { + if (millisecondsTimeout < -1) + { + throw new ArgumentOutOfRangeException("millisecondsTimeout", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegOrNegative1")); + } + Contract.EndContractBlock(); + return WaitOne((long)millisecondsTimeout,exitContext); + } + + public virtual bool WaitOne (TimeSpan timeout, bool exitContext) + { + long tm = (long)timeout.TotalMilliseconds; + if (-1 > tm || (long) Int32.MaxValue < tm) + { + throw new ArgumentOutOfRangeException("timeout", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegOrNegative1")); + } + return WaitOne(tm,exitContext); + } + + public virtual bool WaitOne () + { + //Infinite Timeout + return WaitOne(-1,false); + } + + public virtual bool WaitOne(int millisecondsTimeout) + { + return WaitOne(millisecondsTimeout, false); + } + + public virtual bool WaitOne(TimeSpan timeout) + { + return WaitOne(timeout, false); + } + + [System.Security.SecuritySafeCritical] // auto-generated + [SuppressMessage("Microsoft.Concurrency", "CA8001", Justification = "Reviewed for thread-safety.")] + private bool WaitOne(long timeout, bool exitContext) + { + return InternalWaitOne(safeWaitHandle, timeout, hasThreadAffinity, exitContext); + } + + [System.Security.SecurityCritical] // auto-generated + internal static bool InternalWaitOne(SafeHandle waitableSafeHandle, long millisecondsTimeout, bool hasThreadAffinity, bool exitContext) + { + if (waitableSafeHandle == null) + { + throw new ObjectDisposedException(null, Environment.GetResourceString("ObjectDisposed_Generic")); + } + Contract.EndContractBlock(); + int ret = WaitOneNative(waitableSafeHandle, (uint)millisecondsTimeout, hasThreadAffinity, exitContext); + + if(AppDomainPauseManager.IsPaused) + AppDomainPauseManager.ResumeEvent.WaitOneWithoutFAS(); + + if (ret == WAIT_ABANDONED) + { + ThrowAbandonedMutexException(); + } + return (ret != WaitTimeout); + } + + [System.Security.SecurityCritical] + internal bool WaitOneWithoutFAS() + { + // version of waitone without fast application switch (FAS) support + // This is required to support the Wait which FAS needs (otherwise recursive dependency comes in) + if (safeWaitHandle == null) + { + throw new ObjectDisposedException(null, Environment.GetResourceString("ObjectDisposed_Generic")); + } + Contract.EndContractBlock(); + + long timeout = -1; + int ret = WaitOneNative(safeWaitHandle, (uint)timeout, hasThreadAffinity, false); + if (ret == WAIT_ABANDONED) + { + ThrowAbandonedMutexException(); + } + return (ret != WaitTimeout); + } + + [System.Security.SecurityCritical] // auto-generated + [MethodImplAttribute(MethodImplOptions.InternalCall)] + private static extern int WaitOneNative(SafeHandle waitableSafeHandle, uint millisecondsTimeout, bool hasThreadAffinity, bool exitContext); + + /*======================================================================== + ** Waits for signal from all the objects. + ** timeout indicates how long to wait before the method returns. + ** This method will return either when all the object have been pulsed + ** or timeout milliseonds have elapsed. + ** If exitContext is true then the synchronization domain for the context + ** (if in a synchronized context) is exited before the wait and reacquired + ========================================================================*/ + + [System.Security.SecurityCritical] // auto-generated + [MethodImplAttribute(MethodImplOptions.InternalCall)] + [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)] + private static extern int WaitMultiple(WaitHandle[] waitHandles, int millisecondsTimeout, bool exitContext, bool WaitAll); + + [System.Security.SecuritySafeCritical] // auto-generated + public static bool WaitAll(WaitHandle[] waitHandles, int millisecondsTimeout, bool exitContext) + { + if (waitHandles == null) + { + throw new ArgumentNullException("waitHandles", Environment.GetResourceString("ArgumentNull_Waithandles")); + } + if(waitHandles.Length == 0) + { + // + // Some history: in CLR 1.0 and 1.1, we threw ArgumentException in this case, which was correct. + // Somehow, in 2.0, this became ArgumentNullException. This was not fixed until Silverlight 2, + // which went back to ArgumentException. + // + // Now we're in a bit of a bind. Backward-compatibility requires us to keep throwing ArgumentException + // in CoreCLR, and ArgumentNullException in the desktop CLR. This is ugly, but so is breaking + // user code. + // +#if FEATURE_CORECLR + throw new ArgumentException(Environment.GetResourceString("Argument_EmptyWaithandleArray")); +#else + throw new ArgumentNullException("waitHandles", Environment.GetResourceString("Argument_EmptyWaithandleArray")); +#endif + } + if (waitHandles.Length > MAX_WAITHANDLES) + { + throw new NotSupportedException(Environment.GetResourceString("NotSupported_MaxWaitHandles")); + } + if (-1 > millisecondsTimeout) + { + throw new ArgumentOutOfRangeException("millisecondsTimeout", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegOrNegative1")); + } + Contract.EndContractBlock(); + WaitHandle[] internalWaitHandles = new WaitHandle[waitHandles.Length]; + for (int i = 0; i < waitHandles.Length; i ++) + { + WaitHandle waitHandle = waitHandles[i]; + + if (waitHandle == null) + throw new ArgumentNullException("waitHandles[" + i + "]", Environment.GetResourceString("ArgumentNull_ArrayElement")); + +#if FEATURE_REMOTING + if (RemotingServices.IsTransparentProxy(waitHandle)) + throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_WaitOnTransparentProxy")); +#endif + + internalWaitHandles[i] = waitHandle; + } +#if _DEBUG + // make sure we do not use waitHandles any more. + waitHandles = null; +#endif + + int ret = WaitMultiple(internalWaitHandles, millisecondsTimeout, exitContext, true /* waitall*/ ); + + if(AppDomainPauseManager.IsPaused) + AppDomainPauseManager.ResumeEvent.WaitOneWithoutFAS(); + + if ((WAIT_ABANDONED <= ret) && (WAIT_ABANDONED+internalWaitHandles.Length > ret)) + { + //In the case of WaitAll the OS will only provide the + // information that mutex was abandoned. + // It won't tell us which one. So we can't set the Index or provide access to the Mutex + ThrowAbandonedMutexException(); + } + + GC.KeepAlive(internalWaitHandles); + return (ret != WaitTimeout); + } + + public static bool WaitAll( + WaitHandle[] waitHandles, + TimeSpan timeout, + bool exitContext) + { + long tm = (long)timeout.TotalMilliseconds; + if (-1 > tm || (long) Int32.MaxValue < tm) + { + throw new ArgumentOutOfRangeException("timeout", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegOrNegative1")); + } + return WaitAll(waitHandles,(int)tm, exitContext); + } + + + /*======================================================================== + ** Shorthand for WaitAll with timeout = Timeout.Infinite and exitContext = true + ========================================================================*/ + public static bool WaitAll(WaitHandle[] waitHandles) + { + return WaitAll(waitHandles, Timeout.Infinite, true); + } + + public static bool WaitAll(WaitHandle[] waitHandles, int millisecondsTimeout) + { + return WaitAll(waitHandles, millisecondsTimeout, true); + } + + public static bool WaitAll(WaitHandle[] waitHandles, TimeSpan timeout) + { + return WaitAll(waitHandles, timeout, true); + } + + + /*======================================================================== + ** Waits for notification from any of the objects. + ** timeout indicates how long to wait before the method returns. + ** This method will return either when either one of the object have been + ** signalled or timeout milliseonds have elapsed. + ** If exitContext is true then the synchronization domain for the context + ** (if in a synchronized context) is exited before the wait and reacquired + ========================================================================*/ + + [System.Security.SecuritySafeCritical] // auto-generated + [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)] + public static int WaitAny(WaitHandle[] waitHandles, int millisecondsTimeout, bool exitContext) + { + if (waitHandles==null) + { + throw new ArgumentNullException("waitHandles", Environment.GetResourceString("ArgumentNull_Waithandles")); + } + if(waitHandles.Length == 0) + { + throw new ArgumentException(Environment.GetResourceString("Argument_EmptyWaithandleArray")); + } + if (MAX_WAITHANDLES < waitHandles.Length) + { + throw new NotSupportedException(Environment.GetResourceString("NotSupported_MaxWaitHandles")); + } + if (-1 > millisecondsTimeout) + { + throw new ArgumentOutOfRangeException("millisecondsTimeout", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegOrNegative1")); + } + Contract.EndContractBlock(); + WaitHandle[] internalWaitHandles = new WaitHandle[waitHandles.Length]; + for (int i = 0; i < waitHandles.Length; i ++) + { + WaitHandle waitHandle = waitHandles[i]; + + if (waitHandle == null) + throw new ArgumentNullException("waitHandles[" + i + "]", Environment.GetResourceString("ArgumentNull_ArrayElement")); + +#if FEATURE_REMOTING + if (RemotingServices.IsTransparentProxy(waitHandle)) + throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_WaitOnTransparentProxy")); +#endif + + internalWaitHandles[i] = waitHandle; + } +#if _DEBUG + // make sure we do not use waitHandles any more. + waitHandles = null; +#endif + int ret = WaitMultiple(internalWaitHandles, millisecondsTimeout, exitContext, false /* waitany*/ ); + + if(AppDomainPauseManager.IsPaused) + AppDomainPauseManager.ResumeEvent.WaitOneWithoutFAS(); + + if ((WAIT_ABANDONED <= ret) && (WAIT_ABANDONED+internalWaitHandles.Length > ret)) + { + int mutexIndex = ret -WAIT_ABANDONED; + if(0 <= mutexIndex && mutexIndex < internalWaitHandles.Length) + { + ThrowAbandonedMutexException(mutexIndex,internalWaitHandles[mutexIndex]); + } + else + { + ThrowAbandonedMutexException(); + } + } + + GC.KeepAlive(internalWaitHandles); + return ret; + } + + [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)] + public static int WaitAny( + WaitHandle[] waitHandles, + TimeSpan timeout, + bool exitContext) + { + long tm = (long)timeout.TotalMilliseconds; + if (-1 > tm || (long) Int32.MaxValue < tm) + { + throw new ArgumentOutOfRangeException("timeout", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegOrNegative1")); + } + return WaitAny(waitHandles,(int)tm, exitContext); + } + [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)] + public static int WaitAny(WaitHandle[] waitHandles, TimeSpan timeout) + { + return WaitAny(waitHandles, timeout, true); + } + + + /*======================================================================== + ** Shorthand for WaitAny with timeout = Timeout.Infinite and exitContext = true + ========================================================================*/ + [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)] + public static int WaitAny(WaitHandle[] waitHandles) + { + return WaitAny(waitHandles, Timeout.Infinite, true); + } + + [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)] + public static int WaitAny(WaitHandle[] waitHandles, int millisecondsTimeout) + { + return WaitAny(waitHandles, millisecondsTimeout, true); + } + + /*================================================= + == + == SignalAndWait + == + ==================================================*/ + + [System.Security.SecurityCritical] // auto-generated + [MethodImplAttribute(MethodImplOptions.InternalCall)] + private static extern int SignalAndWaitOne(SafeWaitHandle waitHandleToSignal,SafeWaitHandle waitHandleToWaitOn, int millisecondsTimeout, + bool hasThreadAffinity, bool exitContext); + + public static bool SignalAndWait( + WaitHandle toSignal, + WaitHandle toWaitOn) + { +#if PLATFORM_UNIX + throw new PlatformNotSupportedException(); +#else + return SignalAndWait(toSignal,toWaitOn,-1,false); +#endif + } + + public static bool SignalAndWait( + WaitHandle toSignal, + WaitHandle toWaitOn, + TimeSpan timeout, + bool exitContext) + { +#if PLATFORM_UNIX + throw new PlatformNotSupportedException(); +#else + long tm = (long)timeout.TotalMilliseconds; + if (-1 > tm || (long) Int32.MaxValue < tm) + { + throw new ArgumentOutOfRangeException("timeout", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegOrNegative1")); + } + return SignalAndWait(toSignal,toWaitOn,(int)tm,exitContext); +#endif + } + + [System.Security.SecuritySafeCritical] // auto-generated + [SuppressMessage("Microsoft.Concurrency", "CA8001", Justification = "Reviewed for thread-safety.")] + public static bool SignalAndWait( + WaitHandle toSignal, + WaitHandle toWaitOn, + int millisecondsTimeout, + bool exitContext) + { +#if PLATFORM_UNIX + throw new PlatformNotSupportedException(); +#else + if(null == toSignal) + { + throw new ArgumentNullException("toSignal"); + } + if(null == toWaitOn) + { + throw new ArgumentNullException("toWaitOn"); + } + if (-1 > millisecondsTimeout) + { + throw new ArgumentOutOfRangeException("millisecondsTimeout", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegOrNegative1")); + } + Contract.EndContractBlock(); + + //NOTE: This API is not supporting Pause/Resume as it's not exposed in CoreCLR (not in WP or SL) + int ret = SignalAndWaitOne(toSignal.safeWaitHandle,toWaitOn.safeWaitHandle,millisecondsTimeout, + toWaitOn.hasThreadAffinity,exitContext); + +#if !FEATURE_CORECLR + if(WAIT_FAILED != ret && toSignal.hasThreadAffinity) + { + Thread.EndCriticalRegion(); + Thread.EndThreadAffinity(); + } +#endif + + if(WAIT_ABANDONED == ret) + { + ThrowAbandonedMutexException(); + } + + if(ERROR_TOO_MANY_POSTS == ret) + { + throw new InvalidOperationException(Environment.GetResourceString("Threading.WaitHandleTooManyPosts")); + } + + //Object was signaled + if(WAIT_OBJECT_0 == ret) + { + return true; + } + + //Timeout + return false; +#endif + } + + private static void ThrowAbandonedMutexException() + { + throw new AbandonedMutexException(); + } + + private static void ThrowAbandonedMutexException(int location, WaitHandle handle) + { + throw new AbandonedMutexException(location, handle); + } + + public virtual void Close() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + [System.Security.SecuritySafeCritical] // auto-generated + protected virtual void Dispose(bool explicitDisposing) + { + if (safeWaitHandle != null) + { + safeWaitHandle.Close(); + } + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + } +} diff --git a/src/mscorlib/src/System/Threading/WaitHandleCannotBeOpenedException.cs b/src/mscorlib/src/System/Threading/WaitHandleCannotBeOpenedException.cs new file mode 100644 index 0000000000..f873057992 --- /dev/null +++ b/src/mscorlib/src/System/Threading/WaitHandleCannotBeOpenedException.cs @@ -0,0 +1,40 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +// +namespace System.Threading +{ + using System; + using System.Runtime.Serialization; + using System.Runtime.InteropServices; + + [Serializable] + [ComVisibleAttribute(false)] + +#if FEATURE_CORECLR + public class WaitHandleCannotBeOpenedException : Exception { +#else + public class WaitHandleCannotBeOpenedException : ApplicationException { +#endif // FEATURE_CORECLR + public WaitHandleCannotBeOpenedException() : base(Environment.GetResourceString("Threading.WaitHandleCannotBeOpenedException")) + { + SetErrorCode(__HResults.COR_E_WAITHANDLECANNOTBEOPENED); + } + + public WaitHandleCannotBeOpenedException(String message) : base(message) + { + SetErrorCode(__HResults.COR_E_WAITHANDLECANNOTBEOPENED); + } + + public WaitHandleCannotBeOpenedException(String message, Exception innerException) : base(message, innerException) + { + SetErrorCode(__HResults.COR_E_WAITHANDLECANNOTBEOPENED); + } + + protected WaitHandleCannotBeOpenedException(SerializationInfo info, StreamingContext context) : base (info, context) + { + } + } +} + diff --git a/src/mscorlib/src/System/Threading/WaitHandleExtensions.cs b/src/mscorlib/src/System/Threading/WaitHandleExtensions.cs new file mode 100644 index 0000000000..76c3feb649 --- /dev/null +++ b/src/mscorlib/src/System/Threading/WaitHandleExtensions.cs @@ -0,0 +1,46 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +// + +using Microsoft.Win32.SafeHandles; +using System.Security; + +namespace System.Threading +{ + public static class WaitHandleExtensions + { + /// <summary> + /// Gets the native operating system handle. + /// </summary> + /// <param name="waitHandle">The <see cref="System.Threading.WaitHandle"/> to operate on.</param> + /// <returns>A <see cref="System.Runtime.InteropServices.SafeHandle"/> representing the native operating system handle.</returns> + [SecurityCritical] + public static SafeWaitHandle GetSafeWaitHandle(this WaitHandle waitHandle) + { + if (waitHandle == null) + { + throw new ArgumentNullException("waitHandle"); + } + + return waitHandle.SafeWaitHandle; + } + + /// <summary> + /// Sets the native operating system handle + /// </summary> + /// <param name="waitHandle">The <see cref="System.Threading.WaitHandle"/> to operate on.</param> + /// <param name="value">A <see cref="System.Runtime.InteropServices.SafeHandle"/> representing the native operating system handle.</param> + [SecurityCritical] + public static void SetSafeWaitHandle(this WaitHandle waitHandle, SafeWaitHandle value) + { + if (waitHandle == null) + { + throw new ArgumentNullException("waitHandle"); + } + + waitHandle.SafeWaitHandle = value; + } + } +} |