diff options
Diffstat (limited to 'src/mscorlib/shared/System/Threading')
26 files changed, 1770 insertions, 0 deletions
diff --git a/src/mscorlib/shared/System/Threading/AbandonedMutexException.cs b/src/mscorlib/shared/System/Threading/AbandonedMutexException.cs new file mode 100644 index 0000000000..8056a3b330 --- /dev/null +++ b/src/mscorlib/shared/System/Threading/AbandonedMutexException.cs @@ -0,0 +1,77 @@ +// 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. +//////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Threading; +using System.Runtime.InteropServices; +using System.Runtime.Serialization; + +namespace System.Threading +{ + [Serializable] + public class AbandonedMutexException : SystemException + { + private int _mutexIndex = -1; + private Mutex _mutex = null; + + public AbandonedMutexException() + : base(SR.Threading_AbandonedMutexException) + { + HResult = __HResults.COR_E_ABANDONEDMUTEX; + } + + public AbandonedMutexException(String message) + : base(message) + { + HResult = __HResults.COR_E_ABANDONEDMUTEX; + } + + public AbandonedMutexException(String message, Exception inner) + : base(message, inner) + { + HResult = __HResults.COR_E_ABANDONEDMUTEX; + } + + public AbandonedMutexException(int location, WaitHandle handle) + : base(SR.Threading_AbandonedMutexException) + { + HResult = __HResults.COR_E_ABANDONEDMUTEX; + SetupException(location, handle); + } + + public AbandonedMutexException(String message, int location, WaitHandle handle) + : base(message) + { + HResult = __HResults.COR_E_ABANDONEDMUTEX; + SetupException(location, handle); + } + + public AbandonedMutexException(String message, Exception inner, int location, WaitHandle handle) + : base(message, inner) + { + HResult = __HResults.COR_E_ABANDONEDMUTEX; + SetupException(location, handle); + } + + protected AbandonedMutexException(SerializationInfo info, StreamingContext context) + : base(info, context) + { + } + + private void SetupException(int location, WaitHandle handle) + { + _mutexIndex = location; + if (handle != null) + _mutex = handle as Mutex; + } + + public Mutex Mutex => _mutex; + public int MutexIndex => _mutexIndex; + } +} diff --git a/src/mscorlib/shared/System/Threading/ApartmentState.cs b/src/mscorlib/shared/System/Threading/ApartmentState.cs new file mode 100644 index 0000000000..47c1677cb5 --- /dev/null +++ b/src/mscorlib/shared/System/Threading/ApartmentState.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. + +namespace System.Threading +{ + public enum ApartmentState + { + /*========================================================================= + ** Constants for thread apartment states. + =========================================================================*/ + STA = 0, + MTA = 1, + Unknown = 2 + } +} diff --git a/src/mscorlib/shared/System/Threading/AsyncLocal.cs b/src/mscorlib/shared/System/Threading/AsyncLocal.cs new file mode 100644 index 0000000000..59c8fb3c88 --- /dev/null +++ b/src/mscorlib/shared/System/Threading/AsyncLocal.cs @@ -0,0 +1,484 @@ +// 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.Collections.Generic; +using System.Diagnostics; + +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 + { + 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. + // + public AsyncLocal(Action<AsyncLocalValueChangedArgs<T>> valueChangedHandler) + { + m_valueChangedHandler = valueChangedHandler; + } + + public T Value + { + get + { + object obj = ExecutionContext.GetLocalValue(this); + return (obj == null) ? default(T) : (T)obj; + } + set + { + ExecutionContext.SetLocalValue(this, value, m_valueChangedHandler != null); + } + } + + void IAsyncLocal.OnValueChanged(object previousValueObj, object currentValueObj, bool contextChanged) + { + Debug.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 + { + 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; + } + } + + // + // Interface used to store an IAsyncLocal => object mapping in ExecutionContext. + // Implementations are specialized based on the number of elements in the immutable + // map in order to minimize memory consumption and look-up times. + // + internal interface IAsyncLocalValueMap + { + bool TryGetValue(IAsyncLocal key, out object value); + IAsyncLocalValueMap Set(IAsyncLocal key, object value); + } + + // + // Utility functions for getting/creating instances of IAsyncLocalValueMap + // + internal static class AsyncLocalValueMap + { + public static IAsyncLocalValueMap Empty { get; } = new EmptyAsyncLocalValueMap(); + + // Instance without any key/value pairs. Used as a singleton/ + private sealed class EmptyAsyncLocalValueMap : IAsyncLocalValueMap + { + public IAsyncLocalValueMap Set(IAsyncLocal key, object value) + { + // If the value isn't null, then create a new one-element map to store + // the key/value pair. If it is null, then we're still empty. + return value != null ? + new OneElementAsyncLocalValueMap(key, value) : + (IAsyncLocalValueMap)this; + } + + public bool TryGetValue(IAsyncLocal key, out object value) + { + value = null; + return false; + } + } + + // Instance with one key/value pair. + private sealed class OneElementAsyncLocalValueMap : IAsyncLocalValueMap + { + private readonly IAsyncLocal _key1; + private readonly object _value1; + + public OneElementAsyncLocalValueMap(IAsyncLocal key, object value) + { + _key1 = key; _value1 = value; + } + + public IAsyncLocalValueMap Set(IAsyncLocal key, object value) + { + if (value != null) + { + // The value is non-null. If the key matches one already contained in this map, + // then create a new one-element map with the updated value, otherwise create + // a two-element map with the additional key/value. + return ReferenceEquals(key, _key1) ? + new OneElementAsyncLocalValueMap(key, value) : + (IAsyncLocalValueMap)new TwoElementAsyncLocalValueMap(_key1, _value1, key, value); + } + else + { + // The value is null. If the key exists in this map, remove it by downgrading to an empty map. + // Otherwise, there's nothing to add or remove, so just return this map. + return ReferenceEquals(key, _key1) ? + Empty : + (IAsyncLocalValueMap)this; + } + } + + public bool TryGetValue(IAsyncLocal key, out object value) + { + if (ReferenceEquals(key, _key1)) + { + value = _value1; + return true; + } + else + { + value = null; + return false; + } + } + } + + // Instance with two key/value pairs. + private sealed class TwoElementAsyncLocalValueMap : IAsyncLocalValueMap + { + private readonly IAsyncLocal _key1, _key2; + private readonly object _value1, _value2; + + public TwoElementAsyncLocalValueMap(IAsyncLocal key1, object value1, IAsyncLocal key2, object value2) + { + _key1 = key1; _value1 = value1; + _key2 = key2; _value2 = value2; + } + + public IAsyncLocalValueMap Set(IAsyncLocal key, object value) + { + if (value != null) + { + // The value is non-null. If the key matches one already contained in this map, + // then create a new two-element map with the updated value, otherwise create + // a three-element map with the additional key/value. + return + ReferenceEquals(key, _key1) ? new TwoElementAsyncLocalValueMap(key, value, _key2, _value2) : + ReferenceEquals(key, _key2) ? new TwoElementAsyncLocalValueMap(_key1, _value1, key, value) : + (IAsyncLocalValueMap)new ThreeElementAsyncLocalValueMap(_key1, _value1, _key2, _value2, key, value); + } + else + { + // The value is null. If the key exists in this map, remove it by downgrading to a one-element map + // without the key. Otherwise, there's nothing to add or remove, so just return this map. + return + ReferenceEquals(key, _key1) ? new OneElementAsyncLocalValueMap(_key2, _value2) : + ReferenceEquals(key, _key2) ? new OneElementAsyncLocalValueMap(_key1, _value1) : + (IAsyncLocalValueMap)this; + } + } + + public bool TryGetValue(IAsyncLocal key, out object value) + { + if (ReferenceEquals(key, _key1)) + { + value = _value1; + return true; + } + else if (ReferenceEquals(key, _key2)) + { + value = _value2; + return true; + } + else + { + value = null; + return false; + } + } + } + + // Instance with three key/value pairs. + private sealed class ThreeElementAsyncLocalValueMap : IAsyncLocalValueMap + { + private readonly IAsyncLocal _key1, _key2, _key3; + private readonly object _value1, _value2, _value3; + + public ThreeElementAsyncLocalValueMap(IAsyncLocal key1, object value1, IAsyncLocal key2, object value2, IAsyncLocal key3, object value3) + { + _key1 = key1; _value1 = value1; + _key2 = key2; _value2 = value2; + _key3 = key3; _value3 = value3; + } + + public IAsyncLocalValueMap Set(IAsyncLocal key, object value) + { + if (value != null) + { + // The value is non-null. If the key matches one already contained in this map, + // then create a new three-element map with the updated value. + if (ReferenceEquals(key, _key1)) return new ThreeElementAsyncLocalValueMap(key, value, _key2, _value2, _key3, _value3); + if (ReferenceEquals(key, _key2)) return new ThreeElementAsyncLocalValueMap(_key1, _value1, key, value, _key3, _value3); + if (ReferenceEquals(key, _key3)) return new ThreeElementAsyncLocalValueMap(_key1, _value1, _key2, _value2, key, value); + + // The key doesn't exist in this map, so upgrade to a multi map that contains + // the additional key/value pair. + var multi = new MultiElementAsyncLocalValueMap(4); + multi.UnsafeStore(0, _key1, _value1); + multi.UnsafeStore(1, _key2, _value2); + multi.UnsafeStore(2, _key3, _value3); + multi.UnsafeStore(3, key, value); + return multi; + } + else + { + // The value is null. If the key exists in this map, remove it by downgrading to a two-element map + // without the key. Otherwise, there's nothing to add or remove, so just return this map. + return + ReferenceEquals(key, _key1) ? new TwoElementAsyncLocalValueMap(_key2, _value2, _key3, _value3) : + ReferenceEquals(key, _key2) ? new TwoElementAsyncLocalValueMap(_key1, _value1, _key3, _value3) : + ReferenceEquals(key, _key3) ? new TwoElementAsyncLocalValueMap(_key1, _value1, _key2, _value2) : + (IAsyncLocalValueMap)this; + } + } + + public bool TryGetValue(IAsyncLocal key, out object value) + { + if (ReferenceEquals(key, _key1)) + { + value = _value1; + return true; + } + else if (ReferenceEquals(key, _key2)) + { + value = _value2; + return true; + } + else if (ReferenceEquals(key, _key3)) + { + value = _value3; + return true; + } + else + { + value = null; + return false; + } + } + } + + // Instance with up to 16 key/value pairs. + private sealed class MultiElementAsyncLocalValueMap : IAsyncLocalValueMap + { + internal const int MaxMultiElements = 16; + private readonly KeyValuePair<IAsyncLocal, object>[] _keyValues; + + internal MultiElementAsyncLocalValueMap(int count) + { + Debug.Assert(count <= MaxMultiElements); + _keyValues = new KeyValuePair<IAsyncLocal, object>[count]; + } + + internal void UnsafeStore(int index, IAsyncLocal key, object value) + { + Debug.Assert(index < _keyValues.Length); + _keyValues[index] = new KeyValuePair<IAsyncLocal, object>(key, value); + } + + public IAsyncLocalValueMap Set(IAsyncLocal key, object value) + { + // Find the key in this map. + for (int i = 0; i < _keyValues.Length; i++) + { + if (ReferenceEquals(key, _keyValues[i].Key)) + { + // The key is in the map. If the value isn't null, then create a new map of the same + // size that has all of the same pairs, with this new key/value pair overwriting the old. + if (value != null) + { + var multi = new MultiElementAsyncLocalValueMap(_keyValues.Length); + Array.Copy(_keyValues, 0, multi._keyValues, 0, _keyValues.Length); + multi._keyValues[i] = new KeyValuePair<IAsyncLocal, object>(key, value); + return multi; + } + else if (_keyValues.Length == 4) + { + // The value is null, and we only have four elements, one of which we're removing, + // so downgrade to a three-element map, without the matching element. + return + i == 0 ? new ThreeElementAsyncLocalValueMap(_keyValues[1].Key, _keyValues[1].Value, _keyValues[2].Key, _keyValues[2].Value, _keyValues[3].Key, _keyValues[3].Value) : + i == 1 ? new ThreeElementAsyncLocalValueMap(_keyValues[0].Key, _keyValues[0].Value, _keyValues[2].Key, _keyValues[2].Value, _keyValues[3].Key, _keyValues[3].Value) : + i == 2 ? new ThreeElementAsyncLocalValueMap(_keyValues[0].Key, _keyValues[0].Value, _keyValues[1].Key, _keyValues[1].Value, _keyValues[3].Key, _keyValues[3].Value) : + (IAsyncLocalValueMap)new ThreeElementAsyncLocalValueMap(_keyValues[0].Key, _keyValues[0].Value, _keyValues[1].Key, _keyValues[1].Value, _keyValues[2].Key, _keyValues[2].Value); + } + else + { + // The value is null, and we have enough elements remaining to warrant a multi map. + // Create a new one and copy all of the elements from this one, except the one to be removed. + var multi = new MultiElementAsyncLocalValueMap(_keyValues.Length - 1); + if (i != 0) Array.Copy(_keyValues, 0, multi._keyValues, 0, i); + if (i != _keyValues.Length - 1) Array.Copy(_keyValues, i + 1, multi._keyValues, i, _keyValues.Length - i - 1); + return multi; + } + } + } + + // The key does not already exist in this map. + + // If the value is null, then we can simply return this same map, as there's nothing to add or remove. + if (value == null) + { + return this; + } + + // We need to create a new map that has the additional key/value pair. + // If with the addition we can still fit in a multi map, create one. + if (_keyValues.Length < MaxMultiElements) + { + var multi = new MultiElementAsyncLocalValueMap(_keyValues.Length + 1); + Array.Copy(_keyValues, 0, multi._keyValues, 0, _keyValues.Length); + multi._keyValues[_keyValues.Length] = new KeyValuePair<IAsyncLocal, object>(key, value); + return multi; + } + + // Otherwise, upgrade to a many map. + var many = new ManyElementAsyncLocalValueMap(MaxMultiElements + 1); + foreach (KeyValuePair<IAsyncLocal, object> pair in _keyValues) + { + many[pair.Key] = pair.Value; + } + many[key] = value; + return many; + } + + public bool TryGetValue(IAsyncLocal key, out object value) + { + foreach (KeyValuePair<IAsyncLocal, object> pair in _keyValues) + { + if (ReferenceEquals(key, pair.Key)) + { + value = pair.Value; + return true; + } + } + value = null; + return false; + } + } + + // Instance with any number of key/value pairs. + private sealed class ManyElementAsyncLocalValueMap : Dictionary<IAsyncLocal, object>, IAsyncLocalValueMap + { + public ManyElementAsyncLocalValueMap(int capacity) : base(capacity) { } + + public IAsyncLocalValueMap Set(IAsyncLocal key, object value) + { + int count = Count; + bool containsKey = ContainsKey(key); + + // If the value being set exists, create a new many map, copy all of the elements from this one, + // and then store the new key/value pair into it. This is the most common case. + if (value != null) + { + var map = new ManyElementAsyncLocalValueMap(count + (containsKey ? 0 : 1)); + foreach (KeyValuePair<IAsyncLocal, object> pair in this) + { + map[pair.Key] = pair.Value; + } + map[key] = value; + return map; + } + + // Otherwise, the value is null, which means null is being stored into an AsyncLocal.Value. + // Since there's no observable difference at the API level between storing null and the key + // not existing at all, we can downgrade to a smaller map rather than storing null. + + // If the key is contained in this map, we're going to create a new map that's one pair smaller. + if (containsKey) + { + // If the new count would be within range of a multi map instead of a many map, + // downgrade to the many map, which uses less memory and is faster to access. + // Otherwise, just create a new many map that's missing this key. + if (count == MultiElementAsyncLocalValueMap.MaxMultiElements + 1) + { + var multi = new MultiElementAsyncLocalValueMap(MultiElementAsyncLocalValueMap.MaxMultiElements); + int index = 0; + foreach (KeyValuePair<IAsyncLocal, object> pair in this) + { + if (!ReferenceEquals(key, pair.Key)) + { + multi.UnsafeStore(index++, pair.Key, pair.Value); + } + } + Debug.Assert(index == MultiElementAsyncLocalValueMap.MaxMultiElements); + return multi; + } + else + { + var map = new ManyElementAsyncLocalValueMap(count - 1); + foreach (KeyValuePair<IAsyncLocal, object> pair in this) + { + if (!ReferenceEquals(key, pair.Key)) + { + map[pair.Key] = pair.Value; + } + } + Debug.Assert(map.Count == count - 1); + return map; + } + } + + // We were storing null, but the key wasn't in the map, so there's nothing to change. + // Just return this instance. + return this; + } + } + } +} diff --git a/src/mscorlib/shared/System/Threading/AutoResetEvent.cs b/src/mscorlib/shared/System/Threading/AutoResetEvent.cs new file mode 100644 index 0000000000..8320d7ad5a --- /dev/null +++ b/src/mscorlib/shared/System/Threading/AutoResetEvent.cs @@ -0,0 +1,12 @@ +// 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 +{ + public sealed class AutoResetEvent : EventWaitHandle + { + public AutoResetEvent(bool initialState) : base(initialState, EventResetMode.AutoReset) { } + } +} + diff --git a/src/mscorlib/shared/System/Threading/DeferredDisposableLifetime.cs b/src/mscorlib/shared/System/Threading/DeferredDisposableLifetime.cs new file mode 100644 index 0000000000..89380fee60 --- /dev/null +++ b/src/mscorlib/shared/System/Threading/DeferredDisposableLifetime.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.Diagnostics; + +namespace System.Threading +{ + /// <summary> + /// Provides callbacks to objects whose lifetime is managed by <see cref="DeferredDisposableLifetime{T}"/>. + /// </summary> + internal interface IDeferredDisposable + { + /// <summary> + /// Called when the object's refcount reaches zero. + /// </summary> + /// <param name="disposed"> + /// Indicates whether the object has been disposed. + /// </param> + /// <remarks> + /// If the refount reaches zero before the object is disposed, this method will be called with + /// <paramref name="disposed"/> set to false. If the object is then disposed, this method will be + /// called again, with <paramref name="disposed"/> set to true. If the refcount reaches zero + /// after the object has already been disposed, this will be called a single time, with + /// <paramref name="disposed"/> set to true. + /// </remarks> + void OnFinalRelease(bool disposed); + } + + /// <summary> + /// Manages the lifetime of an object which implements IDisposable, but which must defer the actual + /// cleanup of state until all existing uses of the object are complete. + /// </summary> + /// <typeparam name="T">The type of object whose lifetime will be managed.</typeparam> + /// <remarks> + /// This type maintains a reference count, and tracks whether the object has been disposed. When + /// Callbacks are made to <see cref="IDeferredDisposable.OnFinalRelease(bool)"/> when the refcount + /// reaches zero. Objects that need to defer cleanup until they have been disposed *and* they have + /// no more references can do so in <see cref="IDeferredDisposable.OnFinalRelease(bool)"/> when + /// 'disposed' is true. + /// </remarks> + internal struct DeferredDisposableLifetime<T> where T : class, IDeferredDisposable + { + // + // _count is positive until Dispose is called, after which it's (-1 - refcount). + // + private int _count; + + public bool AddRef(T obj) + { + while (true) + { + int oldCount = Volatile.Read(ref _count); + + // Have we been disposed? + if (oldCount < 0) + throw new ObjectDisposedException(typeof(T).ToString()); + + int newCount = checked(oldCount + 1); + + if (Interlocked.CompareExchange(ref _count, newCount, oldCount) == oldCount) + return true; + } + } + + public void Release(T obj) + { + while (true) + { + int oldCount = Volatile.Read(ref _count); + if (oldCount > 0) + { + // We haven't been disposed. Decrement _count. + int newCount = oldCount - 1; + if (Interlocked.CompareExchange(ref _count, newCount, oldCount) == oldCount) + { + if (newCount == 0) + obj.OnFinalRelease(disposed: false); + return; + } + } + else + { + Debug.Assert(oldCount != 0 && oldCount != -1); + + // We've been disposed. Increment _count. + int newCount = oldCount + 1; + if (Interlocked.CompareExchange(ref _count, newCount, oldCount) == oldCount) + { + if (newCount == -1) + obj.OnFinalRelease(disposed: true); + return; + } + } + } + } + + public void Dispose(T obj) + { + while (true) + { + int oldCount = Volatile.Read(ref _count); + if (oldCount < 0) + return; // already disposed + + int newCount = -1 - oldCount; + if (Interlocked.CompareExchange(ref _count, newCount, oldCount) == oldCount) + { + if (newCount == -1) + obj.OnFinalRelease(disposed: true); + return; + } + } + } + } +} diff --git a/src/mscorlib/shared/System/Threading/EventResetMode.cs b/src/mscorlib/shared/System/Threading/EventResetMode.cs new file mode 100644 index 0000000000..7aac0f51eb --- /dev/null +++ b/src/mscorlib/shared/System/Threading/EventResetMode.cs @@ -0,0 +1,22 @@ +// 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 +{ + public enum EventResetMode + { + AutoReset = 0, + ManualReset = 1 + } +} diff --git a/src/mscorlib/shared/System/Threading/ExecutionContext.cs b/src/mscorlib/shared/System/Threading/ExecutionContext.cs new file mode 100644 index 0000000000..67857e9b11 --- /dev/null +++ b/src/mscorlib/shared/System/Threading/ExecutionContext.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. + +/*============================================================ +** +** +** +** Purpose: Capture execution context for a thread +** +** +===========================================================*/ + +using System.Diagnostics; +using System.Diagnostics.Contracts; +using System.Runtime.ExceptionServices; +using System.Runtime.Serialization; + +using Thread = Internal.Runtime.Augments.RuntimeThread; + +namespace System.Threading +{ + public delegate void ContextCallback(Object state); + + internal struct ExecutionContextSwitcher + { + internal ExecutionContext m_ec; + internal SynchronizationContext m_sc; + + internal void Undo(Thread currentThread) + { + Debug.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); + } + } + } + + [Serializable] + public sealed class ExecutionContext : IDisposable, ISerializable + { + internal static readonly ExecutionContext Default = new ExecutionContext(); + + private readonly IAsyncLocalValueMap m_localValues; + private readonly IAsyncLocal[] m_localChangeNotifications; + private readonly bool m_isFlowSuppressed; + + private ExecutionContext() + { + m_localValues = AsyncLocalValueMap.Empty; + m_localChangeNotifications = Array.Empty<IAsyncLocal>(); + } + + private ExecutionContext( + IAsyncLocalValueMap localValues, + IAsyncLocal[] localChangeNotifications, + bool isFlowSuppressed) + { + m_localValues = localValues; + m_localChangeNotifications = localChangeNotifications; + m_isFlowSuppressed = isFlowSuppressed; + } + + public void GetObjectData(SerializationInfo info, StreamingContext context) + { + if (info == null) + { + throw new ArgumentNullException(nameof(info)); + } + Contract.EndContractBlock(); + } + + private ExecutionContext(SerializationInfo info, StreamingContext context) + { + } + + public static ExecutionContext Capture() + { + ExecutionContext executionContext = Thread.CurrentThread.ExecutionContext; + return + executionContext == null ? Default : + executionContext.m_isFlowSuppressed ? null : + executionContext; + } + + private ExecutionContext ShallowClone(bool isFlowSuppressed) + { + Debug.Assert(isFlowSuppressed != m_isFlowSuppressed); + + if (!isFlowSuppressed && + m_localValues == Default.m_localValues && + m_localChangeNotifications == Default.m_localChangeNotifications) + { + return null; // implies the default context + } + return new ExecutionContext(m_localValues, m_localChangeNotifications, isFlowSuppressed); + } + + public static AsyncFlowControl SuppressFlow() + { + Thread currentThread = Thread.CurrentThread; + ExecutionContext executionContext = currentThread.ExecutionContext ?? Default; + if (executionContext.m_isFlowSuppressed) + { + throw new InvalidOperationException(SR.InvalidOperation_CannotSupressFlowMultipleTimes); + } + Contract.EndContractBlock(); + + executionContext = executionContext.ShallowClone(isFlowSuppressed: true); + var asyncFlowControl = new AsyncFlowControl(); + currentThread.ExecutionContext = executionContext; + asyncFlowControl.Initialize(currentThread); + return asyncFlowControl; + } + + public static void RestoreFlow() + { + Thread currentThread = Thread.CurrentThread; + ExecutionContext executionContext = currentThread.ExecutionContext; + if (executionContext == null || !executionContext.m_isFlowSuppressed) + { + throw new InvalidOperationException(SR.InvalidOperation_CannotRestoreUnsupressedFlow); + } + Contract.EndContractBlock(); + + currentThread.ExecutionContext = executionContext.ShallowClone(isFlowSuppressed: false); + } + + public static bool IsFlowSuppressed() + { + ExecutionContext executionContext = Thread.CurrentThread.ExecutionContext; + return executionContext != null && executionContext.m_isFlowSuppressed; + } + + public static void Run(ExecutionContext executionContext, ContextCallback callback, Object state) + { + if (executionContext == null) + throw new InvalidOperationException(SR.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); + } + + internal static void Restore(Thread currentThread, ExecutionContext executionContext) + { + Debug.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); + } + } + + internal static void EstablishCopyOnWriteScope(Thread currentThread, ref ExecutionContextSwitcher ecsw) + { + Debug.Assert(currentThread == Thread.CurrentThread); + + ecsw.m_ec = currentThread.ExecutionContext; + ecsw.m_sc = currentThread.SynchronizationContext; + } + + private static void OnContextChanged(ExecutionContext previous, ExecutionContext current) + { + Debug.Assert(previous != null); + Debug.Assert(current != null); + Debug.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( + SR.ExecutionContext_ExceptionInAsyncLocalNotification, + ex); + } + } + } + + 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; + } + + 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; + + IAsyncLocalValueMap newValues = current.m_localValues.Set(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) + { + Debug.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, current.m_isFlowSuppressed); + + if (needChangeNotifications) + { + local.OnValueChanged(previousValue, newValue, false); + } + } + + 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 struct AsyncFlowControl : IDisposable + { + private Thread _thread; + + internal void Initialize(Thread currentThread) + { + Debug.Assert(currentThread == Thread.CurrentThread); + _thread = currentThread; + } + + public void Undo() + { + if (_thread == null) + { + throw new InvalidOperationException(SR.InvalidOperation_CannotUseAFCMultiple); + } + if (Thread.CurrentThread != _thread) + { + throw new InvalidOperationException(SR.InvalidOperation_CannotUseAFCOtherThread); + } + + // An async flow control cannot be undone when a different execution context is applied. The desktop framework + // mutates the execution context when its state changes, and only changes the instance when an execution context + // is applied (for instance, through ExecutionContext.Run). The framework prevents a suppressed-flow execution + // context from being applied by returning null from ExecutionContext.Capture, so the only type of execution + // context that can be applied is one whose flow is not suppressed. After suppressing flow and changing an async + // local's value, the desktop framework verifies that a different execution context has not been applied by + // checking the execution context instance against the one saved from when flow was suppressed. In .NET Core, + // since the execution context instance will change after changing the async local's value, it verifies that a + // different execution context has not been applied, by instead ensuring that the current execution context's + // flow is suppressed. + if (!ExecutionContext.IsFlowSuppressed()) + { + throw new InvalidOperationException(SR.InvalidOperation_AsyncFlowCtrlCtxMismatch); + } + Contract.EndContractBlock(); + + _thread = null; + ExecutionContext.RestoreFlow(); + } + + public void Dispose() + { + Undo(); + } + + public override bool Equals(object obj) + { + return obj is AsyncFlowControl && Equals((AsyncFlowControl)obj); + } + + public bool Equals(AsyncFlowControl obj) + { + return _thread == obj._thread; + } + + public override int GetHashCode() + { + return _thread?.GetHashCode() ?? 0; + } + + public static bool operator ==(AsyncFlowControl a, AsyncFlowControl b) + { + return a.Equals(b); + } + + public static bool operator !=(AsyncFlowControl a, AsyncFlowControl b) + { + return !(a == b); + } + } +} diff --git a/src/mscorlib/shared/System/Threading/LazyThreadSafetyMode.cs b/src/mscorlib/shared/System/Threading/LazyThreadSafetyMode.cs new file mode 100644 index 0000000000..2d13f23762 --- /dev/null +++ b/src/mscorlib/shared/System/Threading/LazyThreadSafetyMode.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. + +// =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ +// +// a set of lightweight static helpers for lazy initialization. +// +// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + +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 + } +} diff --git a/src/mscorlib/shared/System/Threading/LockRecursionException.cs b/src/mscorlib/shared/System/Threading/LockRecursionException.cs new file mode 100644 index 0000000000..2f296cb14e --- /dev/null +++ b/src/mscorlib/shared/System/Threading/LockRecursionException.cs @@ -0,0 +1,29 @@ +// 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.Serialization; + +namespace System.Threading +{ + [Serializable] + public class LockRecursionException : System.Exception + { + public LockRecursionException() + { + } + + public LockRecursionException(string message) + : base(message) + { + } + + public LockRecursionException(string message, Exception innerException) + : base(message, innerException) + { + } + + protected LockRecursionException(SerializationInfo info, StreamingContext context) : base(info, context) { } + } +} diff --git a/src/mscorlib/shared/System/Threading/ManualResetEvent.cs b/src/mscorlib/shared/System/Threading/ManualResetEvent.cs new file mode 100644 index 0000000000..4b8d61f960 --- /dev/null +++ b/src/mscorlib/shared/System/Threading/ManualResetEvent.cs @@ -0,0 +1,12 @@ +// 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 +{ + public sealed class ManualResetEvent : EventWaitHandle + { + public ManualResetEvent(bool initialState) : base(initialState, EventResetMode.ManualReset) { } + } +} + diff --git a/src/mscorlib/shared/System/Threading/ParameterizedThreadStart.cs b/src/mscorlib/shared/System/Threading/ParameterizedThreadStart.cs new file mode 100644 index 0000000000..c0f29e8e80 --- /dev/null +++ b/src/mscorlib/shared/System/Threading/ParameterizedThreadStart.cs @@ -0,0 +1,18 @@ +// 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 +{ + public delegate void ParameterizedThreadStart(object obj); +} diff --git a/src/mscorlib/shared/System/Threading/SemaphoreFullException.cs b/src/mscorlib/shared/System/Threading/SemaphoreFullException.cs new file mode 100644 index 0000000000..19ac19d6e0 --- /dev/null +++ b/src/mscorlib/shared/System/Threading/SemaphoreFullException.cs @@ -0,0 +1,29 @@ +// 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.Serialization; + +namespace System.Threading +{ + [Serializable] + public class SemaphoreFullException : SystemException + { + public SemaphoreFullException() : base(SR.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/shared/System/Threading/SendOrPostCallback.cs b/src/mscorlib/shared/System/Threading/SendOrPostCallback.cs new file mode 100644 index 0000000000..6692d35ab2 --- /dev/null +++ b/src/mscorlib/shared/System/Threading/SendOrPostCallback.cs @@ -0,0 +1,8 @@ +// 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 +{ + public delegate void SendOrPostCallback(Object state); +} diff --git a/src/mscorlib/shared/System/Threading/SynchronizationLockException.cs b/src/mscorlib/shared/System/Threading/SynchronizationLockException.cs new file mode 100644 index 0000000000..120577fdcf --- /dev/null +++ b/src/mscorlib/shared/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. +** +** +=============================================================================*/ + +using System.Runtime.Serialization; + +namespace System.Threading +{ + [Serializable] + public class SynchronizationLockException : SystemException + { + public SynchronizationLockException() + : base(SR.Arg_SynchronizationLockException) + { + HResult = __HResults.COR_E_SYNCHRONIZATIONLOCK; + } + + public SynchronizationLockException(String message) + : base(message) + { + HResult = __HResults.COR_E_SYNCHRONIZATIONLOCK; + } + + public SynchronizationLockException(String message, Exception innerException) + : base(message, innerException) + { + HResult = __HResults.COR_E_SYNCHRONIZATIONLOCK; + } + + protected SynchronizationLockException(SerializationInfo info, StreamingContext context) : base(info, context) + { + } + } +} diff --git a/src/mscorlib/shared/System/Threading/Tasks/TaskCanceledException.cs b/src/mscorlib/shared/System/Threading/Tasks/TaskCanceledException.cs new file mode 100644 index 0000000000..d7690d4c7c --- /dev/null +++ b/src/mscorlib/shared/System/Threading/Tasks/TaskCanceledException.cs @@ -0,0 +1,89 @@ +// 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(SR.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(SR.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/shared/System/Threading/Tasks/TaskExtensions.cs b/src/mscorlib/shared/System/Threading/Tasks/TaskExtensions.cs new file mode 100644 index 0000000000..1098299517 --- /dev/null +++ b/src/mscorlib/shared/System/Threading/Tasks/TaskExtensions.cs @@ -0,0 +1,48 @@ +// 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.Tasks +{ + /// <summary>Provides a set of static methods for working with specific kinds of <see cref="Task"/> instances.</summary> + public static class TaskExtensions + { + /// <summary>Creates a proxy <see cref="Task"/> that represents the asynchronous operation of a <see cref="Task{Task}"/>.</summary> + /// <param name="task">The <see cref="Task{Task}"/> to unwrap.</param> + /// <returns>A <see cref="Task"/> that represents the asynchronous operation of the provided <see cref="Task{Task}"/>.</returns> + public static Task Unwrap(this Task<Task> task) + { + if (task == null) + { + throw new ArgumentNullException(nameof(task)); + } + + // If the task hasn't completed or was faulted/canceled, wrap it in an unwrap promise. Otherwise, + // it completed successfully. Return its inner task to avoid unnecessary wrapping, or if the inner + // task is null, return a canceled task to match the same semantics as CreateUnwrapPromise. + return + !task.IsRanToCompletion ? Task.CreateUnwrapPromise<VoidTaskResult>(task, lookForOce: false) : + task.Result ?? + Task.FromCanceled(new CancellationToken(true)); + } + + /// <summary>Creates a proxy <see cref="Task{TResult}"/> that represents the asynchronous operation of a <see cref="Task{Task{TResult}}"/>.</summary> + /// <param name="task">The <see cref="Task{Task{TResult}}"/> to unwrap.</param> + /// <returns>A <see cref="Task{TResult}"/> that represents the asynchronous operation of the provided <see cref="Task{Task{TResult}}"/>.</returns> + public static Task<TResult> Unwrap<TResult>(this Task<Task<TResult>> task) + { + if (task == null) + { + throw new ArgumentNullException(nameof(task)); + } + + // If the task hasn't completed or was faulted/canceled, wrap it in an unwrap promise. Otherwise, + // it completed successfully. Return its inner task to avoid unnecessary wrapping, or if the inner + // task is null, return a canceled task to match the same semantics as CreateUnwrapPromise. + return + !task.IsRanToCompletion ? Task.CreateUnwrapPromise<TResult>(task, lookForOce: false) : + task.Result ?? + Task.FromCanceled<TResult>(new CancellationToken(true)); + } + } +} diff --git a/src/mscorlib/shared/System/Threading/Tasks/TaskSchedulerException.cs b/src/mscorlib/shared/System/Threading/Tasks/TaskSchedulerException.cs new file mode 100644 index 0000000000..148b6300ef --- /dev/null +++ b/src/mscorlib/shared/System/Threading/Tasks/TaskSchedulerException.cs @@ -0,0 +1,77 @@ +// 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(SR.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(SR.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/shared/System/Threading/ThreadAbortException.cs b/src/mscorlib/shared/System/Threading/ThreadAbortException.cs new file mode 100644 index 0000000000..e693e7192f --- /dev/null +++ b/src/mscorlib/shared/System/Threading/ThreadAbortException.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: 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. +** +** +=============================================================================*/ + +using System.Runtime.Serialization; + +namespace System.Threading +{ + [Serializable] + public sealed class ThreadAbortException : SystemException + { + private ThreadAbortException() + { + HResult = __HResults.COR_E_THREADABORTED; + } + + internal ThreadAbortException(SerializationInfo info, StreamingContext context) + : base(info, context) + { + } + + public object ExceptionState => null; + } +} diff --git a/src/mscorlib/shared/System/Threading/ThreadPriority.cs b/src/mscorlib/shared/System/Threading/ThreadPriority.cs new file mode 100644 index 0000000000..3b34bd5eac --- /dev/null +++ b/src/mscorlib/shared/System/Threading/ThreadPriority.cs @@ -0,0 +1,18 @@ +// 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 +{ + public enum ThreadPriority + { + /*========================================================================= + ** Constants for thread priorities. + =========================================================================*/ + Lowest = 0, + BelowNormal = 1, + Normal = 2, + AboveNormal = 3, + Highest = 4 + } +} diff --git a/src/mscorlib/shared/System/Threading/ThreadStart.cs b/src/mscorlib/shared/System/Threading/ThreadStart.cs new file mode 100644 index 0000000000..5532539fc7 --- /dev/null +++ b/src/mscorlib/shared/System/Threading/ThreadStart.cs @@ -0,0 +1,18 @@ +// 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 +{ + public delegate void ThreadStart(); +} diff --git a/src/mscorlib/shared/System/Threading/ThreadStartException.cs b/src/mscorlib/shared/System/Threading/ThreadStartException.cs new file mode 100644 index 0000000000..2ff77bc5fd --- /dev/null +++ b/src/mscorlib/shared/System/Threading/ThreadStartException.cs @@ -0,0 +1,29 @@ +// 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.Runtime.Serialization; + +namespace System.Threading +{ + [Serializable] + public sealed class ThreadStartException : SystemException + { + internal ThreadStartException() + : base(SR.Arg_ThreadStartException) + { + HResult = __HResults.COR_E_THREADSTART; + } + + internal ThreadStartException(Exception reason) + : base(SR.Arg_ThreadStartException, reason) + { + HResult = __HResults.COR_E_THREADSTART; + } + + internal ThreadStartException(SerializationInfo info, StreamingContext context) + : base(info, context) + { + } + } +} diff --git a/src/mscorlib/shared/System/Threading/ThreadState.cs b/src/mscorlib/shared/System/Threading/ThreadState.cs new file mode 100644 index 0000000000..4bf3b5184d --- /dev/null +++ b/src/mscorlib/shared/System/Threading/ThreadState.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. + +namespace System.Threading +{ + [Flags] + 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/shared/System/Threading/ThreadStateException.cs b/src/mscorlib/shared/System/Threading/ThreadStateException.cs new file mode 100644 index 0000000000..33bc8baee6 --- /dev/null +++ b/src/mscorlib/shared/System/Threading/ThreadStateException.cs @@ -0,0 +1,45 @@ +// 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. +** +** +=============================================================================*/ + +using System.Runtime.Serialization; + +namespace System.Threading +{ + [Serializable] + public class ThreadStateException : SystemException + { + public ThreadStateException() + : base(SR.Arg_ThreadStateException) + { + HResult = __HResults.COR_E_THREADSTATE; + } + + public ThreadStateException(String message) + : base(message) + { + HResult = __HResults.COR_E_THREADSTATE; + } + + public ThreadStateException(String message, Exception innerException) + : base(message, innerException) + { + HResult = __HResults.COR_E_THREADSTATE; + } + + protected ThreadStateException(SerializationInfo info, StreamingContext context) + : base(info, context) + { + } + } +} diff --git a/src/mscorlib/shared/System/Threading/Timeout.cs b/src/mscorlib/shared/System/Threading/Timeout.cs new file mode 100644 index 0000000000..df1ea5f2bc --- /dev/null +++ b/src/mscorlib/shared/System/Threading/Timeout.cs @@ -0,0 +1,20 @@ +// 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.Threading; +using System; + +namespace System.Threading +{ + // A constant used by methods that take a timeout (Object.Wait, Thread.Sleep + // etc) to indicate that no timeout should occur. + // + public static class Timeout + { + 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/shared/System/Threading/TimeoutHelper.cs b/src/mscorlib/shared/System/Threading/TimeoutHelper.cs new file mode 100644 index 0000000000..c66c9add92 --- /dev/null +++ b/src/mscorlib/shared/System/Threading/TimeoutHelper.cs @@ -0,0 +1,54 @@ +// 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; + +namespace System.Threading +{ + /// <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 + Debug.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/shared/System/Threading/WaitHandleCannotBeOpenedException.cs b/src/mscorlib/shared/System/Threading/WaitHandleCannotBeOpenedException.cs new file mode 100644 index 0000000000..e44946a669 --- /dev/null +++ b/src/mscorlib/shared/System/Threading/WaitHandleCannotBeOpenedException.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. + +using System.Runtime.Serialization; + +namespace System.Threading +{ + [Serializable] + public class WaitHandleCannotBeOpenedException : ApplicationException + { + public WaitHandleCannotBeOpenedException() : base(SR.Threading_WaitHandleCannotBeOpenedException) + { + HResult = __HResults.COR_E_WAITHANDLECANNOTBEOPENED; + } + + public WaitHandleCannotBeOpenedException(String message) : base(message) + { + HResult = __HResults.COR_E_WAITHANDLECANNOTBEOPENED; + } + + public WaitHandleCannotBeOpenedException(String message, Exception innerException) : base(message, innerException) + { + HResult = __HResults.COR_E_WAITHANDLECANNOTBEOPENED; + } + + protected WaitHandleCannotBeOpenedException(SerializationInfo info, StreamingContext context) : base(info, context) + { + } + } +} |