diff --git a/src/System.Private.CoreLib/shared/System.Private.CoreLib.Shared.projitems b/src/System.Private.CoreLib/shared/System.Private.CoreLib.Shared.projitems
index 16ebd62c5ca..bfd79aa3919 100644
--- a/src/System.Private.CoreLib/shared/System.Private.CoreLib.Shared.projitems
+++ b/src/System.Private.CoreLib/shared/System.Private.CoreLib.Shared.projitems
@@ -809,6 +809,7 @@
+
diff --git a/src/System.Private.CoreLib/shared/System/IO/MemoryStream.cs b/src/System.Private.CoreLib/shared/System/IO/MemoryStream.cs
index b9f2b5e35fc..3dfc9aef445 100644
--- a/src/System.Private.CoreLib/shared/System/IO/MemoryStream.cs
+++ b/src/System.Private.CoreLib/shared/System/IO/MemoryStream.cs
@@ -414,7 +414,7 @@ public override Task ReadAsync(byte[] buffer, int offset, int count, Cancel
}
catch (OperationCanceledException oce)
{
- return Task.FromCancellation(oce);
+ return Task.FromCanceled(oce);
}
catch (Exception exception)
{
@@ -450,7 +450,7 @@ public override ValueTask ReadAsync(Memory buffer, CancellationToken
}
catch (OperationCanceledException oce)
{
- return new ValueTask(Task.FromCancellation(oce));
+ return new ValueTask(Task.FromCanceled(oce));
}
catch (Exception exception)
{
@@ -739,7 +739,7 @@ public override Task WriteAsync(byte[] buffer, int offset, int count, Cancellati
}
catch (OperationCanceledException oce)
{
- return Task.FromCancellation(oce);
+ return Task.FromCanceled(oce);
}
catch (Exception exception)
{
@@ -770,7 +770,7 @@ public override ValueTask WriteAsync(ReadOnlyMemory buffer, CancellationTo
}
catch (OperationCanceledException oce)
{
- return new ValueTask(Task.FromCancellation(oce));
+ return new ValueTask(Task.FromCanceled(oce));
}
catch (Exception exception)
{
diff --git a/src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/AsyncMethodBuilder.cs b/src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/AsyncMethodBuilder.cs
index 08802ee5466..ef5d609a800 100644
--- a/src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/AsyncMethodBuilder.cs
+++ b/src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/AsyncMethodBuilder.cs
@@ -190,7 +190,7 @@ public struct AsyncTaskMethodBuilder
#if PROJECTN
private static readonly Task s_cachedCompleted = AsyncTaskCache.CreateCacheableTask(default(VoidTaskResult));
#else
- private readonly static Task s_cachedCompleted = AsyncTaskMethodBuilder.s_defaultResultTask;
+ private static readonly Task s_cachedCompleted = AsyncTaskMethodBuilder.s_defaultResultTask;
#endif
/// The generic builder object to which this non-generic instance delegates.
diff --git a/src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/AsyncValueTaskMethodBuilder.cs b/src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/AsyncValueTaskMethodBuilder.cs
index 0e1220d1190..b39ff7604b3 100644
--- a/src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/AsyncValueTaskMethodBuilder.cs
+++ b/src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/AsyncValueTaskMethodBuilder.cs
@@ -38,11 +38,7 @@ public static AsyncValueTaskMethodBuilder Create() =>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Start(ref TStateMachine stateMachine) where TStateMachine : IAsyncStateMachine =>
// will provide the right ExecutionContext semantics
-#if netstandard
- _methodBuilder.Start(ref stateMachine);
-#else
AsyncMethodBuilderCore.Start(ref stateMachine);
-#endif
/// Associates the builder with the specified state machine.
/// The state machine instance to associate with the builder.
@@ -143,11 +139,7 @@ public static AsyncValueTaskMethodBuilder Create() =>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Start(ref TStateMachine stateMachine) where TStateMachine : IAsyncStateMachine =>
// will provide the right ExecutionContext semantics
-#if netstandard
- _methodBuilder.Start(ref stateMachine);
-#else
AsyncMethodBuilderCore.Start(ref stateMachine);
-#endif
/// Associates the builder with the specified state machine.
/// The state machine instance to associate with the builder.
diff --git a/src/System.Private.CoreLib/shared/System/Threading/EventWaitHandle.Windows.cs b/src/System.Private.CoreLib/shared/System/Threading/EventWaitHandle.Windows.cs
index 2da53b2b80b..0d41e2a88d9 100644
--- a/src/System.Private.CoreLib/shared/System/Threading/EventWaitHandle.Windows.cs
+++ b/src/System.Private.CoreLib/shared/System/Threading/EventWaitHandle.Windows.cs
@@ -89,5 +89,10 @@ public bool Set()
throw Win32Marshal.GetExceptionForLastWin32Error();
return res;
}
+
+ internal static bool Set(SafeWaitHandle waitHandle)
+ {
+ return Interop.Kernel32.SetEvent(waitHandle);
+ }
}
}
diff --git a/src/System.Private.CoreLib/shared/System/Threading/Tasks/ConcurrentExclusiveSchedulerPair.cs b/src/System.Private.CoreLib/shared/System/Threading/Tasks/ConcurrentExclusiveSchedulerPair.cs
index 9adcdd6df9b..e207a7421f7 100644
--- a/src/System.Private.CoreLib/shared/System/Threading/Tasks/ConcurrentExclusiveSchedulerPair.cs
+++ b/src/System.Private.CoreLib/shared/System/Threading/Tasks/ConcurrentExclusiveSchedulerPair.cs
@@ -147,7 +147,7 @@ public void Complete()
public Task Completion
{
// ValueLock not needed, but it's ok if it's held
- get { return EnsureCompletionStateInitialized().Task; }
+ get { return EnsureCompletionStateInitialized(); }
}
/// Gets the lazily-initialized completion state.
@@ -214,12 +214,12 @@ private void CompleteTaskAsync()
ThreadPool.QueueUserWorkItem(state =>
{
var localThis = (ConcurrentExclusiveSchedulerPair)state;
- Debug.Assert(!localThis.m_completionState.Task.IsCompleted, "Completion should only happen once.");
+ Debug.Assert(!localThis.m_completionState.IsCompleted, "Completion should only happen once.");
List exceptions = localThis.m_completionState.m_exceptions;
bool success = (exceptions != null && exceptions.Count > 0) ?
localThis.m_completionState.TrySetException(exceptions) :
- localThis.m_completionState.TrySetResult(default);
+ localThis.m_completionState.TrySetResult();
Debug.Assert(success, "Expected to complete completion task.");
localThis.m_threadProcessingMode.Dispose();
@@ -479,7 +479,7 @@ private void ProcessConcurrentTasks()
/// the Completion.
///
[SuppressMessage("Microsoft.Performance", "CA1812:AvoidUninstantiatedInternalClasses")]
- private sealed class CompletionState : TaskCompletionSource
+ private sealed class CompletionState : Task
{
/// Whether the scheduler has had completion requested.
/// This variable is not volatile, so to gurantee safe reading reads, Volatile.Read is used in TryExecuteTaskInline.
@@ -741,7 +741,7 @@ private ProcessingMode ModeForDebugger
get
{
// If our completion task is done, so are we.
- if (m_completionState != null && m_completionState.Task.IsCompleted) return ProcessingMode.Completed;
+ if (m_completionState != null && m_completionState.IsCompleted) return ProcessingMode.Completed;
// Otherwise, summarize our current state.
var mode = ProcessingMode.NotCurrentlyProcessing;
diff --git a/src/System.Private.CoreLib/shared/System/Threading/Tasks/Future.cs b/src/System.Private.CoreLib/shared/System/Threading/Tasks/Future.cs
index 5cc0b6737b5..06eb1186ae2 100644
--- a/src/System.Private.CoreLib/shared/System/Threading/Tasks/Future.cs
+++ b/src/System.Private.CoreLib/shared/System/Threading/Tasks/Future.cs
@@ -386,6 +386,8 @@ internal bool TrySetResult(TResult result)
{
Debug.Assert(m_action == null, "Task.TrySetResult(): non-null m_action");
+ 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.
@@ -411,10 +413,10 @@ internal bool TrySetResult(TResult result)
props.SetCompleted();
}
FinishContinuations();
- return true;
+ returnValue = true;
}
- return false;
+ return returnValue;
}
// Transitions the promise task into a successfully completed state with the specified result.
@@ -491,95 +493,6 @@ internal TResult GetResultCore(bool waitCompletionNotification)
return m_result;
}
- // Allow multiple exceptions to be assigned to a promise-style task.
- // This is useful when a TaskCompletionSource 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.SetException(IEnumerable).
- internal bool TrySetException(object exceptionObject)
- {
- Debug.Assert(m_action == null, "Task.TrySetException(): non-null m_action");
-
- // TCS.{Try}SetException() should have checked for this
- Debug.Assert(exceptionObject != null, "Expected non-null exceptionObject argument");
-
- // Only accept these types.
- Debug.Assert(
- (exceptionObject is Exception) || (exceptionObject is IEnumerable) ||
- (exceptionObject is ExceptionDispatchInfo) || (exceptionObject is IEnumerable),
- "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)
- {
- Debug.Assert(m_action == null, "Task.TrySetCanceled(): non-null m_action");
-#if DEBUG
- var ceAsEdi = cancellationException as ExceptionDispatchInfo;
- Debug.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
- // instead of Task, because I have a property named Task and that
- // was confusing the compiler.
- if (AtomicStateUpdate(Task.TASK_STATE_COMPLETION_RESERVED,
- Task.TASK_STATE_COMPLETION_RESERVED | Task.TASK_STATE_CANCELED |
- Task.TASK_STATE_FAULTED | Task.TASK_STATE_RAN_TO_COMPLETION))
- {
- RecordInternalCancellationRequest(tokenToRecord, cancellationException);
- CancellationCleanupLogic(); // perform cancellation cleanup actions
- returnValue = true;
- }
-
- return returnValue;
- }
-
///
/// Provides access to factory methods for creating instances.
///
diff --git a/src/System.Private.CoreLib/shared/System/Threading/Tasks/FutureFactory.cs b/src/System.Private.CoreLib/shared/System/Threading/Tasks/FutureFactory.cs
index 0d8cdcb535a..55fab9a5de3 100644
--- a/src/System.Private.CoreLib/shared/System/Threading/Tasks/FutureFactory.cs
+++ b/src/System.Private.CoreLib/shared/System/Threading/Tasks/FutureFactory.cs
@@ -800,7 +800,7 @@ internal static Task FromAsyncImpl(Func FromAsyncImpl(Func FromAsyncImpl(Func FromAsyncImpl(FuncCompletes a promise task as RanToCompletion.
+ /// If this is a Task{T}, default(T) is the implied result.
+ /// true if the task was transitioned to ran to completion; false if it was already completed.
+ internal bool TrySetResult()
+ {
+ Debug.Assert(m_action == null, "Task.TrySetResult(): non-null m_action");
+
+ if (AtomicStateUpdate(
+ TASK_STATE_COMPLETION_RESERVED | TASK_STATE_RAN_TO_COMPLETION,
+ TASK_STATE_COMPLETION_RESERVED | TASK_STATE_RAN_TO_COMPLETION | TASK_STATE_FAULTED | TASK_STATE_CANCELED))
+ {
+ ContingentProperties props = m_contingentProperties;
+ if (props != null)
+ {
+ NotifyParentIfPotentiallyAttachedTask();
+ props.SetCompleted();
+ }
+ FinishContinuations();
+ return true;
+ }
+
+ return false;
+ }
+
+ // Allow multiple exceptions to be assigned to a promise-style task.
+ // This is useful when a TaskCompletionSource 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.SetException(IEnumerable).
+ internal bool TrySetException(object exceptionObject)
+ {
+ Debug.Assert(m_action == null, "Task.TrySetException(): non-null m_action");
+
+ // TCS.{Try}SetException() should have checked for this
+ Debug.Assert(exceptionObject != null, "Expected non-null exceptionObject argument");
+
+ // Only accept these types.
+ Debug.Assert(
+ (exceptionObject is Exception) || (exceptionObject is IEnumerable) ||
+ (exceptionObject is ExceptionDispatchInfo) || (exceptionObject is IEnumerable),
+ "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)
+ {
+ Debug.Assert(m_action == null, "Task.TrySetCanceled(): non-null m_action");
+ Debug.Assert(
+ cancellationException == null ||
+ cancellationException is OperationCanceledException ||
+ (cancellationException as ExceptionDispatchInfo)?.SourceException is OperationCanceledException,
+ "Expected null or an OperationCanceledException");
+
+ 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.
+ if (AtomicStateUpdate(
+ TASK_STATE_COMPLETION_RESERVED,
+ TASK_STATE_COMPLETION_RESERVED | TASK_STATE_CANCELED | TASK_STATE_FAULTED | TASK_STATE_RAN_TO_COMPLETION))
+ {
+ RecordInternalCancellationRequest(tokenToRecord, cancellationException);
+ CancellationCleanupLogic(); // perform cancellation cleanup actions
+ returnValue = true;
+ }
+
+ return returnValue;
+ }
+
//
// Continuation passing functionality (aka ContinueWith)
@@ -5054,7 +5161,12 @@ public static Task FromResult(TResult result)
/// The faulted task.
public static Task FromException(Exception exception)
{
- return FromException(exception);
+ if (exception == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.exception);
+
+ var task = new Task();
+ bool succeeded = task.TrySetException(exception);
+ Debug.Assert(succeeded, "This should always succeed on a new task.");
+ return task;
}
/// Creates a that's completed exceptionally with the specified exception.
@@ -5092,13 +5204,26 @@ public static Task FromCanceled(CancellationToken cancellation
return new Task(true, default, TaskCreationOptions.None, cancellationToken);
}
+ /// Creates a that's completed due to cancellation with the specified exception.
+ /// The exception with which to complete the task.
+ /// The canceled task.
+ internal static Task FromCanceled(OperationCanceledException exception)
+ {
+ Debug.Assert(exception != null);
+
+ var task = new Task();
+ bool succeeded = task.TrySetCanceled(exception.CancellationToken, exception);
+ Debug.Assert(succeeded, "This should always succeed on a new task.");
+ return task;
+ }
+
/// Creates a that's completed due to cancellation with the specified exception.
/// The type of the result returned by the task.
/// The exception with which to complete the task.
/// The canceled task.
- internal static Task FromCancellation(OperationCanceledException exception)
+ internal static Task FromCanceled(OperationCanceledException exception)
{
- if (exception == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.exception);
+ Debug.Assert(exception != null);
var task = new Task();
bool succeeded = task.TrySetCanceled(exception.CancellationToken, exception);
@@ -5389,10 +5514,9 @@ public static Task Delay(int millisecondsDelay, CancellationToken cancellationTo
}
/// Task that also stores the completion closure and logic for Task.Delay implementation.
- private sealed class DelayPromise : Task
+ private sealed class DelayPromise : Task
{
internal DelayPromise(CancellationToken token)
- : base()
{
this.Token = token;
if (AsyncCausalityTracer.LoggingOn)
@@ -5423,7 +5547,7 @@ internal void Complete()
if (s_asyncDebuggingEnabled)
RemoveFromActiveTasks(this);
- setSucceeded = TrySetResult(default);
+ setSucceeded = TrySetResult();
}
// If we set the value, also clean up.
@@ -5555,10 +5679,10 @@ private static Task InternalWhenAll(Task[] tasks)
new WhenAllPromise(tasks);
}
- // A Task that gets completed when all of its constituent tasks complete.
+ // A Task 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 promise = new Task(...);
+ // Task promise = new Task(...);
// Action completionAction = delegate { };
// TaskFactory.CommonCWAllLogic(tasksCopy).AddCompletionAction(completionAction);
// return promise;
@@ -5567,7 +5691,7 @@ private static Task InternalWhenAll(Task[] tasks)
// which saves a couple of allocations and enables debugger notification specialization.
//
// Used in InternalWhenAll(Task[])
- private sealed class WhenAllPromise : Task, ITaskCompletionAction
+ private sealed class WhenAllPromise : Task, ITaskCompletionAction
{
///
/// Stores all of the constituent tasks. Tasks clear themselves out of this
@@ -5577,8 +5701,7 @@ private sealed class WhenAllPromise : Task, ITaskCompletionActio
/// The number of tasks remaining to complete.
private int m_count;
- internal WhenAllPromise(Task[] tasks) :
- base()
+ internal WhenAllPromise(Task[] tasks)
{
Debug.Assert(tasks != null, "Expected a non-null task array");
Debug.Assert(tasks.Length > 0, "Expected a non-zero length task array");
@@ -5656,7 +5779,7 @@ public void Invoke(Task completedTask)
if (s_asyncDebuggingEnabled)
RemoveFromActiveTasks(this);
- TrySetResult(default);
+ TrySetResult();
}
}
Debug.Assert(m_count >= 0, "Count should never go below 0");
diff --git a/src/System.Private.CoreLib/shared/System/Threading/Tasks/ValueTask.cs b/src/System.Private.CoreLib/shared/System/Threading/Tasks/ValueTask.cs
index 4d6a7596058..256d4d71a2c 100644
--- a/src/System.Private.CoreLib/shared/System/Threading/Tasks/ValueTask.cs
+++ b/src/System.Private.CoreLib/shared/System/Threading/Tasks/ValueTask.cs
@@ -7,10 +7,7 @@
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Threading.Tasks.Sources;
-
-#if !netstandard
using Internal.Runtime.CompilerServices;
-#endif
namespace System.Threading.Tasks
{
@@ -60,19 +57,10 @@ namespace System.Threading.Tasks
public readonly struct ValueTask : IEquatable
{
/// A task canceled using `new CancellationToken(true)`.
- private static readonly Task s_canceledTask =
-#if netstandard
- Task.Delay(Timeout.Infinite, new CancellationToken(canceled: true));
-#else
- Task.FromCanceled(new CancellationToken(canceled: true));
-#endif
+ private static readonly Task s_canceledTask = Task.FromCanceled(new CancellationToken(canceled: true));
+
/// A successfully completed task.
- internal static Task CompletedTask
-#if netstandard
- { get; } = Task.Delay(0);
-#else
- => Task.CompletedTask;
-#endif
+ internal static Task CompletedTask => Task.CompletedTask;
/// null if representing a successful synchronous completion, otherwise a or a .
internal readonly object _obj;
@@ -190,45 +178,27 @@ private Task GetTaskForValueTaskSource(IValueTaskSource t)
{
if (status == ValueTaskSourceStatus.Canceled)
{
-#if !netstandard
if (exc is OperationCanceledException oce)
{
- var task = new Task();
+ var task = new Task();
task.TrySetCanceled(oce.CancellationToken, oce);
return task;
}
-#endif
+
return s_canceledTask;
}
else
{
-#if netstandard
- var tcs = new TaskCompletionSource();
- tcs.TrySetException(exc);
- return tcs.Task;
-#else
return Task.FromException(exc);
-#endif
}
}
}
- var m = new ValueTaskSourceAsTask(t, _token);
- return
-#if netstandard
- m.Task;
-#else
- m;
-#endif
+ return new ValueTaskSourceAsTask(t, _token);
}
/// Type used to create a to represent a .
- private sealed class ValueTaskSourceAsTask :
-#if netstandard
- TaskCompletionSource
-#else
- Task
-#endif
+ private sealed class ValueTaskSourceAsTask : Task
{
private static readonly Action s_completionAction = state =>
{
@@ -247,15 +217,12 @@ private sealed class ValueTaskSourceAsTask :
try
{
source.GetResult(vtst._token);
- vtst.TrySetResult(default);
+ vtst.TrySetResult();
}
catch (Exception exc)
{
if (status == ValueTaskSourceStatus.Canceled)
{
-#if netstandard
- vtst.TrySetCanceled();
-#else
if (exc is OperationCanceledException oce)
{
vtst.TrySetCanceled(oce.CancellationToken, oce);
@@ -264,7 +231,6 @@ private sealed class ValueTaskSourceAsTask :
{
vtst.TrySetCanceled(new CancellationToken(true));
}
-#endif
}
else
{
@@ -325,12 +291,7 @@ public bool IsCompletedSuccessfully
if (obj is Task t)
{
- return
-#if netstandard
- t.Status == TaskStatus.RanToCompletion;
-#else
- t.IsCompletedSuccessfully;
-#endif
+ return t.IsCompletedSuccessfully;
}
return Unsafe.As(obj).GetStatus(_token) == ValueTaskSourceStatus.Succeeded;
@@ -398,11 +359,7 @@ internal void ThrowIfCompletedUnsuccessfully()
{
if (obj is Task t)
{
-#if netstandard
- t.GetAwaiter().GetResult();
-#else
TaskAwaiter.ValidateEnd(t);
-#endif
}
else
{
@@ -569,12 +526,7 @@ public Task AsTask()
if (obj == null)
{
- return
-#if netstandard
- Task.FromResult(_result);
-#else
- AsyncTaskMethodBuilder.GetTaskForResult(_result);
-#endif
+ return AsyncTaskMethodBuilder.GetTaskForResult(_result);
}
if (obj is Task t)
@@ -602,12 +554,7 @@ private Task GetTaskForValueTaskSource(IValueTaskSource t)
{
// Get the result of the operation and return a task for it.
// If any exception occurred, propagate it
- return
-#if netstandard
- Task.FromResult(t.GetResult(_token));
-#else
- AsyncTaskMethodBuilder.GetTaskForResult(t.GetResult(_token));
-#endif
+ return AsyncTaskMethodBuilder.GetTaskForResult(t.GetResult(_token));
// If status is Faulted or Canceled, GetResult should throw. But
// we can't guarantee every implementation will do the "right thing".
@@ -618,59 +565,33 @@ private Task GetTaskForValueTaskSource(IValueTaskSource t)
{
if (status == ValueTaskSourceStatus.Canceled)
{
-#if !netstandard
if (exc is OperationCanceledException oce)
{
var task = new Task();
task.TrySetCanceled(oce.CancellationToken, oce);
return task;
}
-#endif
Task canceledTask = s_canceledTask;
if (canceledTask == null)
{
-#if netstandard
- var tcs = new TaskCompletionSource();
- tcs.TrySetCanceled();
- canceledTask = tcs.Task;
-#else
- canceledTask = Task.FromCanceled(new CancellationToken(true));
-#endif
// Benign race condition to initialize cached task, as identity doesn't matter.
- s_canceledTask = canceledTask;
+ s_canceledTask = Task.FromCanceled(new CancellationToken(true));
}
return canceledTask;
}
else
{
-#if netstandard
- var tcs = new TaskCompletionSource();
- tcs.TrySetException(exc);
- return tcs.Task;
-#else
return Task.FromException(exc);
-#endif
}
}
}
- var m = new ValueTaskSourceAsTask(t, _token);
- return
-#if netstandard
- m.Task;
-#else
- m;
-#endif
+ return new ValueTaskSourceAsTask(t, _token);
}
/// Type used to create a to represent a .
- private sealed class ValueTaskSourceAsTask :
-#if netstandard
- TaskCompletionSource
-#else
- Task
-#endif
+ private sealed class ValueTaskSourceAsTask : Task
{
private static readonly Action s_completionAction = state =>
{
@@ -694,9 +615,6 @@ private sealed class ValueTaskSourceAsTask :
{
if (status == ValueTaskSourceStatus.Canceled)
{
-#if netstandard
- vtst.TrySetCanceled();
-#else
if (exc is OperationCanceledException oce)
{
vtst.TrySetCanceled(oce.CancellationToken, oce);
@@ -705,7 +623,6 @@ private sealed class ValueTaskSourceAsTask :
{
vtst.TrySetCanceled(new CancellationToken(true));
}
-#endif
}
else
{
@@ -766,12 +683,7 @@ public bool IsCompletedSuccessfully
if (obj is Task t)
{
- return
-#if netstandard
- t.Status == TaskStatus.RanToCompletion;
-#else
- t.IsCompletedSuccessfully;
-#endif
+ return t.IsCompletedSuccessfully;
}
return Unsafe.As>(obj).GetStatus(_token) == ValueTaskSourceStatus.Succeeded;
@@ -843,12 +755,8 @@ public TResult Result
if (obj is Task t)
{
-#if netstandard
- return t.GetAwaiter().GetResult();
-#else
TaskAwaiter.ValidateEnd(t);
return t.ResultOnSuccess;
-#endif
}
return Unsafe.As>(obj).GetResult(_token);
diff --git a/src/System.Private.CoreLib/shared/System/Threading/Timer.cs b/src/System.Private.CoreLib/shared/System/Threading/Timer.cs
new file mode 100644
index 00000000000..9e1f929fc07
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Threading/Timer.cs
@@ -0,0 +1,866 @@
+// 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 Internal.Runtime.Augments;
+using System.Diagnostics;
+using System.Diagnostics.Tracing;
+using System.Threading.Tasks;
+
+using Thread = Internal.Runtime.Augments.RuntimeThread;
+
+namespace System.Threading
+{
+ public delegate void TimerCallback(object state);
+
+ // TimerQueue maintains a list of active timers. We use a single native timer to schedule all managed timers
+ // in the process.
+ //
+ // 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, and we can live
+ // with linear traversal times when firing timers. However, we still want to minimize the number of timers
+ // we need to traverse while doing the linear walk: in cases where we have lots of long-lived timers as well as
+ // lots of short-lived timers, when the short-lived timers fire, they incur the cost of walking the long-lived ones.
+ //
+ // 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. We maintain two such lists: one for all of the
+ // timers that'll next fire within a certain threshold, and one for the rest.
+ //
+ // Note that all instance methods of this class require that the caller hold a lock on the TimerQueue instance.
+ // We partition the timers across multiple TimerQueues, each with its own lock and set of short/long lists,
+ // in order to minimize contention when lots of threads are concurrently creating and destroying timers often.
+ internal partial class TimerQueue
+ {
+ #region Shared TimerQueue instances
+
+ public static TimerQueue[] Instances { get; } = CreateTimerQueues();
+
+ private static TimerQueue[] CreateTimerQueues()
+ {
+ var queues = new TimerQueue[Environment.ProcessorCount];
+ for (int i = 0; i < queues.Length; i++)
+ {
+ queues[i] = new TimerQueue(i);
+ }
+ return queues;
+ }
+
+ #endregion
+
+ #region interface to native timer
+
+ private bool _isTimerScheduled;
+ private int _currentTimerStartTicks;
+ private uint _currentTimerDuration;
+
+ private bool EnsureTimerFiresBy(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 (_isTimerScheduled)
+ {
+ uint elapsed = (uint)(TickCount - _currentTimerStartTicks);
+ if (elapsed >= _currentTimerDuration)
+ return true; //the timer's about to fire
+
+ uint remainingDuration = _currentTimerDuration - 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 (_pauseTicks != 0)
+ {
+ Debug.Assert(!_isTimerScheduled);
+ return true;
+ }
+
+ if (SetTimer(actualDuration))
+ {
+ _isTimerScheduled = true;
+ _currentTimerStartTicks = TickCount;
+ _currentTimerDuration = actualDuration;
+ return true;
+ }
+
+ return false;
+ }
+
+ #endregion
+
+ #region Firing timers
+
+ // The two lists of timers that are part of this TimerQueue. They conform to a single guarantee:
+ // no timer in _longTimers has an absolute next firing time <= _currentAbsoluteThreshold.
+ // That way, when FireNextTimers is invoked, we always process the short list, and we then only
+ // process the long list if the current time is greater than _currentAbsoluteThreshold (or
+ // if the short list is now empty and we need to process the long list to know when to next
+ // invoke FireNextTimers).
+ private TimerQueueTimer _shortTimers;
+ private TimerQueueTimer _longTimers;
+
+ // The current threshold, an absolute time where any timers scheduled to go off at or
+ // before this time must be queued to the short list.
+ private int _currentAbsoluteThreshold = ShortTimersThresholdMilliseconds;
+
+ // Default threshold that separates which timers target _shortTimers vs _longTimers. The threshold
+ // is chosen to balance the number of timers in the small list against the frequency with which
+ // we need to scan the long list. It's thus somewhat arbitrary and could be changed based on
+ // observed workload demand. The larger the number, the more timers we'll likely need to enumerate
+ // every time the timer fires, but also the more likely it is that when it does we won't
+ // need to look at the long list because the current time will be <= _currentAbsoluteThreshold.
+ private const int ShortTimersThresholdMilliseconds = 333;
+
+ // Time when Pause was called
+ private volatile int _pauseTicks = 0;
+
+ // Fire any timers that have expired, and update the native timer to schedule the rest of them.
+ // We're in a thread pool work item here, and if there are multiple timers to be fired, we want
+ // to queue all but the first one. The first may can then be invoked synchronously or queued,
+ // a task left up to our caller, which might be firing timers from multiple queues.
+ private void FireNextTimers()
+ {
+ // We fire the first timer on this thread; any other timers that need to be fired
+ // are queued to the ThreadPool.
+ TimerQueueTimer timerToFireOnThisThread = null;
+
+ lock (this)
+ {
+ // Since we got here, that means our previous timer has fired.
+ _isTimerScheduled = false;
+ bool haveTimerToSchedule = false;
+ uint nextTimerDuration = uint.MaxValue;
+
+ int nowTicks = TickCount;
+
+ // Sweep through the "short" timers. If the current tick count is greater than
+ // the current threshold, also sweep through the "long" timers. Finally, as part
+ // of sweeping the long timers, move anything that'll fire within the next threshold
+ // to the short list. It's functionally ok if more timers end up in the short list
+ // than is truly necessary (but not the opposite).
+ TimerQueueTimer timer = _shortTimers;
+ for (int listNum = 0; listNum < 2; listNum++) // short == 0, long == 1
+ {
+ while (timer != null)
+ {
+ Debug.Assert(timer._dueTime != Timeout.UnsignedInfinite, "A timer in the list must have a valid due time.");
+
+ // Save off the next timer to examine, in case our examination of this timer results
+ // in our deleting or moving it; we'll continue after with this saved next timer.
+ TimerQueueTimer next = timer._next;
+
+ uint elapsed = (uint)(nowTicks - timer._startTicks);
+ int remaining = (int)timer._dueTime - (int)elapsed;
+ if (remaining <= 0)
+ {
+ // Timer is ready to fire.
+
+ if (timer._period != Timeout.UnsignedInfinite)
+ {
+ // This is a repeating timer; schedule it to run again.
+
+ // Discount the extra amount of time that has elapsed since the previous firing time to
+ // prevent timer ticks from drifting. If enough time has already elapsed for the timer to fire
+ // again, meaning the timer can't keep up with the short period, have it fire 1 ms from now to
+ // avoid spinning without a delay.
+ timer._startTicks = nowTicks;
+ uint elapsedForNextDueTime = elapsed - timer._dueTime;
+ timer._dueTime = (elapsedForNextDueTime < timer._period) ?
+ timer._period - elapsedForNextDueTime :
+ 1;
+
+ // Update the timer if this becomes the next timer to fire.
+ if (timer._dueTime < nextTimerDuration)
+ {
+ haveTimerToSchedule = true;
+ nextTimerDuration = timer._dueTime;
+ }
+
+ // Validate that the repeating timer is still on the right list. It's likely that
+ // it started in the long list and was moved to the short list at some point, so
+ // we now want to move it back to the long list if that's where it belongs. Note that
+ // if we're currently processing the short list and move it to the long list, we may
+ // end up revisiting it again if we also enumerate the long list, but we will have already
+ // updated the due time appropriately so that we won't fire it again (it's also possible
+ // but rare that we could be moving a timer from the long list to the short list here,
+ // if the initial due time was set to be long but the timer then had a short period).
+ bool targetShortList = (nowTicks + timer._dueTime) - _currentAbsoluteThreshold <= 0;
+ if (timer._short != targetShortList)
+ {
+ MoveTimerToCorrectList(timer, targetShortList);
+ }
+ }
+ else
+ {
+ // Not repeating; remove it from the queue
+ DeleteTimer(timer);
+ }
+
+ // If this is the first timer, we'll fire it on this thread (after processing
+ // all others). Otherwise, queue it to the ThreadPool.
+ if (timerToFireOnThisThread == null)
+ {
+ timerToFireOnThisThread = timer;
+ }
+ else
+ {
+ ThreadPool.UnsafeQueueUserWorkItemInternal(timer, preferLocal: false);
+ }
+ }
+ else
+ {
+ // This timer isn't ready to fire. Update the next time the native timer fires if necessary,
+ // and move this timer to the short list if its remaining time is now at or under the threshold.
+
+ if (remaining < nextTimerDuration)
+ {
+ haveTimerToSchedule = true;
+ nextTimerDuration = (uint)remaining;
+ }
+
+ if (!timer._short && remaining <= ShortTimersThresholdMilliseconds)
+ {
+ MoveTimerToCorrectList(timer, shortList: true);
+ }
+ }
+
+ timer = next;
+ }
+
+ // Switch to process the long list if necessary.
+ if (listNum == 0)
+ {
+ // Determine how much time remains between now and the current threshold. If time remains,
+ // we can skip processing the long list. We use > rather than >= because, although we
+ // know that if remaining == 0 no timers in the long list will need to be fired, we
+ // don't know without looking at them when we'll need to call FireNextTimers again. We
+ // could in that case just set the next firing to 1, but we may as well just iterate the
+ // long list now; otherwise, most timers created in the interim would end up in the long
+ // list and we'd likely end up paying for another invocation of FireNextTimers that could
+ // have been delayed longer (to whatever is the current minimum in the long list).
+ int remaining = _currentAbsoluteThreshold - nowTicks;
+ if (remaining > 0)
+ {
+ if (_shortTimers == null && _longTimers != null)
+ {
+ // We don't have any short timers left and we haven't examined the long list,
+ // which means we likely don't have an accurate nextTimerDuration.
+ // But we do know that nothing in the long list will be firing before or at _currentAbsoluteThreshold,
+ // so we can just set nextTimerDuration to the difference between then and now.
+ nextTimerDuration = (uint)remaining + 1;
+ haveTimerToSchedule = true;
+ }
+ break;
+ }
+
+ // Switch to processing the long list.
+ timer = _longTimers;
+
+ // Now that we're going to process the long list, update the current threshold.
+ _currentAbsoluteThreshold = nowTicks + ShortTimersThresholdMilliseconds;
+ }
+ }
+
+ // If we still have scheduled timers, update the timer to ensure it fires
+ // in time for the next one in line.
+ if (haveTimerToSchedule)
+ {
+ EnsureTimerFiresBy(nextTimerDuration);
+ }
+ }
+
+ // Fire the user timer outside of the lock!
+ timerToFireOnThisThread?.Fire();
+ }
+
+ #endregion
+
+ #region Queue implementation
+
+ public bool UpdateTimer(TimerQueueTimer timer, uint dueTime, uint period)
+ {
+ int nowTicks = TickCount;
+
+ // The timer can be put onto the short list if it's next absolute firing time
+ // is <= the current absolute threshold.
+ int absoluteDueTime = (int)(nowTicks + dueTime);
+ bool shouldBeShort = _currentAbsoluteThreshold - absoluteDueTime >= 0;
+
+ if (timer._dueTime == Timeout.UnsignedInfinite)
+ {
+ // If the timer wasn't previously scheduled, now add it to the right list.
+ timer._short = shouldBeShort;
+ LinkTimer(timer);
+ }
+ else if (timer._short != shouldBeShort)
+ {
+ // If the timer was previously scheduled, but this update should cause
+ // it to move over the list threshold in either direction, do so.
+ UnlinkTimer(timer);
+ timer._short = shouldBeShort;
+ LinkTimer(timer);
+ }
+
+ timer._dueTime = dueTime;
+ timer._period = (period == 0) ? Timeout.UnsignedInfinite : period;
+ timer._startTicks = nowTicks;
+ return EnsureTimerFiresBy(dueTime);
+ }
+
+ public void MoveTimerToCorrectList(TimerQueueTimer timer, bool shortList)
+ {
+ Debug.Assert(timer._dueTime != Timeout.UnsignedInfinite, "Expected timer to be on a list.");
+ Debug.Assert(timer._short != shortList, "Unnecessary if timer is already on the right list.");
+
+ // Unlink it from whatever list it's on, change its list association, then re-link it.
+ UnlinkTimer(timer);
+ timer._short = shortList;
+ LinkTimer(timer);
+ }
+
+ private void LinkTimer(TimerQueueTimer timer)
+ {
+ // Use timer._short to decide to which list to add.
+ ref TimerQueueTimer listHead = ref timer._short ? ref _shortTimers : ref _longTimers;
+ timer._next = listHead;
+ if (timer._next != null)
+ {
+ timer._next._prev = timer;
+ }
+ timer._prev = null;
+ listHead = timer;
+ }
+
+ private void UnlinkTimer(TimerQueueTimer timer)
+ {
+ TimerQueueTimer t = timer._next;
+ if (t != null)
+ {
+ t._prev = timer._prev;
+ }
+
+ if (_shortTimers == timer)
+ {
+ Debug.Assert(timer._short);
+ _shortTimers = t;
+ }
+ else if (_longTimers == timer)
+ {
+ Debug.Assert(!timer._short);
+ _longTimers = t;
+ }
+
+ t = timer._prev;
+ if (t != null)
+ {
+ t._next = timer._next;
+ }
+
+ // At this point the timer is no longer in a list, but its next and prev
+ // references may still point to other nodes. UnlinkTimer should thus be
+ // followed by something that overwrites those references, either with null
+ // if deleting the timer or other nodes if adding it to another list.
+ }
+
+ public void DeleteTimer(TimerQueueTimer timer)
+ {
+ if (timer._dueTime != Timeout.UnsignedInfinite)
+ {
+ UnlinkTimer(timer);
+ timer._prev = null;
+ timer._next = null;
+ timer._dueTime = Timeout.UnsignedInfinite;
+ timer._period = Timeout.UnsignedInfinite;
+ timer._startTicks = 0;
+ timer._short = false;
+ }
+ }
+
+ #endregion
+ }
+
+ // A timer in our TimerQueue.
+ internal sealed partial class TimerQueueTimer : IThreadPoolWorkItem
+ {
+ // The associated timer queue.
+ private readonly TimerQueue _associatedTimerQueue;
+
+ // All mutable fields of this class are protected by a lock on _associatedTimerQueue.
+ // The first six fields are maintained by TimerQueue.
+
+ // Links to the next and prev timers in the list.
+ internal TimerQueueTimer _next;
+ internal TimerQueueTimer _prev;
+
+ // true if on the short list; otherwise, false.
+ internal bool _short;
+
+ // The time, according to TimerQueue.TickCount, when this timer's current interval started.
+ internal int _startTicks;
+
+ // Timeout.UnsignedInfinite if we are not going to fire. Otherwise, the offset from _startTime when we will fire.
+ internal uint _dueTime;
+
+ // Timeout.UnsignedInfinite if we are a single-shot timer. Otherwise, the repeat interval.
+ internal uint _period;
+
+ // Info about the user's callback
+ private readonly TimerCallback _timerCallback;
+ private readonly object _state;
+ private readonly ExecutionContext _executionContext;
+
+ // When Timer.Dispose(WaitHandle) is used, we need to signal the wait handle only
+ // after all pending callbacks are complete. We set _canceled to prevent any callbacks that
+ // are already queued from running. We track the number of callbacks currently executing in
+ // _callbacksRunning. We set _notifyWhenNoCallbacksRunning only when _callbacksRunning
+ // reaches zero. Same applies if Timer.DisposeAsync() is used, except with a Task
+ // instead of with a provided WaitHandle.
+ private int _callbacksRunning;
+ private volatile bool _canceled;
+ private volatile object _notifyWhenNoCallbacksRunning; // may be either WaitHandle or Task
+
+
+ internal TimerQueueTimer(TimerCallback timerCallback, object state, uint dueTime, uint period, bool flowExecutionContext)
+ {
+ _timerCallback = timerCallback;
+ _state = state;
+ _dueTime = Timeout.UnsignedInfinite;
+ _period = Timeout.UnsignedInfinite;
+ if (flowExecutionContext)
+ {
+ _executionContext = ExecutionContext.Capture();
+ }
+ _associatedTimerQueue = TimerQueue.Instances[RuntimeThread.GetCurrentProcessorId() % TimerQueue.Instances.Length];
+
+ // 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 (_associatedTimerQueue)
+ {
+ if (_canceled)
+ throw new ObjectDisposedException(null, SR.ObjectDisposed_Generic);
+
+ _period = period;
+
+ if (dueTime == Timeout.UnsignedInfinite)
+ {
+ _associatedTimerQueue.DeleteTimer(this);
+ success = true;
+ }
+ else
+ {
+ if (
+#if CORECLR
+ // Don't emit this event during EventPipeController. This avoids initializing FrameworkEventSource during start-up which is expensive relative to the rest of start-up.
+ !EventPipeController.Initializing &&
+#endif
+ FrameworkEventSource.IsInitialized && FrameworkEventSource.Log.IsEnabled(EventLevel.Informational, FrameworkEventSource.Keywords.ThreadTransfer))
+ FrameworkEventSource.Log.ThreadTransferSendObj(this, 1, string.Empty, true, (int)dueTime, (int)period);
+
+ success = _associatedTimerQueue.UpdateTimer(this, dueTime, period);
+ }
+ }
+
+ return success;
+ }
+
+
+ public void Close()
+ {
+ lock (_associatedTimerQueue)
+ {
+ if (!_canceled)
+ {
+ _canceled = true;
+ _associatedTimerQueue.DeleteTimer(this);
+ }
+ }
+ }
+
+
+ public bool Close(WaitHandle toSignal)
+ {
+ bool success;
+ bool shouldSignal = false;
+
+ lock (_associatedTimerQueue)
+ {
+ if (_canceled)
+ {
+ success = false;
+ }
+ else
+ {
+ _canceled = true;
+ _notifyWhenNoCallbacksRunning = toSignal;
+ _associatedTimerQueue.DeleteTimer(this);
+ shouldSignal = _callbacksRunning == 0;
+ success = true;
+ }
+ }
+
+ if (shouldSignal)
+ SignalNoCallbacksRunning();
+
+ return success;
+ }
+
+ public ValueTask CloseAsync()
+ {
+ lock (_associatedTimerQueue)
+ {
+ object notifyWhenNoCallbacksRunning = _notifyWhenNoCallbacksRunning;
+
+ // Mark the timer as canceled if it's not already.
+ if (_canceled)
+ {
+ if (notifyWhenNoCallbacksRunning is WaitHandle)
+ {
+ // A previous call to Close(WaitHandle) stored a WaitHandle. We could try to deal with
+ // this case by using ThreadPool.RegisterWaitForSingleObject to create a Task that'll
+ // complete when the WaitHandle is set, but since arbitrary WaitHandle's can be supplied
+ // by the caller, it could be for an auto-reset event or similar where that caller's
+ // WaitOne on the WaitHandle could prevent this wrapper Task from completing. We could also
+ // change the implementation to support storing multiple objects, but that's not pay-for-play,
+ // and the existing Close(WaitHandle) already discounts this as being invalid, instead just
+ // returning false if you use it multiple times. Since first calling Timer.Dispose(WaitHandle)
+ // and then calling Timer.DisposeAsync is not something anyone is likely to or should do, we
+ // simplify by just failing in that case.
+ return new ValueTask(Task.FromException(new InvalidOperationException(SR.InvalidOperation_TimerAlreadyClosed)));
+ }
+ }
+ else
+ {
+ _canceled = true;
+ _associatedTimerQueue.DeleteTimer(this);
+ }
+
+ // We've deleted the timer, so if there are no callbacks queued or running,
+ // we're done and return an already-completed value task.
+ if (_callbacksRunning == 0)
+ {
+ return default;
+ }
+
+ Debug.Assert(
+ notifyWhenNoCallbacksRunning == null ||
+ notifyWhenNoCallbacksRunning is Task);
+
+ // There are callbacks queued or running, so we need to store a Task
+ // that'll be used to signal the caller when all callbacks complete. Do so as long as
+ // there wasn't a previous CloseAsync call that did.
+ if (notifyWhenNoCallbacksRunning == null)
+ {
+ var t = new Task((object)null, TaskCreationOptions.RunContinuationsAsynchronously);
+ _notifyWhenNoCallbacksRunning = t;
+ return new ValueTask(t);
+ }
+
+ // A previous CloseAsync call already hooked up a task. Just return it.
+ return new ValueTask((Task)notifyWhenNoCallbacksRunning);
+ }
+ }
+
+ void IThreadPoolWorkItem.Execute() => Fire(isThreadPool: true);
+
+ internal void Fire(bool isThreadPool = false)
+ {
+ bool canceled = false;
+
+ lock (_associatedTimerQueue)
+ {
+ canceled = _canceled;
+ if (!canceled)
+ _callbacksRunning++;
+ }
+
+ if (canceled)
+ return;
+
+ CallCallback(isThreadPool);
+
+ bool shouldSignal = false;
+ lock (_associatedTimerQueue)
+ {
+ _callbacksRunning--;
+ if (_canceled && _callbacksRunning == 0 && _notifyWhenNoCallbacksRunning != null)
+ shouldSignal = true;
+ }
+
+ if (shouldSignal)
+ SignalNoCallbacksRunning();
+ }
+
+ internal void SignalNoCallbacksRunning()
+ {
+ object toSignal = _notifyWhenNoCallbacksRunning;
+ Debug.Assert(toSignal is WaitHandle || toSignal is Task);
+
+ if (toSignal is WaitHandle wh)
+ {
+ EventWaitHandle.Set(wh.SafeWaitHandle);
+ }
+ else
+ {
+ ((Task)toSignal).TrySetResult(true);
+ }
+ }
+
+ internal void CallCallback(bool isThreadPool)
+ {
+ 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
+ ExecutionContext context = _executionContext;
+ if (context == null)
+ {
+ _timerCallback(_state);
+ }
+ else
+ {
+ if (isThreadPool)
+ {
+ ExecutionContext.RunFromThreadPoolDispatchLoop(Thread.CurrentThread, context, s_callCallbackInContext, this);
+ }
+ else
+ {
+ ExecutionContext.RunInternal(context, s_callCallbackInContext, this);
+ }
+ }
+ }
+
+ private static readonly ContextCallback s_callCallbackInContext = state =>
+ {
+ TimerQueueTimer t = (TimerQueueTimer)state;
+ t._timerCallback(t._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.
+ internal sealed class TimerHolder
+ {
+ internal TimerQueueTimer _timer;
+
+ public TimerHolder(TimerQueueTimer timer)
+ {
+ _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 anymore.
+ if (Environment.HasShutdownStarted)
+ return;
+
+ _timer.Close();
+ }
+
+ public void Close()
+ {
+ _timer.Close();
+ GC.SuppressFinalize(this);
+ }
+
+ public bool Close(WaitHandle notifyObject)
+ {
+ bool result = _timer.Close(notifyObject);
+ GC.SuppressFinalize(this);
+ return result;
+ }
+
+ public ValueTask CloseAsync()
+ {
+ ValueTask result = _timer.CloseAsync();
+ GC.SuppressFinalize(this);
+ return result;
+ }
+ }
+
+
+ public sealed class Timer : MarshalByRefObject, IDisposable, IAsyncDisposable
+ {
+ private const uint MAX_SUPPORTED_TIMEOUT = (uint)0xfffffffe;
+
+ private TimerHolder _timer;
+
+ public Timer(TimerCallback callback,
+ object state,
+ int dueTime,
+ int period) :
+ this(callback, state, dueTime, period, flowExecutionContext: true)
+ {
+ }
+
+ internal Timer(TimerCallback callback,
+ object state,
+ int dueTime,
+ int period,
+ bool flowExecutionContext)
+ {
+ if (dueTime < -1)
+ throw new ArgumentOutOfRangeException(nameof(dueTime), SR.ArgumentOutOfRange_NeedNonNegOrNegative1);
+ if (period < -1)
+ throw new ArgumentOutOfRangeException(nameof(period), SR.ArgumentOutOfRange_NeedNonNegOrNegative1);
+
+ TimerSetup(callback, state, (uint)dueTime, (uint)period, flowExecutionContext);
+ }
+
+ public Timer(TimerCallback callback,
+ object state,
+ TimeSpan dueTime,
+ TimeSpan period)
+ {
+ long dueTm = (long)dueTime.TotalMilliseconds;
+ if (dueTm < -1)
+ throw new ArgumentOutOfRangeException(nameof(dueTm), SR.ArgumentOutOfRange_NeedNonNegOrNegative1);
+ if (dueTm > MAX_SUPPORTED_TIMEOUT)
+ throw new ArgumentOutOfRangeException(nameof(dueTm), SR.ArgumentOutOfRange_TimeoutTooLarge);
+
+ long periodTm = (long)period.TotalMilliseconds;
+ if (periodTm < -1)
+ throw new ArgumentOutOfRangeException(nameof(periodTm), SR.ArgumentOutOfRange_NeedNonNegOrNegative1);
+ if (periodTm > MAX_SUPPORTED_TIMEOUT)
+ throw new ArgumentOutOfRangeException(nameof(periodTm), SR.ArgumentOutOfRange_PeriodTooLarge);
+
+ TimerSetup(callback, state, (uint)dueTm, (uint)periodTm);
+ }
+
+ [CLSCompliant(false)]
+ public Timer(TimerCallback callback,
+ object state,
+ uint dueTime,
+ uint period)
+ {
+ TimerSetup(callback, state, dueTime, period);
+ }
+
+ public Timer(TimerCallback callback,
+ object state,
+ long dueTime,
+ long period)
+ {
+ if (dueTime < -1)
+ throw new ArgumentOutOfRangeException(nameof(dueTime), SR.ArgumentOutOfRange_NeedNonNegOrNegative1);
+ if (period < -1)
+ throw new ArgumentOutOfRangeException(nameof(period), SR.ArgumentOutOfRange_NeedNonNegOrNegative1);
+ if (dueTime > MAX_SUPPORTED_TIMEOUT)
+ throw new ArgumentOutOfRangeException(nameof(dueTime), SR.ArgumentOutOfRange_TimeoutTooLarge);
+ if (period > MAX_SUPPORTED_TIMEOUT)
+ throw new ArgumentOutOfRangeException(nameof(period), SR.ArgumentOutOfRange_PeriodTooLarge);
+ TimerSetup(callback, state, (uint)dueTime, (uint)period);
+ }
+
+ 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).
+
+ TimerSetup(callback, this, (uint)dueTime, (uint)period);
+ }
+
+ private void TimerSetup(TimerCallback callback,
+ object state,
+ uint dueTime,
+ uint period,
+ bool flowExecutionContext = true)
+ {
+ if (callback == null)
+ throw new ArgumentNullException(nameof(TimerCallback));
+
+ _timer = new TimerHolder(new TimerQueueTimer(callback, state, dueTime, period, flowExecutionContext));
+ }
+
+ public bool Change(int dueTime, int period)
+ {
+ if (dueTime < -1)
+ throw new ArgumentOutOfRangeException(nameof(dueTime), SR.ArgumentOutOfRange_NeedNonNegOrNegative1);
+ if (period < -1)
+ throw new ArgumentOutOfRangeException(nameof(period), SR.ArgumentOutOfRange_NeedNonNegOrNegative1);
+
+ return _timer._timer.Change((uint)dueTime, (uint)period);
+ }
+
+ public bool Change(TimeSpan dueTime, TimeSpan period)
+ {
+ return Change((long)dueTime.TotalMilliseconds, (long)period.TotalMilliseconds);
+ }
+
+ [CLSCompliant(false)]
+ public bool Change(uint dueTime, uint period)
+ {
+ return _timer._timer.Change(dueTime, period);
+ }
+
+ public bool Change(long dueTime, long period)
+ {
+ if (dueTime < -1)
+ throw new ArgumentOutOfRangeException(nameof(dueTime), SR.ArgumentOutOfRange_NeedNonNegOrNegative1);
+ if (period < -1)
+ throw new ArgumentOutOfRangeException(nameof(period), SR.ArgumentOutOfRange_NeedNonNegOrNegative1);
+ if (dueTime > MAX_SUPPORTED_TIMEOUT)
+ throw new ArgumentOutOfRangeException(nameof(dueTime), SR.ArgumentOutOfRange_TimeoutTooLarge);
+ if (period > MAX_SUPPORTED_TIMEOUT)
+ throw new ArgumentOutOfRangeException(nameof(period), SR.ArgumentOutOfRange_PeriodTooLarge);
+
+ return _timer._timer.Change((uint)dueTime, (uint)period);
+ }
+
+ public bool Dispose(WaitHandle notifyObject)
+ {
+ if (notifyObject == null)
+ throw new ArgumentNullException(nameof(notifyObject));
+
+ return _timer.Close(notifyObject);
+ }
+
+ public void Dispose()
+ {
+ _timer.Close();
+ }
+
+ public ValueTask DisposeAsync()
+ {
+ return _timer.CloseAsync();
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/src/System.Private.CoreLib.csproj b/src/System.Private.CoreLib/src/System.Private.CoreLib.csproj
index 57efc1cff09..64ac952c752 100644
--- a/src/System.Private.CoreLib/src/System.Private.CoreLib.csproj
+++ b/src/System.Private.CoreLib/src/System.Private.CoreLib.csproj
@@ -292,7 +292,6 @@
-
diff --git a/src/System.Private.CoreLib/src/System/Diagnostics/Tracing/FrameworkEventSource.cs b/src/System.Private.CoreLib/src/System/Diagnostics/Tracing/FrameworkEventSource.cs
index 0298b20e67c..d9aecc66b90 100644
--- a/src/System.Private.CoreLib/src/System/Diagnostics/Tracing/FrameworkEventSource.cs
+++ b/src/System.Private.CoreLib/src/System/Diagnostics/Tracing/FrameworkEventSource.cs
@@ -19,6 +19,8 @@ public static class Keywords
public const EventKeywords ThreadTransfer = (EventKeywords)0x0010;
}
+ public static bool IsInitialized { get => true; }
+
public bool IsEnabled(EventLevel level, EventKeywords keywords)
{
return false;
@@ -31,6 +33,14 @@ public void ThreadPoolEnqueueWorkObject(object workID)
public void ThreadPoolDequeueWorkObject(object workID)
{
}
+
+ public void ThreadTransferSendObj(object id, int kind, string info, bool multiDequeues, int intInfo1, int intInfo2)
+ {
+ }
+
+ public void ThreadTransferReceiveObj(object id, int kind, string info)
+ {
+ }
}
}
diff --git a/src/System.Private.CoreLib/src/System/Threading/EventWaitHandle.Unix.cs b/src/System.Private.CoreLib/src/System/Threading/EventWaitHandle.Unix.cs
index 5f1aeb3e40b..7abcf5bf88f 100644
--- a/src/System.Private.CoreLib/src/System/Threading/EventWaitHandle.Unix.cs
+++ b/src/System.Private.CoreLib/src/System/Threading/EventWaitHandle.Unix.cs
@@ -53,6 +53,20 @@ public bool Set()
}
}
+ internal static bool Set(SafeWaitHandle waitHandle)
+ {
+ waitHandle.DangerousAddRef();
+ try
+ {
+ WaitSubsystem.SetEvent(waitHandle.DangerousGetHandle());
+ return true;
+ }
+ finally
+ {
+ waitHandle.DangerousRelease();
+ }
+ }
+
private SafeWaitHandle ValidateHandle()
{
// The field value is modifiable via the public property, save it locally
diff --git a/src/System.Private.CoreLib/src/System/Threading/Timer.Unix.cs b/src/System.Private.CoreLib/src/System/Threading/Timer.Unix.cs
index 958f13b7281..9a51dfbd057 100644
--- a/src/System.Private.CoreLib/src/System/Threading/Timer.Unix.cs
+++ b/src/System.Private.CoreLib/src/System/Threading/Timer.Unix.cs
@@ -26,11 +26,12 @@ internal partial class TimerQueue
///
private static volatile int s_nextTimerDuration;
- private void SetTimer(uint actualDuration)
+ private TimerQueue(int id)
{
- // This function is called with the TimerQueue lock acquired
- Debug.Assert(Lock.IsAcquired);
+ }
+ private bool SetTimer(uint actualDuration)
+ {
// Note: AutoResetEvent.WaitOne takes an Int32 value as a timeout.
// The TimerQueue code ensures that timer duration is not greater than max Int32 value
Debug.Assert(actualDuration <= (uint)int.MaxValue);
@@ -50,6 +51,8 @@ private void SetTimer(uint actualDuration)
{
s_timerEvent.Set();
}
+
+ return true;
}
@@ -57,7 +60,7 @@ private void SetTimer(uint actualDuration)
/// This method is executed on a dedicated a timer thread. Its purpose is
/// to handle timer request and notify the TimerQueue when a timer expires.
///
- private static void TimerThread()
+ private void TimerThread()
{
int currentTimerInterval;
@@ -92,7 +95,7 @@ private static void TimerThread()
// Check whether TimerQueue needs to process expired timers.
if (timerHasExpired)
{
- Instance.FireNextTimers();
+ FireNextTimers();
// When FireNextTimers() installs a new timer, it also sets the timer event.
// Reset the event so the timer thread is not woken up right away unnecessary.
@@ -116,31 +119,4 @@ private static int TickCount
}
}
}
-
- internal sealed partial class TimerQueueTimer
- {
- private void SignalNoCallbacksRunning()
- {
- object toSignal = _notifyWhenNoCallbacksRunning;
- Debug.Assert(toSignal is WaitHandle || toSignal is Task);
-
- if (toSignal is WaitHandle wh)
- {
- SafeWaitHandle waitHandle = wh.SafeWaitHandle;
- waitHandle.DangerousAddRef();
- try
- {
- WaitSubsystem.SetEvent(waitHandle.DangerousGetHandle());
- }
- finally
- {
- waitHandle.DangerousRelease();
- }
- }
- else
- {
- ((Task)toSignal).TrySetResult(true);
- }
- }
- }
}
diff --git a/src/System.Private.CoreLib/src/System/Threading/Timer.Windows.cs b/src/System.Private.CoreLib/src/System/Threading/Timer.Windows.cs
index 49ccf13f844..7123bc0ec83 100644
--- a/src/System.Private.CoreLib/src/System/Threading/Timer.Windows.cs
+++ b/src/System.Private.CoreLib/src/System/Threading/Timer.Windows.cs
@@ -14,22 +14,29 @@ namespace System.Threading
internal partial class TimerQueue
{
private IntPtr _nativeTimer;
+ private readonly int _id;
+
+ private TimerQueue(int id)
+ {
+ _id = id;
+ }
[NativeCallable(CallingConvention = CallingConvention.StdCall)]
private static void TimerCallback(IntPtr instance, IntPtr context, IntPtr timer)
{
+ int id = (int)context;
var wrapper = ThreadPoolCallbackWrapper.Enter();
- Instance.FireNextTimers();
+ Instances[id].FireNextTimers();
wrapper.Exit();
}
- private unsafe void SetTimer(uint actualDuration)
+ private unsafe bool SetTimer(uint actualDuration)
{
if (_nativeTimer == IntPtr.Zero)
{
IntPtr nativeCallback = AddrofIntrinsics.AddrOf(TimerCallback);
- _nativeTimer = Interop.mincore.CreateThreadpoolTimer(nativeCallback, IntPtr.Zero, IntPtr.Zero);
+ _nativeTimer = Interop.mincore.CreateThreadpoolTimer(nativeCallback, (IntPtr)_id, IntPtr.Zero);
if (_nativeTimer == IntPtr.Zero)
throw new OutOfMemoryException();
}
@@ -37,6 +44,8 @@ private unsafe void SetTimer(uint actualDuration)
// Negative time indicates the amount of time to wait relative to the current time, in 100 nanosecond units
long dueTime = -10000 * (long)actualDuration;
Interop.mincore.SetThreadpoolTimer(_nativeTimer, &dueTime, 0, 0);
+
+ return true;
}
//
@@ -62,22 +71,4 @@ private static int TickCount
}
}
}
-
- internal sealed partial class TimerQueueTimer
- {
- private void SignalNoCallbacksRunning()
- {
- object toSignal = _notifyWhenNoCallbacksRunning;
- Debug.Assert(toSignal is WaitHandle || toSignal is Task);
-
- if (toSignal is WaitHandle wh)
- {
- Interop.Kernel32.SetEvent(wh.SafeWaitHandle);
- }
- else
- {
- ((Task)toSignal).TrySetResult(true);
- }
- }
- }
}
diff --git a/src/System.Private.CoreLib/src/System/Threading/Timer.cs b/src/System.Private.CoreLib/src/System/Threading/Timer.cs
deleted file mode 100644
index 522e7176b71..00000000000
--- a/src/System.Private.CoreLib/src/System/Threading/Timer.cs
+++ /dev/null
@@ -1,707 +0,0 @@
-// 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;
-using System.Threading.Tasks;
-
-namespace System.Threading
-{
- public delegate void TimerCallback(object state);
-
- //
- // TimerQueue maintains a list of active timers in this AppDomain. We use a single native timer to schedule
- // all managed timers in the process.
- //
- // 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.
- //
- internal partial class TimerQueue
- {
- #region singleton pattern implementation
-
- // The one-and-only TimerQueue for the AppDomain.
- private 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
-
- private int _currentNativeTimerStartTicks;
- private uint _currentNativeTimerDuration = uint.MaxValue;
-
- private void EnsureAppDomainTimerFiresBy(uint requestedDuration)
- {
- //
- // The CLR 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 (_currentNativeTimerDuration != uint.MaxValue)
- {
- uint elapsed = (uint)(TickCount - _currentNativeTimerStartTicks);
- if (elapsed >= _currentNativeTimerDuration)
- return; //the timer's about to fire
-
- uint remainingDuration = _currentNativeTimerDuration - elapsed;
- if (actualDuration >= remainingDuration)
- return; //the timer will fire earlier than this request
- }
-
- SetTimer(actualDuration);
- _currentNativeTimerDuration = actualDuration;
-
- _currentNativeTimerStartTicks = TickCount;
- }
-
- #endregion
-
- #region Firing timers
-
- //
- // The list of timers
- //
- private TimerQueueTimer _timers;
- readonly internal Lock Lock = new Lock();
-
- //
- // 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;
-
- using (LockHolder.Hold(Lock))
- {
- //
- // since we got here, that means our previous timer has fired.
- //
- _currentNativeTimerDuration = uint.MaxValue;
-
- 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 = _timers;
- while (timer != null)
- {
- Debug.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;
- uint elapsedForNextDueTime = elapsed - timer.m_dueTime;
- if (elapsedForNextDueTime < timer.m_period)
- {
- // Discount the extra time that has elapsed since the previous firing
- // to prevent the timer ticks from drifting
- timer.m_dueTime = timer.m_period - elapsedForNextDueTime;
- }
- else
- {
- // Enough time has elapsed to fire the timer yet again. The timer is not able to keep up
- // with the short period, have it fire 1 ms from now to avoid spnning without delay.
- timer.m_dueTime = 1;
- }
-
- //
- // 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();
- }
-
- 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 = _timers;
- timer.m_prev = null;
- if (timer.m_next != null)
- timer.m_next.m_prev = timer;
- _timers = timer;
- }
- timer.m_dueTime = dueTime;
- timer.m_period = (period == 0) ? Timeout.UnsignedInfinite : period;
- timer.m_startTicks = TickCount;
- EnsureAppDomainTimerFiresBy(dueTime);
- return true;
- }
-
- 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 (_timers == timer)
- _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.
- //
- internal sealed partial 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
- //
- private readonly TimerCallback _timerCallback;
- private readonly object _state;
- private readonly ExecutionContext _executionContext;
-
-
- //
- // When Timer.Dispose(WaitHandle) is used, we need to signal the wait handle only
- // after all pending callbacks are complete. We set _canceled to prevent any callbacks that
- // are already queued from running. We track the number of callbacks currently executing in
- // _callbacksRunning. We set _notifyWhenNoCallbacksRunning only when _callbacksRunning
- // reaches zero. Same applies if Timer.DisposeAsync() is used, except with a Task
- // instead of with a provided WaitHandle.
- private int _callbacksRunning;
- private volatile bool _canceled;
- private volatile object _notifyWhenNoCallbacksRunning;
-
-
- internal TimerQueueTimer(TimerCallback timerCallback, object state, uint dueTime, uint period, bool flowExecutionContext)
- {
- _timerCallback = timerCallback;
- _state = state;
- m_dueTime = Timeout.UnsignedInfinite;
- m_period = Timeout.UnsignedInfinite;
- if (flowExecutionContext)
- {
- _executionContext = ExecutionContext.Capture();
- }
-
- //
- // 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;
-
- using (LockHolder.Hold(TimerQueue.Instance.Lock))
- {
- if (_canceled)
- throw new ObjectDisposedException(null, SR.ObjectDisposed_Generic);
-
- m_period = period;
-
- if (dueTime == Timeout.UnsignedInfinite)
- {
- TimerQueue.Instance.DeleteTimer(this);
- success = true;
- }
- else
- {
- success = TimerQueue.Instance.UpdateTimer(this, dueTime, period);
- }
- }
-
- return success;
- }
-
-
- public void Close()
- {
- using (LockHolder.Hold(TimerQueue.Instance.Lock))
- {
- if (!_canceled)
- {
- _canceled = true;
- TimerQueue.Instance.DeleteTimer(this);
- }
- }
- }
-
-
- public bool Close(WaitHandle toSignal)
- {
- bool success;
- bool shouldSignal = false;
-
- using (LockHolder.Hold(TimerQueue.Instance.Lock))
- {
- if (_canceled)
- {
- success = false;
- }
- else
- {
- _canceled = true;
- _notifyWhenNoCallbacksRunning = toSignal;
- TimerQueue.Instance.DeleteTimer(this);
- shouldSignal = _callbacksRunning == 0;
- success = true;
- }
- }
-
- if (shouldSignal)
- SignalNoCallbacksRunning();
-
- return success;
- }
-
- public ValueTask CloseAsync()
- {
- using (LockHolder.Hold(TimerQueue.Instance.Lock))
- {
- object notifyWhenNoCallbacksRunning = _notifyWhenNoCallbacksRunning;
-
- // Mark the timer as canceled if it's not already.
- if (_canceled)
- {
- if (notifyWhenNoCallbacksRunning is WaitHandle)
- {
- // A previous call to Close(WaitHandle) stored a WaitHandle. We could try to deal with
- // this case by using ThreadPool.RegisterWaitForSingleObject to create a Task that'll
- // complete when the WaitHandle is set, but since arbitrary WaitHandle's can be supplied
- // by the caller, it could be for an auto-reset event or similar where that caller's
- // WaitOne on the WaitHandle could prevent this wrapper Task from completing. We could also
- // change the implementation to support storing multiple objects, but that's not pay-for-play,
- // and the existing Close(WaitHandle) already discounts this as being invalid, instead just
- // returning false if you use it multiple times. Since first calling Timer.Dispose(WaitHandle)
- // and then calling Timer.DisposeAsync is not something anyone is likely to or should do, we
- // simplify by just failing in that case.
- return new ValueTask(Task.FromException(new InvalidOperationException(SR.InvalidOperation_TimerAlreadyClosed)));
- }
- }
- else
- {
- _canceled = true;
- TimerQueue.Instance.DeleteTimer(this);
- }
-
- // We've deleted the timer, so if there are no callbacks queued or running,
- // we're done and return an already-completed value task.
- if (_callbacksRunning == 0)
- {
- return default;
- }
-
- Debug.Assert(
- notifyWhenNoCallbacksRunning == null ||
- notifyWhenNoCallbacksRunning is Task);
-
- // There are callbacks queued or running, so we need to store a Task
- // that'll be used to signal the caller when all callbacks complete. Do so as long as
- // there wasn't a previous CloseAsync call that did.
- if (notifyWhenNoCallbacksRunning == null)
- {
- var t = new Task((object)null, TaskCreationOptions.RunContinuationsAsynchronously);
- _notifyWhenNoCallbacksRunning = t;
- return new ValueTask(t);
- }
-
- // A previous CloseAsync call already hooked up a task. Just return it.
- return new ValueTask((Task)notifyWhenNoCallbacksRunning);
- }
- }
-
- internal void Fire()
- {
- bool canceled = false;
-
- lock (TimerQueue.Instance)
- {
- canceled = _canceled;
- if (!canceled)
- _callbacksRunning++;
- }
-
- if (canceled)
- return;
-
- CallCallback();
-
- bool shouldSignal = false;
- using (LockHolder.Hold(TimerQueue.Instance.Lock))
- {
- _callbacksRunning--;
- if (_canceled && _callbacksRunning == 0 && _notifyWhenNoCallbacksRunning != null)
- shouldSignal = true;
- }
-
- if (shouldSignal)
- SignalNoCallbacksRunning();
- }
-
- internal void CallCallback()
- {
- ContextCallback callback = s_callCallbackInContext;
- if (callback == null)
- s_callCallbackInContext = callback = new ContextCallback(CallCallbackInContext);
-
- // call directly if EC flow is suppressed
- if (_executionContext == null)
- {
- _timerCallback(_state);
- }
- else
- {
- ExecutionContext.Run(_executionContext, callback, this);
- }
- }
-
- private static ContextCallback s_callCallbackInContext;
-
- private static void CallCallbackInContext(object state)
- {
- TimerQueueTimer t = (TimerQueueTimer)state;
- t._timerCallback(t._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.
- //
- internal sealed class TimerHolder
- {
- internal TimerQueueTimer m_timer;
-
- public TimerHolder(TimerQueueTimer timer)
- {
- m_timer = timer;
- }
-
- ~TimerHolder()
- {
- 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;
- }
-
- public ValueTask CloseAsync()
- {
- ValueTask result = m_timer.CloseAsync();
- GC.SuppressFinalize(this);
- return result;
- }
- }
-
- public sealed class Timer : MarshalByRefObject, IDisposable, IAsyncDisposable
- {
- private const uint MAX_SUPPORTED_TIMEOUT = (uint)0xfffffffe;
-
- private TimerHolder _timer;
-
- public Timer(TimerCallback callback,
- object state,
- int dueTime,
- int period) :
- this(callback, state, dueTime, period, flowExecutionContext: true)
- {
- }
-
- internal Timer(TimerCallback callback,
- object state,
- int dueTime,
- int period,
- bool flowExecutionContext)
- {
- if (dueTime < -1)
- throw new ArgumentOutOfRangeException(nameof(dueTime), SR.ArgumentOutOfRange_NeedNonNegOrNegative1);
- if (period < -1)
- throw new ArgumentOutOfRangeException(nameof(period), SR.ArgumentOutOfRange_NeedNonNegOrNegative1);
-
- TimerSetup(callback, state, (uint)dueTime, (uint)period, flowExecutionContext);
- }
-
- public Timer(TimerCallback callback,
- object state,
- TimeSpan dueTime,
- TimeSpan period)
- {
- long dueTm = (long)dueTime.TotalMilliseconds;
- if (dueTm < -1)
- throw new ArgumentOutOfRangeException(nameof(dueTm), SR.ArgumentOutOfRange_NeedNonNegOrNegative1);
- if (dueTm > MAX_SUPPORTED_TIMEOUT)
- throw new ArgumentOutOfRangeException(nameof(dueTm), SR.ArgumentOutOfRange_TimeoutTooLarge);
-
- long periodTm = (long)period.TotalMilliseconds;
- if (periodTm < -1)
- throw new ArgumentOutOfRangeException(nameof(periodTm), SR.ArgumentOutOfRange_NeedNonNegOrNegative1);
- if (periodTm > MAX_SUPPORTED_TIMEOUT)
- throw new ArgumentOutOfRangeException(nameof(periodTm), SR.ArgumentOutOfRange_PeriodTooLarge);
-
- TimerSetup(callback, state, (uint)dueTm, (uint)periodTm);
- }
-
- [CLSCompliant(false)]
- public Timer(TimerCallback callback,
- object state,
- uint dueTime,
- uint period)
- {
- TimerSetup(callback, state, dueTime, period);
- }
-
- public Timer(TimerCallback callback,
- object state,
- long dueTime,
- long period)
- {
- if (dueTime < -1)
- throw new ArgumentOutOfRangeException(nameof(dueTime), SR.ArgumentOutOfRange_NeedNonNegOrNegative1);
- if (period < -1)
- throw new ArgumentOutOfRangeException(nameof(period), SR.ArgumentOutOfRange_NeedNonNegOrNegative1);
- if (dueTime > MAX_SUPPORTED_TIMEOUT)
- throw new ArgumentOutOfRangeException(nameof(dueTime), SR.ArgumentOutOfRange_TimeoutTooLarge);
- if (period > MAX_SUPPORTED_TIMEOUT)
- throw new ArgumentOutOfRangeException(nameof(period), SR.ArgumentOutOfRange_PeriodTooLarge);
- TimerSetup(callback, state, (uint)dueTime, (uint)period);
- }
-
- 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).
-
- TimerSetup(callback, this, (uint)dueTime, (uint)period);
- }
-
- private void TimerSetup(TimerCallback callback,
- object state,
- uint dueTime,
- uint period,
- bool flowExecutionContext = true)
- {
- if (callback == null)
- throw new ArgumentNullException(nameof(TimerCallback));
-
- _timer = new TimerHolder(new TimerQueueTimer(callback, state, dueTime, period, flowExecutionContext));
- }
-
- public bool Change(int dueTime, int period)
- {
- if (dueTime < -1)
- throw new ArgumentOutOfRangeException(nameof(dueTime), SR.ArgumentOutOfRange_NeedNonNegOrNegative1);
- if (period < -1)
- throw new ArgumentOutOfRangeException(nameof(period), SR.ArgumentOutOfRange_NeedNonNegOrNegative1);
-
- return _timer.m_timer.Change((uint)dueTime, (uint)period);
- }
-
- public bool Change(TimeSpan dueTime, TimeSpan period)
- {
- return Change((long)dueTime.TotalMilliseconds, (long)period.TotalMilliseconds);
- }
-
- [CLSCompliant(false)]
- public bool Change(uint dueTime, uint period)
- {
- return _timer.m_timer.Change(dueTime, period);
- }
-
- public bool Change(long dueTime, long period)
- {
- if (dueTime < -1)
- throw new ArgumentOutOfRangeException(nameof(dueTime), SR.ArgumentOutOfRange_NeedNonNegOrNegative1);
- if (period < -1)
- throw new ArgumentOutOfRangeException(nameof(period), SR.ArgumentOutOfRange_NeedNonNegOrNegative1);
- if (dueTime > MAX_SUPPORTED_TIMEOUT)
- throw new ArgumentOutOfRangeException(nameof(dueTime), SR.ArgumentOutOfRange_TimeoutTooLarge);
- if (period > MAX_SUPPORTED_TIMEOUT)
- throw new ArgumentOutOfRangeException(nameof(period), SR.ArgumentOutOfRange_PeriodTooLarge);
-
- return _timer.m_timer.Change((uint)dueTime, (uint)period);
- }
-
- public bool Dispose(WaitHandle notifyObject)
- {
- if (notifyObject == null)
- throw new ArgumentNullException(nameof(notifyObject));
-
- return _timer.Close(notifyObject);
- }
-
- public void Dispose()
- {
- _timer.Close();
- }
-
- public ValueTask DisposeAsync()
- {
- return _timer.CloseAsync();
- }
- }
-}