public static Task TimeoutAfter(this Task task, int millisecondsTimeout) { if (task is null) { throw new ArgumentNullException(nameof(task)); } // Short-circuit #1: infinite timeout or task already completed if (task.IsCompleted || (millisecondsTimeout == Timeout.Infinite)) { // Either the task has already completed or timeout will never occur. // No proxy necessary. return(task); } // tcs.Task will be returned as a proxy to the caller var tcs = new TaskCompletionSource <VoidTypeStruct>(); // Short-circuit #2: zero timeout if (millisecondsTimeout == 0) { // We've already timed out. tcs.SetException(new TimeoutException()); return(tcs.Task); } // Set up a timer to complete after the specified timeout period var timer = new Timer( callback: state => ((TaskCompletionSource <VoidTypeStruct>)state !).TrySetException(new TimeoutException()), state: tcs, dueTime: millisecondsTimeout, period: Timeout.Infinite); // Wire up the logic for what happens when source task completes _ = task.ContinueWith( (antecedent, state) => { // Recover our state data #pragma warning disable CS8600 // Converting null literal or possible null value to non-nullable type. var tuple = (Tuple <Timer, TaskCompletionSource <VoidTypeStruct> >)state; #pragma warning restore CS8600 // Converting null literal or possible null value to non-nullable type. // Cancel the Timer tuple !.Item1.Dispose(); // Marshal results to proxy MarshalTaskResults(antecedent, tuple.Item2); }, Tuple.Create(timer, tcs), CancellationToken.None, TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default); return(tcs.Task); }
/// <inheritdoc /> public void Dispose() { Item1.Dispose(); Item2.Dispose(); }