/// <summary> /// It's recommended to use this method (or another TryAwaitOrTimeout or AwaitOrTimeout method) /// instead of just Task.WhenAny(taskToAwait, Task.Delay(timeout)) /// because this method cancels the timer for timeout while <c>Task.Delay(timeout)</c>. /// If the number of “zombie” timer jobs starts becoming significant, performance could suffer. /// For more detailed explanation see https://devblogs.microsoft.com/pfxteam/crafting-a-task-timeoutafter-method/ /// </summary> /// <returns><c>true</c> if <c>taskToAwait</c> completed before the timeout, <c>false</c> otherwise</returns> internal static async Task <bool> TryAwaitOrTimeout(this IAgentTimer agentTimer, Task taskToAwait , AgentTimeInstant until, CancellationToken cancellationToken = default ) { var timeoutDelayCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); var timeoutDelayTask = agentTimer.Delay(until, timeoutDelayCts.Token); try { var completedTask = await Task.WhenAny(taskToAwait, timeoutDelayTask); if (completedTask == taskToAwait) { // await taskToAwait to make it throw if taskToAwait is faulted or cancelled so it will throw as it should await taskToAwait; return(true); } Assertion.IfEnabled?.That(completedTask == timeoutDelayTask , $"{nameof(completedTask)}: {completedTask}, {nameof(timeoutDelayTask)}: timeOutTask, {nameof(taskToAwait)}: taskToAwait"); // await timeout task in case it is cancelled and did not timed out so it will throw as it should await timeoutDelayTask; return(false); } finally { if (!timeoutDelayTask.IsCompleted) { timeoutDelayCts.Cancel(); } timeoutDelayCts.Dispose(); } }
private static void VerifyInstantsAreCompatible(AgentTimeInstant i1, AgentTimeInstant i2, [CallerMemberName] string caller = null) { if (i1.IsCompatibleWith(i2._sourceAgentTimer)) { return; } var opName = caller == null ? "an operation" : $"operation {caller}"; throw new InvalidOperationException($"It's illegal to perform {opName} on two AgentTimeInstant instances " + "that did not originate from the same timer." + $" The first AgentTimeInstant: timer: {i1._sourceAgentTimer}, value: {i1._elapsedSinceTimerStarted}." + $" The second AgentTimeInstant: timer: {i2._sourceAgentTimer}, value: {i2._elapsedSinceTimerStarted}."); }
public bool Equals(AgentTimeInstant other) => _sourceAgentTimer == other._sourceAgentTimer && _elapsedSinceTimerStarted == other._elapsedSinceTimerStarted;
/// <summary> /// It's recommended to use this method (or another TryAwaitOrTimeout or AwaitOrTimeout method) /// instead of just Task.WhenAny(taskToAwait, Task.Delay(timeout)) /// because this method cancels the timer for timeout while <c>Task.Delay(timeout)</c>. /// If the number of “zombie” timer jobs starts becoming significant, performance could suffer. /// For more detailed explanation see https://devblogs.microsoft.com/pfxteam/crafting-a-task-timeoutafter-method/ /// </summary> /// <returns> /// (<c>true</c>, result of <c>taskToAwait</c>) if <c>taskToAwait</c> completed before the timeout, <c>false</c> /// otherwise /// </returns> internal static async Task <ValueTuple <bool, TResult> > TryAwaitOrTimeout <TResult>(this IAgentTimer agentTimer, Task <TResult> taskToAwait , AgentTimeInstant until, CancellationToken cancellationToken = default ) { var hasTaskToAwaitCompletedBeforeTimeout = await TryAwaitOrTimeout(agentTimer, (Task)taskToAwait, until, cancellationToken); return(hasTaskToAwaitCompletedBeforeTimeout, hasTaskToAwaitCompletedBeforeTimeout ? await taskToAwait : default);