|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Shared.TestHooks;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.Editor.Shared.Utilities;
internal class ResettableDelay
{
public static readonly ResettableDelay CompletedDelay = new();
private readonly int _delayInMilliseconds;
private readonly TaskCompletionSource<object?> _taskCompletionSource = new();
private int _lastSetTime;
/// <summary>
/// Create a ResettableDelay that will complete a task after a certain duration. The delay
/// can be reset at any point before it elapses in which case completion is postponed. The
/// delay can be reset multiple times.
/// </summary>
/// <param name="delayInMilliseconds">The time to delay before completing the task</param>
public ResettableDelay(int delayInMilliseconds, IExpeditableDelaySource expeditableDelaySource, CancellationToken cancellationToken = default)
{
Contract.ThrowIfFalse(delayInMilliseconds >= 50, "Perf, only use delays >= 50ms");
_delayInMilliseconds = delayInMilliseconds;
Reset();
_ = StartTimerAsync(expeditableDelaySource, cancellationToken);
}
private ResettableDelay()
{
// create resettableDelay with completed state
_delayInMilliseconds = 0;
_taskCompletionSource = new TaskCompletionSource<object?>();
_taskCompletionSource.SetResult(null);
Reset();
}
public Task Task => _taskCompletionSource.Task;
public void Reset()
{
// Note: Environment.TickCount - this.lastSetTime is safe in the presence of overflow, but most
// other operations are not.
_lastSetTime = Environment.TickCount;
}
private async Task StartTimerAsync(IExpeditableDelaySource expeditableDelaySource, CancellationToken cancellationToken)
{
try
{
do
{
// Keep delaying until at least delayInMilliseconds has elapsed since lastSetTime
if (!await expeditableDelaySource.Delay(TimeSpan.FromMilliseconds(_delayInMilliseconds), cancellationToken).ConfigureAwait(false))
{
// The operation is being expedited.
break;
}
}
while (Environment.TickCount - _lastSetTime < _delayInMilliseconds);
_taskCompletionSource.SetResult(null);
}
catch (OperationCanceledException)
{
// Calling the "Try" variant because that's the only one that accepts the token to associate with the task
_taskCompletionSource.TrySetCanceled(cancellationToken);
}
}
}
|