|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
//
//
//
// A task that produces a value.
//
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
namespace System.Threading.Tasks
{
/// <summary>
/// Represents an asynchronous operation that produces a result at some time in the future.
/// </summary>
/// <typeparam name="TResult">
/// The type of the result produced by this <see cref="Task{TResult}"/>.
/// </typeparam>
/// <remarks>
/// <para>
/// <see cref="Task{TResult}"/> instances may be created in a variety of ways. The most common approach is by
/// using the task's <see cref="Factory"/> property to retrieve a <see
/// cref="TaskFactory{TResult}"/> instance that can be used to create tasks for several
/// purposes. For example, to create a <see cref="Task{TResult}"/> that runs a function, the factory's StartNew
/// method may be used:
/// <code>
/// // C#
/// var t = Task<int>.Factory.StartNew(() => GenerateResult());
/// - or -
/// var t = Task.Factory.StartNew(() => GenerateResult());
///
/// ' Visual Basic
/// Dim t = Task<int>.Factory.StartNew(Function() GenerateResult())
/// - or -
/// Dim t = Task.Factory.StartNew(Function() GenerateResult())
/// </code>
/// </para>
/// <para>
/// The <see cref="Task{TResult}"/> class also provides constructors that initialize the task but that do not
/// schedule it for execution. For performance reasons, the StartNew method should be the
/// preferred mechanism for creating and scheduling computational tasks, but for scenarios where creation
/// and scheduling must be separated, the constructors may be used, and the task's
/// <see cref="Task.Start()">Start</see>
/// method may then be used to schedule the task for execution at a later time.
/// </para>
/// <para>
/// All members of <see cref="Task{TResult}"/>, except for
/// <see cref="Task.Dispose()">Dispose</see>, are thread-safe
/// and may be used from multiple threads concurrently.
/// </para>
/// </remarks>
[DebuggerTypeProxy(typeof(SystemThreadingTasks_FutureDebugView<>))]
[DebuggerDisplay("Id = {Id}, Status = {Status}, Method = {DebuggerDisplayMethodDescription}, Result = {DebuggerDisplayResultDescription}")]
public class Task<TResult> : Task
{
/// <summary>A cached task for default(TResult).</summary>
internal static readonly Task<TResult> s_defaultResultTask = TaskCache.CreateCacheableTask<TResult>(default);
private static TaskFactory<TResult>? s_Factory;
// The value itself, if set.
internal TResult? m_result;
// Construct a promise-style task without any options.
internal Task()
{
}
// Construct a promise-style task with state and options.
internal Task(object? state, TaskCreationOptions options) :
base(state, options, promiseStyle: true)
{
}
// Construct a pre-completed Task<TResult>
internal Task(TResult result) :
base(false, TaskCreationOptions.None, default)
{
m_result = result;
}
internal Task(bool canceled, TResult? result, TaskCreationOptions creationOptions, CancellationToken ct)
: base(canceled, creationOptions, ct)
{
if (!canceled)
{
m_result = result;
}
}
/// <summary>
/// Initializes a new <see cref="Task{TResult}"/> with the specified function.
/// </summary>
/// <param name="function">
/// The delegate that represents the code to execute in the task. When the function has completed,
/// the task's <see cref="Result"/> property will be set to return the result value of the function.
/// </param>
/// <exception cref="ArgumentException">
/// The <paramref name="function"/> argument is null.
/// </exception>
public Task(Func<TResult> function)
: this(function, null, default,
TaskCreationOptions.None, InternalTaskOptions.None, null)
{
}
/// <summary>
/// Initializes a new <see cref="Task{TResult}"/> with the specified function.
/// </summary>
/// <param name="function">
/// The delegate that represents the code to execute in the task. When the function has completed,
/// the task's <see cref="Result"/> property will be set to return the result value of the function.
/// </param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to be assigned to this task.</param>
/// <exception cref="ArgumentException">
/// The <paramref name="function"/> argument is null.
/// </exception>
/// <exception cref="ObjectDisposedException">The provided <see cref="CancellationToken">CancellationToken</see>
/// has already been disposed.
/// </exception>
public Task(Func<TResult> function, CancellationToken cancellationToken)
: this(function, null, cancellationToken,
TaskCreationOptions.None, InternalTaskOptions.None, null)
{
}
/// <summary>
/// Initializes a new <see cref="Task{TResult}"/> with the specified function and creation options.
/// </summary>
/// <param name="function">
/// The delegate that represents the code to execute in the task. When the function has completed,
/// the task's <see cref="Result"/> property will be set to return the result value of the function.
/// </param>
/// <param name="creationOptions">
/// The <see cref="TaskCreationOptions">TaskCreationOptions</see> used to
/// customize the task's behavior.
/// </param>
/// <exception cref="ArgumentException">
/// The <paramref name="function"/> argument is null.
/// </exception>
/// <exception cref="ArgumentOutOfRangeException">
/// The <paramref name="creationOptions"/> argument specifies an invalid value for <see
/// cref="TaskCreationOptions"/>.
/// </exception>
public Task(Func<TResult> function, TaskCreationOptions creationOptions)
: this(function, InternalCurrentIfAttached(creationOptions), default, creationOptions, InternalTaskOptions.None, null)
{
}
/// <summary>
/// Initializes a new <see cref="Task{TResult}"/> with the specified function and creation options.
/// </summary>
/// <param name="function">
/// The delegate that represents the code to execute in the task. When the function has completed,
/// the task's <see cref="Result"/> property will be set to return the result value of the function.
/// </param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that will be assigned to the new task.</param>
/// <param name="creationOptions">
/// The <see cref="TaskCreationOptions">TaskCreationOptions</see> used to
/// customize the task's behavior.
/// </param>
/// <exception cref="ArgumentException">
/// The <paramref name="function"/> argument is null.
/// </exception>
/// <exception cref="ArgumentOutOfRangeException">
/// The <paramref name="creationOptions"/> argument specifies an invalid value for <see
/// cref="TaskCreationOptions"/>.
/// </exception>
/// <exception cref="ObjectDisposedException">The provided <see cref="CancellationToken">CancellationToken</see>
/// has already been disposed.
/// </exception>
public Task(Func<TResult> function, CancellationToken cancellationToken, TaskCreationOptions creationOptions)
: this(function, InternalCurrentIfAttached(creationOptions), cancellationToken, creationOptions, InternalTaskOptions.None, null)
{
}
/// <summary>
/// Initializes a new <see cref="Task{TResult}"/> with the specified function and state.
/// </summary>
/// <param name="function">
/// The delegate that represents the code to execute in the task. When the function has completed,
/// the task's <see cref="Result"/> property will be set to return the result value of the function.
/// </param>
/// <param name="state">An object representing data to be used by the action.</param>
/// <exception cref="ArgumentException">
/// The <paramref name="function"/> argument is null.
/// </exception>
public Task(Func<object?, TResult> function, object? state)
: this(function, state, null, default,
TaskCreationOptions.None, InternalTaskOptions.None, null)
{
}
/// <summary>
/// Initializes a new <see cref="Task{TResult}"/> with the specified action, state, and options.
/// </summary>
/// <param name="function">
/// The delegate that represents the code to execute in the task. When the function has completed,
/// the task's <see cref="Result"/> property will be set to return the result value of the function.
/// </param>
/// <param name="state">An object representing data to be used by the function.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to be assigned to the new task.</param>
/// <exception cref="ArgumentException">
/// The <paramref name="function"/> argument is null.
/// </exception>
/// <exception cref="ObjectDisposedException">The provided <see cref="CancellationToken">CancellationToken</see>
/// has already been disposed.
/// </exception>
public Task(Func<object?, TResult> function, object? state, CancellationToken cancellationToken)
: this(function, state, null, cancellationToken,
TaskCreationOptions.None, InternalTaskOptions.None, null)
{
}
/// <summary>
/// Initializes a new <see cref="Task{TResult}"/> with the specified action, state, and options.
/// </summary>
/// <param name="function">
/// The delegate that represents the code to execute in the task. When the function has completed,
/// the task's <see cref="Result"/> property will be set to return the result value of the function.
/// </param>
/// <param name="state">An object representing data to be used by the function.</param>
/// <param name="creationOptions">
/// The <see cref="TaskCreationOptions">TaskCreationOptions</see> used to
/// customize the task's behavior.
/// </param>
/// <exception cref="ArgumentException">
/// The <paramref name="function"/> argument is null.
/// </exception>
/// <exception cref="ArgumentOutOfRangeException">
/// The <paramref name="creationOptions"/> argument specifies an invalid value for <see
/// cref="TaskCreationOptions"/>.
/// </exception>
public Task(Func<object?, TResult> function, object? state, TaskCreationOptions creationOptions)
: this(function, state, InternalCurrentIfAttached(creationOptions), default,
creationOptions, InternalTaskOptions.None, null)
{
}
/// <summary>
/// Initializes a new <see cref="Task{TResult}"/> with the specified action, state, and options.
/// </summary>
/// <param name="function">
/// The delegate that represents the code to execute in the task. When the function has completed,
/// the task's <see cref="Result"/> property will be set to return the result value of the function.
/// </param>
/// <param name="state">An object representing data to be used by the function.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to be assigned to the new task.</param>
/// <param name="creationOptions">
/// The <see cref="TaskCreationOptions">TaskCreationOptions</see> used to
/// customize the task's behavior.
/// </param>
/// <exception cref="ArgumentException">
/// The <paramref name="function"/> argument is null.
/// </exception>
/// <exception cref="ArgumentOutOfRangeException">
/// The <paramref name="creationOptions"/> argument specifies an invalid value for <see
/// cref="TaskCreationOptions"/>.
/// </exception>
/// <exception cref="ObjectDisposedException">The provided <see cref="CancellationToken">CancellationToken</see>
/// has already been disposed.
/// </exception>
public Task(Func<object?, TResult> function, object? state, CancellationToken cancellationToken, TaskCreationOptions creationOptions)
: this(function, state, InternalCurrentIfAttached(creationOptions), cancellationToken,
creationOptions, InternalTaskOptions.None, null)
{
}
/// <summary>
/// Creates a new future object.
/// </summary>
/// <param name="parent">The parent task for this future.</param>
/// <param name="valueSelector">A function that yields the future value.</param>
/// <param name="scheduler">The task scheduler which will be used to execute the future.</param>
/// <param name="cancellationToken">The CancellationToken for the task.</param>
/// <param name="creationOptions">Options to control the future's behavior.</param>
/// <param name="internalOptions">Internal options to control the future's behavior.</param>
internal Task(Func<TResult> valueSelector, Task? parent, CancellationToken cancellationToken,
TaskCreationOptions creationOptions, InternalTaskOptions internalOptions, TaskScheduler? scheduler) :
base(valueSelector, null, parent, cancellationToken, creationOptions, internalOptions, scheduler)
{
}
/// <summary>
/// Creates a new future object.
/// </summary>
/// <param name="parent">The parent task for this future.</param>
/// <param name="state">An object containing data to be used by the action; may be null.</param>
/// <param name="valueSelector">A function that yields the future value.</param>
/// <param name="cancellationToken">The CancellationToken for the task.</param>
/// <param name="scheduler">The task scheduler which will be used to execute the future.</param>
/// <param name="creationOptions">Options to control the future's behavior.</param>
/// <param name="internalOptions">Internal options to control the future's behavior.</param>
internal Task(Delegate valueSelector, object? state, Task? parent, CancellationToken cancellationToken,
TaskCreationOptions creationOptions, InternalTaskOptions internalOptions, TaskScheduler? scheduler) :
base(valueSelector, state, parent, cancellationToken, creationOptions, internalOptions, scheduler)
{
}
// Internal method used by TaskFactory<TResult>.StartNew() methods
internal static Task<TResult> StartNew(Task? parent, Func<TResult> function, CancellationToken cancellationToken,
TaskCreationOptions creationOptions, InternalTaskOptions internalOptions, TaskScheduler scheduler)
{
if (function == null)
{
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.function);
}
if (scheduler == null)
{
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.scheduler);
}
// Create and schedule the future.
Task<TResult> f = new Task<TResult>(function, parent, cancellationToken, creationOptions, internalOptions | InternalTaskOptions.QueuedByRuntime, scheduler);
f.ScheduleAndStart(false);
return f;
}
// Internal method used by TaskFactory<TResult>.StartNew() methods
internal static Task<TResult> StartNew(Task? parent, Func<object?, TResult> function, object? state, CancellationToken cancellationToken,
TaskCreationOptions creationOptions, InternalTaskOptions internalOptions, TaskScheduler scheduler)
{
if (function == null)
{
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.function);
}
if (scheduler == null)
{
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.scheduler);
}
// Create and schedule the future.
Task<TResult> f = new Task<TResult>(function, state, parent, cancellationToken, creationOptions, internalOptions | InternalTaskOptions.QueuedByRuntime, scheduler);
f.ScheduleAndStart(false);
return f;
}
// Debugger support
private string DebuggerDisplayResultDescription =>
IsCompletedSuccessfully ? "" + m_result : SR.TaskT_DebuggerNoResult;
// Debugger support
private string DebuggerDisplayMethodDescription =>
m_action?.Method.ToString() ?? "{null}";
// internal helper function breaks out logic used by TaskCompletionSource
internal bool TrySetResult(TResult? result)
{
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 set the result and finish completion processing.
if (AtomicStateUpdate((int)TaskStateFlags.CompletionReserved,
(int)TaskStateFlags.CompletionReserved | (int)TaskStateFlags.RanToCompletion | (int)TaskStateFlags.Faulted | (int)TaskStateFlags.Canceled))
{
m_result = result;
// Signal completion, for waiting tasks
// This logic used to be:
// Finish(false);
// However, that goes through a windy code path, involves many non-inlineable functions
// and which can be summarized more concisely with the following snippet from
// FinishStageTwo, omitting everything that doesn't pertain to TrySetResult.
Interlocked.Exchange(ref m_stateFlags, m_stateFlags | (int)TaskStateFlags.RanToCompletion);
ContingentProperties? props = m_contingentProperties;
if (props != null)
{
NotifyParentIfPotentiallyAttachedTask();
props.SetCompleted();
}
FinishContinuations();
returnValue = true;
}
return returnValue;
}
// Transitions the promise task into a successfully completed state with the specified result.
// This is dangerous, as no synchronization is used, and thus must only be used
// before this task is handed out to any consumers, before any continuations are hooked up,
// before its wait handle is accessed, etc. It's use is limited to places like in FromAsync
// where the operation completes synchronously, and thus we know we can forcefully complete
// the task, avoiding expensive completion paths, before the task is actually given to anyone.
internal void DangerousSetResult(TResult result)
{
Debug.Assert(!IsCompleted, "The promise must not yet be completed.");
// If we have a parent, we need to notify it of the completion. Take the slow path to handle that.
if (m_contingentProperties?.m_parent != null)
{
bool success = TrySetResult(result);
// Nobody else has had a chance to complete this Task yet, so we should succeed.
Debug.Assert(success);
}
else
{
m_result = result;
m_stateFlags |= (int)TaskStateFlags.RanToCompletion;
}
}
/// <summary>
/// Gets the result value of this <see cref="Task{TResult}"/>.
/// </summary>
/// <remarks>
/// The get accessor for this property ensures that the asynchronous operation is complete before
/// returning. Once the result of the computation is available, it is stored and will be returned
/// immediately on later calls to <see cref="Result"/>.
/// </remarks>
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
public TResult Result =>
IsWaitNotificationEnabledOrNotRanToCompletion ?
GetResultCore(waitCompletionNotification: true) :
m_result!;
/// <summary>
/// Gets the result value of this <see cref="Task{TResult}"/> once the task has completed successfully.
/// </summary>
/// <remarks>
/// This version of Result should only be used if the task completed successfully and if there's
/// no debugger wait notification enabled for this task.
/// </remarks>
internal TResult ResultOnSuccess
{
get
{
Debug.Assert(!IsWaitNotificationEnabledOrNotRanToCompletion,
"Should only be used when the task completed successfully and there's no wait notification enabled");
return m_result!;
}
}
// Implements Result. Result delegates to this method if the result isn't already available.
internal TResult GetResultCore(bool waitCompletionNotification)
{
// If the result has not been calculated yet, wait for it.
if (!IsCompleted) InternalWait(Timeout.Infinite, default); // won't throw if task faulted or canceled; that's handled below
// Notify the debugger of the wait completion if it's requested such a notification
if (waitCompletionNotification) NotifyDebuggerOfWaitCompletionIfNecessary();
// Throw an exception if appropriate.
if (!IsCompletedSuccessfully) ThrowIfExceptional(includeTaskCanceledExceptions: true);
// We shouldn't be here if the result has not been set.
Debug.Assert(IsCompletedSuccessfully, "Task<T>.Result getter: Expected result to have been set.");
return m_result!;
}
/// <summary>
/// Provides access to factory methods for creating <see cref="Task{TResult}"/> instances.
/// </summary>
/// <remarks>
/// The factory returned from <see cref="Factory"/> is a default instance
/// of <see cref="TaskFactory{TResult}"/>, as would result from using
/// the default constructor on the factory type.
/// </remarks>
public static new TaskFactory<TResult> Factory =>
Volatile.Read(ref s_Factory) ??
Interlocked.CompareExchange(ref s_Factory, new TaskFactory<TResult>(), null) ??
s_Factory;
/// <summary>
/// Evaluates the value selector of the Task which is passed in as an object and stores the result.
/// </summary>
internal override void InnerInvoke()
{
// Invoke the delegate
Debug.Assert(m_action != null);
if (m_action is Func<TResult> func)
{
m_result = func();
return;
}
if (m_action is Func<object?, TResult> funcWithState)
{
m_result = funcWithState(m_stateObject);
return;
}
Debug.Fail("Invalid m_action in Task<TResult>");
}
#region Await Support
/// <summary>Gets an awaiter used to await this <see cref="Task{TResult}"/>.</summary>
/// <returns>An awaiter instance.</returns>
public new TaskAwaiter<TResult> GetAwaiter()
{
return new TaskAwaiter<TResult>(this);
}
/// <summary>Configures an awaiter used to await this <see cref="Task{TResult}"/>.</summary>
/// <param name="continueOnCapturedContext">
/// true to attempt to marshal the continuation back to the original context captured; otherwise, false.
/// </param>
/// <returns>An object used to await this task.</returns>
public new ConfiguredTaskAwaitable<TResult> ConfigureAwait(bool continueOnCapturedContext)
{
return new ConfiguredTaskAwaitable<TResult>(this, continueOnCapturedContext ? ConfigureAwaitOptions.ContinueOnCapturedContext : ConfigureAwaitOptions.None);
}
/// <summary>Configures an awaiter used to await this <see cref="Task"/>.</summary>
/// <param name="options">Options used to configure how awaits on this task are performed.</param>
/// <returns>An object used to await this task.</returns>
/// <exception cref="ArgumentOutOfRangeException">The <paramref name="options"/> argument specifies an invalid value.</exception>
public new ConfiguredTaskAwaitable<TResult> ConfigureAwait(ConfigureAwaitOptions options)
{
if ((options & ~(ConfigureAwaitOptions.ContinueOnCapturedContext |
ConfigureAwaitOptions.ForceYielding)) != 0)
{
ThrowForInvalidOptions(options);
}
return new ConfiguredTaskAwaitable<TResult>(this, options);
static void ThrowForInvalidOptions(ConfigureAwaitOptions options) =>
throw ((options & ConfigureAwaitOptions.SuppressThrowing) == 0 ?
new ArgumentOutOfRangeException(nameof(options)) :
new ArgumentOutOfRangeException(nameof(options), SR.TaskT_ConfigureAwait_InvalidOptions));
}
#endregion
#region WaitAsync methods
/// <summary>Gets a <see cref="Task{TResult}"/> that will complete when this <see cref="Task{TResult}"/> completes or when the specified <see cref="CancellationToken"/> has cancellation requested.</summary>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for a cancellation request.</param>
/// <returns>The <see cref="Task{TResult}"/> representing the asynchronous wait. It may or may not be the same instance as the current instance.</returns>
public new Task<TResult> WaitAsync(CancellationToken cancellationToken) =>
WaitAsync(Timeout.UnsignedInfinite, TimeProvider.System, cancellationToken);
/// <summary>Gets a <see cref="Task{TResult}"/> that will complete when this <see cref="Task{TResult}"/> completes or when the specified timeout expires.</summary>
/// <param name="timeout">The timeout after which the <see cref="Task"/> should be faulted with a <see cref="TimeoutException"/> if it hasn't otherwise completed.</param>
/// <returns>The <see cref="Task{TResult}"/> representing the asynchronous wait. It may or may not be the same instance as the current instance.</returns>
public new Task<TResult> WaitAsync(TimeSpan timeout) =>
WaitAsync(ValidateTimeout(timeout, ExceptionArgument.timeout), TimeProvider.System, default);
/// <summary>
/// Gets a <see cref="Task{TResult}"/> that will complete when this <see cref="Task{TResult}"/> completes or when the specified timeout expires.
/// </summary>
/// <param name="timeout">The timeout after which the <see cref="Task"/> should be faulted with a <see cref="TimeoutException"/> if it hasn't otherwise completed.</param>
/// <param name="timeProvider">The <see cref="TimeProvider"/> with which to interpret <paramref name="timeout"/>.</param>
/// <returns>The <see cref="Task{TResult}"/> representing the asynchronous wait. It may or may not be the same instance as the current instance.</returns>
public new Task<TResult> WaitAsync(TimeSpan timeout, TimeProvider timeProvider)
{
ArgumentNullException.ThrowIfNull(timeProvider);
return WaitAsync(ValidateTimeout(timeout, ExceptionArgument.timeout), timeProvider, default);
}
/// <summary>Gets a <see cref="Task{TResult}"/> that will complete when this <see cref="Task{TResult}"/> completes, when the specified timeout expires, or when the specified <see cref="CancellationToken"/> has cancellation requested.</summary>
/// <param name="timeout">The timeout after which the <see cref="Task"/> should be faulted with a <see cref="TimeoutException"/> if it hasn't otherwise completed.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for a cancellation request.</param>
/// <returns>The <see cref="Task{TResult}"/> representing the asynchronous wait. It may or may not be the same instance as the current instance.</returns>
public new Task<TResult> WaitAsync(TimeSpan timeout, CancellationToken cancellationToken) =>
WaitAsync(ValidateTimeout(timeout, ExceptionArgument.timeout), TimeProvider.System, cancellationToken);
/// <summary>
/// Gets a <see cref="Task{TResult}"/> that will complete when this <see cref="Task{TResult}"/> completes, when the specified timeout expires, or when the specified <see cref="CancellationToken"/> has cancellation requested.
/// </summary>
/// <param name="timeout">The timeout after which the <see cref="Task"/> should be faulted with a <see cref="TimeoutException"/> if it hasn't otherwise completed.</param>
/// <param name="timeProvider">The <see cref="TimeProvider"/> with which to interpret <paramref name="timeout"/>.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for a cancellation request.</param>
/// <returns>The <see cref="Task{TResult}"/> representing the asynchronous wait. It may or may not be the same instance as the current instance.</returns>
public new Task<TResult> WaitAsync(TimeSpan timeout, TimeProvider timeProvider, CancellationToken cancellationToken)
{
ArgumentNullException.ThrowIfNull(timeProvider);
return WaitAsync(ValidateTimeout(timeout, ExceptionArgument.timeout), timeProvider, cancellationToken);
}
private Task<TResult> WaitAsync(uint millisecondsTimeout, TimeProvider timeProvider, CancellationToken cancellationToken)
{
if (IsCompleted || (!cancellationToken.CanBeCanceled && millisecondsTimeout == Timeout.UnsignedInfinite))
{
return this;
}
if (cancellationToken.IsCancellationRequested)
{
return FromCanceled<TResult>(cancellationToken);
}
if (millisecondsTimeout == 0)
{
return FromException<TResult>(new TimeoutException());
}
return new CancellationPromise<TResult>(this, millisecondsTimeout, timeProvider, cancellationToken);
}
#endregion
#region Continuation methods
#region Action<Task<TResult>> continuations
/// <summary>
/// Creates a continuation that executes when the target <see cref="Task{TResult}"/> completes.
/// </summary>
/// <param name="continuationAction">
/// An action to run when the <see cref="Task{TResult}"/> completes. When run, the delegate will be
/// passed the completed task as an argument.
/// </param>
/// <returns>A new continuation <see cref="Task"/>.</returns>
/// <remarks>
/// The returned <see cref="Task"/> will not be scheduled for execution until the current task has
/// completed, whether it completes due to running to completion successfully, faulting due to an
/// unhandled exception, or exiting out early due to being canceled.
/// </remarks>
/// <exception cref="ArgumentNullException">
/// The <paramref name="continuationAction"/> argument is null.
/// </exception>
public Task ContinueWith(Action<Task<TResult>> continuationAction)
{
return ContinueWith(continuationAction, TaskScheduler.Current, default, TaskContinuationOptions.None);
}
/// <summary>
/// Creates a continuation that executes when the target <see cref="Task{TResult}"/> completes.
/// </summary>
/// <param name="continuationAction">
/// An action to run when the <see cref="Task{TResult}"/> completes. When run, the delegate will be
/// passed the completed task as an argument.
/// </param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that will be assigned to the new continuation task.</param>
/// <returns>A new continuation <see cref="Task"/>.</returns>
/// <remarks>
/// The returned <see cref="Task"/> will not be scheduled for execution until the current task has
/// completed, whether it completes due to running to completion successfully, faulting due to an
/// unhandled exception, or exiting out early due to being canceled.
/// </remarks>
/// <exception cref="ArgumentNullException">
/// The <paramref name="continuationAction"/> argument is null.
/// </exception>
public Task ContinueWith(Action<Task<TResult>> continuationAction, CancellationToken cancellationToken)
{
return ContinueWith(continuationAction, TaskScheduler.Current, cancellationToken, TaskContinuationOptions.None);
}
/// <summary>
/// Creates a continuation that executes when the target <see cref="Task{TResult}"/> completes.
/// </summary>
/// <param name="continuationAction">
/// An action to run when the <see cref="Task{TResult}"/> completes. When run, the delegate will be
/// passed the completed task as an argument.
/// </param>
/// <param name="scheduler">
/// The <see cref="TaskScheduler"/> to associate with the continuation task and to use for its execution.
/// </param>
/// <returns>A new continuation <see cref="Task"/>.</returns>
/// <remarks>
/// The returned <see cref="Task"/> will not be scheduled for execution until the current task has
/// completed, whether it completes due to running to completion successfully, faulting due to an
/// unhandled exception, or exiting out early due to being canceled.
/// </remarks>
/// <exception cref="ArgumentNullException">
/// The <paramref name="continuationAction"/> argument is null.
/// </exception>
/// <exception cref="ArgumentNullException">
/// The <paramref name="scheduler"/> argument is null.
/// </exception>
public Task ContinueWith(Action<Task<TResult>> continuationAction, TaskScheduler scheduler)
{
return ContinueWith(continuationAction, scheduler, default, TaskContinuationOptions.None);
}
/// <summary>
/// Creates a continuation that executes when the target <see cref="Task{TResult}"/> completes.
/// </summary>
/// <param name="continuationAction">
/// An action to run when the <see cref="Task{TResult}"/> completes. When run, the delegate will be
/// passed the completed task as an argument.
/// </param>
/// <param name="continuationOptions">
/// Options for when the continuation is scheduled and how it behaves. This includes criteria, such
/// as <see
/// cref="TaskContinuationOptions.OnlyOnCanceled">OnlyOnCanceled</see>, as
/// well as execution options, such as <see
/// cref="TaskContinuationOptions.ExecuteSynchronously">ExecuteSynchronously</see>.
/// </param>
/// <returns>A new continuation <see cref="Task"/>.</returns>
/// <remarks>
/// The returned <see cref="Task"/> will not be scheduled for execution until the current task has
/// completed. If the continuation criteria specified through the <paramref
/// name="continuationOptions"/> parameter are not met, the continuation task will be canceled
/// instead of scheduled.
/// </remarks>
/// <exception cref="ArgumentNullException">
/// The <paramref name="continuationAction"/> argument is null.
/// </exception>
/// <exception cref="ArgumentOutOfRangeException">
/// The <paramref name="continuationOptions"/> argument specifies an invalid value for <see
/// cref="TaskContinuationOptions">TaskContinuationOptions</see>.
/// </exception>
public Task ContinueWith(Action<Task<TResult>> continuationAction, TaskContinuationOptions continuationOptions)
{
return ContinueWith(continuationAction, TaskScheduler.Current, default, continuationOptions);
}
/// <summary>
/// Creates a continuation that executes when the target <see cref="Task{TResult}"/> completes.
/// </summary>
/// <param name="continuationAction">
/// An action to run when the <see cref="Task{TResult}"/> completes. When run, the delegate will be
/// passed the completed task as an argument.
/// </param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that will be assigned to the new continuation task.</param>
/// <param name="continuationOptions">
/// Options for when the continuation is scheduled and how it behaves. This includes criteria, such
/// as <see
/// cref="TaskContinuationOptions.OnlyOnCanceled">OnlyOnCanceled</see>, as
/// well as execution options, such as <see
/// cref="TaskContinuationOptions.ExecuteSynchronously">ExecuteSynchronously</see>.
/// </param>
/// <param name="scheduler">
/// The <see cref="TaskScheduler"/> to associate with the continuation task and to use for its
/// execution.
/// </param>
/// <returns>A new continuation <see cref="Task"/>.</returns>
/// <remarks>
/// The returned <see cref="Task"/> will not be scheduled for execution until the current task has
/// completed. If the criteria specified through the <paramref name="continuationOptions"/> parameter
/// are not met, the continuation task will be canceled instead of scheduled.
/// </remarks>
/// <exception cref="ArgumentNullException">
/// The <paramref name="continuationAction"/> argument is null.
/// </exception>
/// <exception cref="ArgumentOutOfRangeException">
/// The <paramref name="continuationOptions"/> argument specifies an invalid value for <see
/// cref="TaskContinuationOptions">TaskContinuationOptions</see>.
/// </exception>
/// <exception cref="ArgumentNullException">
/// The <paramref name="scheduler"/> argument is null.
/// </exception>
public Task ContinueWith(Action<Task<TResult>> continuationAction, CancellationToken cancellationToken,
TaskContinuationOptions continuationOptions, TaskScheduler scheduler)
{
return ContinueWith(continuationAction, scheduler, cancellationToken, continuationOptions);
}
// Same as the above overload, only with a stack mark.
internal Task ContinueWith(Action<Task<TResult>> continuationAction, TaskScheduler scheduler, CancellationToken cancellationToken,
TaskContinuationOptions continuationOptions)
{
if (continuationAction == null)
{
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.continuationAction);
}
if (scheduler == null)
{
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.scheduler);
}
CreationOptionsFromContinuationOptions(
continuationOptions,
out TaskCreationOptions creationOptions,
out InternalTaskOptions internalOptions);
Task continuationTask = new ContinuationTaskFromResultTask<TResult>(
this, continuationAction, null,
creationOptions, internalOptions
);
// Register the continuation. If synchronous execution is requested, this may
// actually invoke the continuation before returning.
ContinueWithCore(continuationTask, scheduler, cancellationToken, continuationOptions);
return continuationTask;
}
#endregion
#region Action<Task<TResult>, Object> continuations
/// <summary>
/// Creates a continuation that executes when the target <see cref="Task{TResult}"/> completes.
/// </summary>
/// <param name="continuationAction">
/// An action to run when the <see cref="Task{TResult}"/> completes. When run, the delegate will be
/// passed the completed task and the caller-supplied state object as arguments.
/// </param>
/// <param name="state">An object representing data to be used by the continuation action.</param>
/// <returns>A new continuation <see cref="Task"/>.</returns>
/// <remarks>
/// The returned <see cref="Task"/> will not be scheduled for execution until the current task has
/// completed, whether it completes due to running to completion successfully, faulting due to an
/// unhandled exception, or exiting out early due to being canceled.
/// </remarks>
/// <exception cref="ArgumentNullException">
/// The <paramref name="continuationAction"/> argument is null.
/// </exception>
public Task ContinueWith(Action<Task<TResult>, object?> continuationAction, object? state)
{
return ContinueWith(continuationAction, state, TaskScheduler.Current, default, TaskContinuationOptions.None);
}
/// <summary>
/// Creates a continuation that executes when the target <see cref="Task{TResult}"/> completes.
/// </summary>
/// <param name="continuationAction">
/// An action to run when the <see cref="Task{TResult}"/> completes. When run, the delegate will be
/// passed the completed task and the caller-supplied state object as arguments.
/// </param>
/// <param name="state">An object representing data to be used by the continuation action.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that will be assigned to the new continuation task.</param>
/// <returns>A new continuation <see cref="Task"/>.</returns>
/// <remarks>
/// The returned <see cref="Task"/> will not be scheduled for execution until the current task has
/// completed, whether it completes due to running to completion successfully, faulting due to an
/// unhandled exception, or exiting out early due to being canceled.
/// </remarks>
/// <exception cref="ArgumentNullException">
/// The <paramref name="continuationAction"/> argument is null.
/// </exception>
public Task ContinueWith(Action<Task<TResult>, object?> continuationAction, object? state, CancellationToken cancellationToken)
{
return ContinueWith(continuationAction, state, TaskScheduler.Current, cancellationToken, TaskContinuationOptions.None);
}
/// <summary>
/// Creates a continuation that executes when the target <see cref="Task{TResult}"/> completes.
/// </summary>
/// <param name="continuationAction">
/// An action to run when the <see cref="Task{TResult}"/> completes. When run, the delegate will be
/// passed the completed task and the caller-supplied state object as arguments.
/// </param>
/// <param name="state">An object representing data to be used by the continuation action.</param>
/// <param name="scheduler">
/// The <see cref="TaskScheduler"/> to associate with the continuation task and to use for its execution.
/// </param>
/// <returns>A new continuation <see cref="Task"/>.</returns>
/// <remarks>
/// The returned <see cref="Task"/> will not be scheduled for execution until the current task has
/// completed, whether it completes due to running to completion successfully, faulting due to an
/// unhandled exception, or exiting out early due to being canceled.
/// </remarks>
/// <exception cref="ArgumentNullException">
/// The <paramref name="continuationAction"/> argument is null.
/// </exception>
/// <exception cref="ArgumentNullException">
/// The <paramref name="scheduler"/> argument is null.
/// </exception>
public Task ContinueWith(Action<Task<TResult>, object?> continuationAction, object? state, TaskScheduler scheduler)
{
return ContinueWith(continuationAction, state, scheduler, default, TaskContinuationOptions.None);
}
/// <summary>
/// Creates a continuation that executes when the target <see cref="Task{TResult}"/> completes.
/// </summary>
/// <param name="continuationAction">
/// An action to run when the <see cref="Task{TResult}"/> completes. When run, the delegate will be
/// passed the completed task and the caller-supplied state object as arguments.
/// </param>
/// <param name="state">An object representing data to be used by the continuation action.</param>
/// <param name="continuationOptions">
/// Options for when the continuation is scheduled and how it behaves. This includes criteria, such
/// as <see
/// cref="TaskContinuationOptions.OnlyOnCanceled">OnlyOnCanceled</see>, as
/// well as execution options, such as <see
/// cref="TaskContinuationOptions.ExecuteSynchronously">ExecuteSynchronously</see>.
/// </param>
/// <returns>A new continuation <see cref="Task"/>.</returns>
/// <remarks>
/// The returned <see cref="Task"/> will not be scheduled for execution until the current task has
/// completed. If the continuation criteria specified through the <paramref
/// name="continuationOptions"/> parameter are not met, the continuation task will be canceled
/// instead of scheduled.
/// </remarks>
/// <exception cref="ArgumentNullException">
/// The <paramref name="continuationAction"/> argument is null.
/// </exception>
/// <exception cref="ArgumentOutOfRangeException">
/// The <paramref name="continuationOptions"/> argument specifies an invalid value for <see
/// cref="TaskContinuationOptions">TaskContinuationOptions</see>.
/// </exception>
public Task ContinueWith(Action<Task<TResult>, object?> continuationAction, object? state, TaskContinuationOptions continuationOptions)
{
return ContinueWith(continuationAction, state, TaskScheduler.Current, default, continuationOptions);
}
/// <summary>
/// Creates a continuation that executes when the target <see cref="Task{TResult}"/> completes.
/// </summary>
/// <param name="continuationAction">
/// An action to run when the <see cref="Task{TResult}"/> completes. When run, the delegate will be
/// passed the completed task and the caller-supplied state object as arguments.
/// </param>
/// <param name="state">An object representing data to be used by the continuation action.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that will be assigned to the new continuation task.</param>
/// <param name="continuationOptions">
/// Options for when the continuation is scheduled and how it behaves. This includes criteria, such
/// as <see
/// cref="TaskContinuationOptions.OnlyOnCanceled">OnlyOnCanceled</see>, as
/// well as execution options, such as <see
/// cref="TaskContinuationOptions.ExecuteSynchronously">ExecuteSynchronously</see>.
/// </param>
/// <param name="scheduler">
/// The <see cref="TaskScheduler"/> to associate with the continuation task and to use for its
/// execution.
/// </param>
/// <returns>A new continuation <see cref="Task"/>.</returns>
/// <remarks>
/// The returned <see cref="Task"/> will not be scheduled for execution until the current task has
/// completed. If the criteria specified through the <paramref name="continuationOptions"/> parameter
/// are not met, the continuation task will be canceled instead of scheduled.
/// </remarks>
/// <exception cref="ArgumentNullException">
/// The <paramref name="continuationAction"/> argument is null.
/// </exception>
/// <exception cref="ArgumentOutOfRangeException">
/// The <paramref name="continuationOptions"/> argument specifies an invalid value for <see
/// cref="TaskContinuationOptions">TaskContinuationOptions</see>.
/// </exception>
/// <exception cref="ArgumentNullException">
/// The <paramref name="scheduler"/> argument is null.
/// </exception>
public Task ContinueWith(Action<Task<TResult>, object?> continuationAction, object? state, CancellationToken cancellationToken,
TaskContinuationOptions continuationOptions, TaskScheduler scheduler)
{
return ContinueWith(continuationAction, state, scheduler, cancellationToken, continuationOptions);
}
// Same as the above overload, only with a stack mark.
internal Task ContinueWith(Action<Task<TResult>, object?> continuationAction, object? state, TaskScheduler scheduler, CancellationToken cancellationToken,
TaskContinuationOptions continuationOptions)
{
if (continuationAction == null)
{
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.continuationAction);
}
if (scheduler == null)
{
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.scheduler);
}
CreationOptionsFromContinuationOptions(
continuationOptions,
out TaskCreationOptions creationOptions,
out InternalTaskOptions internalOptions);
Task continuationTask = new ContinuationTaskFromResultTask<TResult>(
this, continuationAction, state,
creationOptions, internalOptions
);
// Register the continuation. If synchronous execution is requested, this may
// actually invoke the continuation before returning.
ContinueWithCore(continuationTask, scheduler, cancellationToken, continuationOptions);
return continuationTask;
}
#endregion
#region Func<Task<TResult>,TNewResult> continuations
/// <summary>
/// Creates a continuation that executes when the target <see cref="Task{TResult}"/> completes.
/// </summary>
/// <typeparam name="TNewResult">
/// The type of the result produced by the continuation.
/// </typeparam>
/// <param name="continuationFunction">
/// A function to run when the <see cref="Task{TResult}"/> completes. When run, the delegate will be
/// passed the completed task as an argument.
/// </param>
/// <returns>A new continuation <see cref="Task{TNewResult}"/>.</returns>
/// <remarks>
/// The returned <see cref="Task{TNewResult}"/> will not be scheduled for execution until the current
/// task has completed, whether it completes due to running to completion successfully, faulting due
/// to an unhandled exception, or exiting out early due to being canceled.
/// </remarks>
/// <exception cref="ArgumentNullException">
/// The <paramref name="continuationFunction"/> argument is null.
/// </exception>
public Task<TNewResult> ContinueWith<TNewResult>(Func<Task<TResult>, TNewResult> continuationFunction)
{
return ContinueWith(continuationFunction, TaskScheduler.Current, default, TaskContinuationOptions.None);
}
/// <summary>
/// Creates a continuation that executes when the target <see cref="Task{TResult}"/> completes.
/// </summary>
/// <typeparam name="TNewResult">
/// The type of the result produced by the continuation.
/// </typeparam>
/// <param name="continuationFunction">
/// A function to run when the <see cref="Task{TResult}"/> completes. When run, the delegate will be
/// passed the completed task as an argument.
/// </param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that will be assigned to the new task.</param>
/// <returns>A new continuation <see cref="Task{TNewResult}"/>.</returns>
/// <remarks>
/// The returned <see cref="Task{TNewResult}"/> will not be scheduled for execution until the current
/// task has completed, whether it completes due to running to completion successfully, faulting due
/// to an unhandled exception, or exiting out early due to being canceled.
/// </remarks>
/// <exception cref="ArgumentNullException">
/// The <paramref name="continuationFunction"/> argument is null.
/// </exception>
/// <exception cref="ObjectDisposedException">The provided <see cref="CancellationToken">CancellationToken</see>
/// has already been disposed.
/// </exception>
public Task<TNewResult> ContinueWith<TNewResult>(Func<Task<TResult>, TNewResult> continuationFunction, CancellationToken cancellationToken)
{
return ContinueWith(continuationFunction, TaskScheduler.Current, cancellationToken, TaskContinuationOptions.None);
}
/// <summary>
/// Creates a continuation that executes when the target <see cref="Task{TResult}"/> completes.
/// </summary>
/// <typeparam name="TNewResult">
/// The type of the result produced by the continuation.
/// </typeparam>
/// <param name="continuationFunction">
/// A function to run when the <see cref="Task{TResult}"/> completes. When run, the delegate will be
/// passed the completed task as an argument.
/// </param>
/// <param name="scheduler">
/// The <see cref="TaskScheduler"/> to associate with the continuation task and to use for its execution.
/// </param>
/// <returns>A new continuation <see cref="Task{TNewResult}"/>.</returns>
/// <remarks>
/// The returned <see cref="Task{TNewResult}"/> will not be scheduled for execution until the current task has
/// completed, whether it completes due to running to completion successfully, faulting due to an
/// unhandled exception, or exiting out early due to being canceled.
/// </remarks>
/// <exception cref="ArgumentNullException">
/// The <paramref name="continuationFunction"/> argument is null.
/// </exception>
/// <exception cref="ArgumentNullException">
/// The <paramref name="scheduler"/> argument is null.
/// </exception>
public Task<TNewResult> ContinueWith<TNewResult>(Func<Task<TResult>, TNewResult> continuationFunction, TaskScheduler scheduler)
{
return ContinueWith(continuationFunction, scheduler, default, TaskContinuationOptions.None);
}
/// <summary>
/// Creates a continuation that executes when the target <see cref="Task{TResult}"/> completes.
/// </summary>
/// <typeparam name="TNewResult">
/// The type of the result produced by the continuation.
/// </typeparam>
/// <param name="continuationFunction">
/// A function to run when the <see cref="Task{TResult}"/> completes. When run, the delegate will be
/// passed the completed task as an argument.
/// </param>
/// <param name="continuationOptions">
/// Options for when the continuation is scheduled and how it behaves. This includes criteria, such
/// as <see
/// cref="TaskContinuationOptions.OnlyOnCanceled">OnlyOnCanceled</see>, as
/// well as execution options, such as <see
/// cref="TaskContinuationOptions.ExecuteSynchronously">ExecuteSynchronously</see>.
/// </param>
/// <returns>A new continuation <see cref="Task{TNewResult}"/>.</returns>
/// <remarks>
/// <para>
/// The returned <see cref="Task{TNewResult}"/> will not be scheduled for execution until the current
/// task has completed, whether it completes due to running to completion successfully, faulting due
/// to an unhandled exception, or exiting out early due to being canceled.
/// </para>
/// <para>
/// The <paramref name="continuationFunction"/>, when executed, should return a <see
/// cref="Task{TNewResult}"/>. This task's completion state will be transferred to the task returned
/// from the ContinueWith call.
/// </para>
/// </remarks>
/// <exception cref="ArgumentNullException">
/// The <paramref name="continuationFunction"/> argument is null.
/// </exception>
/// <exception cref="ArgumentOutOfRangeException">
/// The <paramref name="continuationOptions"/> argument specifies an invalid value for <see
/// cref="TaskContinuationOptions">TaskContinuationOptions</see>.
/// </exception>
public Task<TNewResult> ContinueWith<TNewResult>(Func<Task<TResult>, TNewResult> continuationFunction, TaskContinuationOptions continuationOptions)
{
return ContinueWith(continuationFunction, TaskScheduler.Current, default, continuationOptions);
}
/// <summary>
/// Creates a continuation that executes when the target <see cref="Task{TResult}"/> completes.
/// </summary>
/// <typeparam name="TNewResult">
/// The type of the result produced by the continuation.
/// </typeparam>
/// <param name="continuationFunction">
/// A function to run when the <see cref="Task{TResult}"/> completes. When run, the delegate will be passed as
/// an argument this completed task.
/// </param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that will be assigned to the new task.</param>
/// <param name="continuationOptions">
/// Options for when the continuation is scheduled and how it behaves. This includes criteria, such
/// as <see
/// cref="TaskContinuationOptions.OnlyOnCanceled">OnlyOnCanceled</see>, as
/// well as execution options, such as <see
/// cref="TaskContinuationOptions.ExecuteSynchronously">ExecuteSynchronously</see>.
/// </param>
/// <param name="scheduler">
/// The <see cref="TaskScheduler"/> to associate with the continuation task and to use for its
/// execution.
/// </param>
/// <returns>A new continuation <see cref="Task{TNewResult}"/>.</returns>
/// <remarks>
/// <para>
/// The returned <see cref="Task{TNewResult}"/> will not be scheduled for execution until the current task has
/// completed, whether it completes due to running to completion successfully, faulting due to an
/// unhandled exception, or exiting out early due to being canceled.
/// </para>
/// <para>
/// The <paramref name="continuationFunction"/>, when executed, should return a <see cref="Task{TNewResult}"/>.
/// This task's completion state will be transferred to the task returned from the
/// ContinueWith call.
/// </para>
/// </remarks>
/// <exception cref="ArgumentNullException">
/// The <paramref name="continuationFunction"/> argument is null.
/// </exception>
/// <exception cref="ArgumentOutOfRangeException">
/// The <paramref name="continuationOptions"/> argument specifies an invalid value for <see
/// cref="TaskContinuationOptions">TaskContinuationOptions</see>.
/// </exception>
/// <exception cref="ArgumentNullException">
/// The <paramref name="scheduler"/> argument is null.
/// </exception>
/// <exception cref="ObjectDisposedException">The provided <see cref="CancellationToken">CancellationToken</see>
/// has already been disposed.
/// </exception>
public Task<TNewResult> ContinueWith<TNewResult>(Func<Task<TResult>, TNewResult> continuationFunction, CancellationToken cancellationToken,
TaskContinuationOptions continuationOptions, TaskScheduler scheduler)
{
return ContinueWith(continuationFunction, scheduler, cancellationToken, continuationOptions);
}
// Same as the above overload, just with a stack mark.
internal Task<TNewResult> ContinueWith<TNewResult>(Func<Task<TResult>, TNewResult> continuationFunction, TaskScheduler scheduler,
CancellationToken cancellationToken, TaskContinuationOptions continuationOptions)
{
if (continuationFunction == null)
{
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.continuationFunction);
}
if (scheduler == null)
{
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.scheduler);
}
CreationOptionsFromContinuationOptions(
continuationOptions,
out TaskCreationOptions creationOptions,
out InternalTaskOptions internalOptions);
Task<TNewResult> continuationFuture = new ContinuationResultTaskFromResultTask<TResult, TNewResult>(
this, continuationFunction, null,
creationOptions, internalOptions
);
// Register the continuation. If synchronous execution is requested, this may
// actually invoke the continuation before returning.
ContinueWithCore(continuationFuture, scheduler, cancellationToken, continuationOptions);
return continuationFuture;
}
#endregion
#region Func<Task<TResult>, Object,TNewResult> continuations
/// <summary>
/// Creates a continuation that executes when the target <see cref="Task{TResult}"/> completes.
/// </summary>
/// <typeparam name="TNewResult">
/// The type of the result produced by the continuation.
/// </typeparam>
/// <param name="continuationFunction">
/// A function to run when the <see cref="Task{TResult}"/> completes. When run, the delegate will be
/// passed the completed task and the caller-supplied state object as arguments.
/// </param>
/// <param name="state">An object representing data to be used by the continuation function.</param>
/// <returns>A new continuation <see cref="Task{TNewResult}"/>.</returns>
/// <remarks>
/// The returned <see cref="Task{TNewResult}"/> will not be scheduled for execution until the current
/// task has completed, whether it completes due to running to completion successfully, faulting due
/// to an unhandled exception, or exiting out early due to being canceled.
/// </remarks>
/// <exception cref="ArgumentNullException">
/// The <paramref name="continuationFunction"/> argument is null.
/// </exception>
public Task<TNewResult> ContinueWith<TNewResult>(Func<Task<TResult>, object?, TNewResult> continuationFunction, object? state)
{
return ContinueWith(continuationFunction, state, TaskScheduler.Current, default, TaskContinuationOptions.None);
}
/// <summary>
/// Creates a continuation that executes when the target <see cref="Task{TResult}"/> completes.
/// </summary>
/// <typeparam name="TNewResult">
/// The type of the result produced by the continuation.
/// </typeparam>
/// <param name="continuationFunction">
/// A function to run when the <see cref="Task{TResult}"/> completes. When run, the delegate will be
/// passed the completed task and the caller-supplied state object as arguments.
/// </param>
/// <param name="state">An object representing data to be used by the continuation function.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that will be assigned to the new task.</param>
/// <returns>A new continuation <see cref="Task{TNewResult}"/>.</returns>
/// <remarks>
/// The returned <see cref="Task{TNewResult}"/> will not be scheduled for execution until the current
/// task has completed, whether it completes due to running to completion successfully, faulting due
/// to an unhandled exception, or exiting out early due to being canceled.
/// </remarks>
/// <exception cref="ArgumentNullException">
/// The <paramref name="continuationFunction"/> argument is null.
/// </exception>
/// <exception cref="ObjectDisposedException">The provided <see cref="CancellationToken">CancellationToken</see>
/// has already been disposed.
/// </exception>
public Task<TNewResult> ContinueWith<TNewResult>(Func<Task<TResult>, object?, TNewResult> continuationFunction, object? state,
CancellationToken cancellationToken)
{
return ContinueWith(continuationFunction, state, TaskScheduler.Current, cancellationToken, TaskContinuationOptions.None);
}
/// <summary>
/// Creates a continuation that executes when the target <see cref="Task{TResult}"/> completes.
/// </summary>
/// <typeparam name="TNewResult">
/// The type of the result produced by the continuation.
/// </typeparam>
/// <param name="continuationFunction">
/// A function to run when the <see cref="Task{TResult}"/> completes. When run, the delegate will be
/// passed the completed task and the caller-supplied state object as arguments.
/// </param>
/// <param name="state">An object representing data to be used by the continuation function.</param>
/// <param name="scheduler">
/// The <see cref="TaskScheduler"/> to associate with the continuation task and to use for its execution.
/// </param>
/// <returns>A new continuation <see cref="Task{TNewResult}"/>.</returns>
/// <remarks>
/// The returned <see cref="Task{TNewResult}"/> will not be scheduled for execution until the current task has
/// completed, whether it completes due to running to completion successfully, faulting due to an
/// unhandled exception, or exiting out early due to being canceled.
/// </remarks>
/// <exception cref="ArgumentNullException">
/// The <paramref name="continuationFunction"/> argument is null.
/// </exception>
/// <exception cref="ArgumentNullException">
/// The <paramref name="scheduler"/> argument is null.
/// </exception>
public Task<TNewResult> ContinueWith<TNewResult>(Func<Task<TResult>, object?, TNewResult> continuationFunction, object? state,
TaskScheduler scheduler)
{
return ContinueWith(continuationFunction, state, scheduler, default, TaskContinuationOptions.None);
}
/// <summary>
/// Creates a continuation that executes when the target <see cref="Task{TResult}"/> completes.
/// </summary>
/// <typeparam name="TNewResult">
/// The type of the result produced by the continuation.
/// </typeparam>
/// <param name="continuationFunction">
/// A function to run when the <see cref="Task{TResult}"/> completes. When run, the delegate will be
/// passed the completed task and the caller-supplied state object as arguments.
/// </param>
/// <param name="state">An object representing data to be used by the continuation function.</param>
/// <param name="continuationOptions">
/// Options for when the continuation is scheduled and how it behaves. This includes criteria, such
/// as <see
/// cref="TaskContinuationOptions.OnlyOnCanceled">OnlyOnCanceled</see>, as
/// well as execution options, such as <see
/// cref="TaskContinuationOptions.ExecuteSynchronously">ExecuteSynchronously</see>.
/// </param>
/// <returns>A new continuation <see cref="Task{TNewResult}"/>.</returns>
/// <remarks>
/// <para>
/// The returned <see cref="Task{TNewResult}"/> will not be scheduled for execution until the current
/// task has completed, whether it completes due to running to completion successfully, faulting due
/// to an unhandled exception, or exiting out early due to being canceled.
/// </para>
/// <para>
/// The <paramref name="continuationFunction"/>, when executed, should return a <see
/// cref="Task{TNewResult}"/>. This task's completion state will be transferred to the task returned
/// from the ContinueWith call.
/// </para>
/// </remarks>
/// <exception cref="ArgumentNullException">
/// The <paramref name="continuationFunction"/> argument is null.
/// </exception>
/// <exception cref="ArgumentOutOfRangeException">
/// The <paramref name="continuationOptions"/> argument specifies an invalid value for <see
/// cref="TaskContinuationOptions">TaskContinuationOptions</see>.
/// </exception>
public Task<TNewResult> ContinueWith<TNewResult>(Func<Task<TResult>, object?, TNewResult> continuationFunction, object? state,
TaskContinuationOptions continuationOptions)
{
return ContinueWith(continuationFunction, state, TaskScheduler.Current, default, continuationOptions);
}
/// <summary>
/// Creates a continuation that executes when the target <see cref="Task{TResult}"/> completes.
/// </summary>
/// <typeparam name="TNewResult">
/// The type of the result produced by the continuation.
/// </typeparam>
/// <param name="continuationFunction">
/// A function to run when the <see cref="Task{TResult}"/> completes. When run, the delegate will be
/// passed the completed task and the caller-supplied state object as arguments.
/// </param>
/// <param name="state">An object representing data to be used by the continuation function.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that will be assigned to the new task.</param>
/// <param name="continuationOptions">
/// Options for when the continuation is scheduled and how it behaves. This includes criteria, such
/// as <see
/// cref="TaskContinuationOptions.OnlyOnCanceled">OnlyOnCanceled</see>, as
/// well as execution options, such as <see
/// cref="TaskContinuationOptions.ExecuteSynchronously">ExecuteSynchronously</see>.
/// </param>
/// <param name="scheduler">
/// The <see cref="TaskScheduler"/> to associate with the continuation task and to use for its
/// execution.
/// </param>
/// <returns>A new continuation <see cref="Task{TNewResult}"/>.</returns>
/// <remarks>
/// <para>
/// The returned <see cref="Task{TNewResult}"/> will not be scheduled for execution until the current task has
/// completed, whether it completes due to running to completion successfully, faulting due to an
/// unhandled exception, or exiting out early due to being canceled.
/// </para>
/// <para>
/// The <paramref name="continuationFunction"/>, when executed, should return a <see cref="Task{TNewResult}"/>.
/// This task's completion state will be transferred to the task returned from the
/// ContinueWith call.
/// </para>
/// </remarks>
/// <exception cref="ArgumentNullException">
/// The <paramref name="continuationFunction"/> argument is null.
/// </exception>
/// <exception cref="ArgumentOutOfRangeException">
/// The <paramref name="continuationOptions"/> argument specifies an invalid value for <see
/// cref="TaskContinuationOptions">TaskContinuationOptions</see>.
/// </exception>
/// <exception cref="ArgumentNullException">
/// The <paramref name="scheduler"/> argument is null.
/// </exception>
/// <exception cref="ObjectDisposedException">The provided <see cref="CancellationToken">CancellationToken</see>
/// has already been disposed.
/// </exception>
public Task<TNewResult> ContinueWith<TNewResult>(Func<Task<TResult>, object?, TNewResult> continuationFunction, object? state,
CancellationToken cancellationToken, TaskContinuationOptions continuationOptions, TaskScheduler scheduler)
{
return ContinueWith(continuationFunction, state, scheduler, cancellationToken, continuationOptions);
}
// Same as the above overload, just with a stack mark.
internal Task<TNewResult> ContinueWith<TNewResult>(Func<Task<TResult>, object?, TNewResult> continuationFunction, object? state,
TaskScheduler scheduler, CancellationToken cancellationToken, TaskContinuationOptions continuationOptions)
{
if (continuationFunction == null)
{
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.continuationFunction);
}
if (scheduler == null)
{
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.scheduler);
}
CreationOptionsFromContinuationOptions(
continuationOptions,
out TaskCreationOptions creationOptions,
out InternalTaskOptions internalOptions);
Task<TNewResult> continuationFuture = new ContinuationResultTaskFromResultTask<TResult, TNewResult>(
this, continuationFunction, state,
creationOptions, internalOptions
);
// Register the continuation. If synchronous execution is requested, this may
// actually invoke the continuation before returning.
ContinueWithCore(continuationFuture, scheduler, cancellationToken, continuationOptions);
return continuationFuture;
}
#endregion
#endregion
}
// Proxy class for better debugging experience
internal sealed class SystemThreadingTasks_FutureDebugView<TResult>
{
private readonly Task<TResult> m_task;
public SystemThreadingTasks_FutureDebugView(Task<TResult> task)
{
Debug.Assert(task != null);
m_task = task;
}
public TResult? Result => m_task.Status == TaskStatus.RanToCompletion ? m_task.Result : default;
public object? AsyncState => m_task.AsyncState;
public TaskCreationOptions CreationOptions => m_task.CreationOptions;
public Exception? Exception => m_task.Exception;
public int Id => m_task.Id;
public bool CancellationPending => (m_task.Status == TaskStatus.WaitingToRun) && m_task.CancellationToken.IsCancellationRequested;
public TaskStatus Status => m_task.Status;
}
}
|