You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
129 lines
4.6 KiB
129 lines
4.6 KiB
8 months ago
|
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
|
||
|
|
||
|
using System;
|
||
|
using System.Threading;
|
||
|
|
||
|
namespace Cysharp.Threading.Tasks
|
||
|
{
|
||
|
// CancellationTokenSource itself can not reuse but CancelAfter(Timeout.InfiniteTimeSpan) allows reuse if did not reach timeout.
|
||
|
// Similar discussion:
|
||
|
// https://github.com/dotnet/runtime/issues/4694
|
||
|
// https://github.com/dotnet/runtime/issues/48492
|
||
|
// This TimeoutController emulate similar implementation, using CancelAfterSlim; to achieve zero allocation timeout.
|
||
|
|
||
|
public sealed class TimeoutController : IDisposable
|
||
|
{
|
||
|
readonly static Action<object> CancelCancellationTokenSourceStateDelegate = new Action<object>(CancelCancellationTokenSourceState);
|
||
|
|
||
|
static void CancelCancellationTokenSourceState(object state)
|
||
|
{
|
||
|
var cts = (CancellationTokenSource)state;
|
||
|
cts.Cancel();
|
||
|
}
|
||
|
|
||
|
CancellationTokenSource timeoutSource;
|
||
|
CancellationTokenSource linkedSource;
|
||
|
PlayerLoopTimer timer;
|
||
|
bool isDisposed;
|
||
|
|
||
|
readonly DelayType delayType;
|
||
|
readonly PlayerLoopTiming delayTiming;
|
||
|
readonly CancellationTokenSource originalLinkCancellationTokenSource;
|
||
|
|
||
|
public TimeoutController(DelayType delayType = DelayType.DeltaTime, PlayerLoopTiming delayTiming = PlayerLoopTiming.Update)
|
||
|
{
|
||
|
this.timeoutSource = new CancellationTokenSource();
|
||
|
this.originalLinkCancellationTokenSource = null;
|
||
|
this.linkedSource = null;
|
||
|
this.delayType = delayType;
|
||
|
this.delayTiming = delayTiming;
|
||
|
}
|
||
|
|
||
|
public TimeoutController(CancellationTokenSource linkCancellationTokenSource, DelayType delayType = DelayType.DeltaTime, PlayerLoopTiming delayTiming = PlayerLoopTiming.Update)
|
||
|
{
|
||
|
this.timeoutSource = new CancellationTokenSource();
|
||
|
this.originalLinkCancellationTokenSource = linkCancellationTokenSource;
|
||
|
this.linkedSource = CancellationTokenSource.CreateLinkedTokenSource(timeoutSource.Token, linkCancellationTokenSource.Token);
|
||
|
this.delayType = delayType;
|
||
|
this.delayTiming = delayTiming;
|
||
|
}
|
||
|
|
||
|
public CancellationToken Timeout(int millisecondsTimeout)
|
||
|
{
|
||
|
return Timeout(TimeSpan.FromMilliseconds(millisecondsTimeout));
|
||
|
}
|
||
|
|
||
|
public CancellationToken Timeout(TimeSpan timeout)
|
||
|
{
|
||
|
if (originalLinkCancellationTokenSource != null && originalLinkCancellationTokenSource.IsCancellationRequested)
|
||
|
{
|
||
|
return originalLinkCancellationTokenSource.Token;
|
||
|
}
|
||
|
|
||
|
// Timeouted, create new source and timer.
|
||
|
if (timeoutSource.IsCancellationRequested)
|
||
|
{
|
||
|
timeoutSource.Dispose();
|
||
|
timeoutSource = new CancellationTokenSource();
|
||
|
if (linkedSource != null)
|
||
|
{
|
||
|
this.linkedSource.Cancel();
|
||
|
this.linkedSource.Dispose();
|
||
|
this.linkedSource = CancellationTokenSource.CreateLinkedTokenSource(timeoutSource.Token, originalLinkCancellationTokenSource.Token);
|
||
|
}
|
||
|
|
||
|
timer?.Dispose();
|
||
|
timer = null;
|
||
|
}
|
||
|
|
||
|
var useSource = (linkedSource != null) ? linkedSource : timeoutSource;
|
||
|
var token = useSource.Token;
|
||
|
if (timer == null)
|
||
|
{
|
||
|
// Timer complete => timeoutSource.Cancel() -> linkedSource will be canceled.
|
||
|
// (linked)token is canceled => stop timer
|
||
|
timer = PlayerLoopTimer.StartNew(timeout, false, delayType, delayTiming, token, CancelCancellationTokenSourceStateDelegate, timeoutSource);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
timer.Restart(timeout);
|
||
|
}
|
||
|
|
||
|
return token;
|
||
|
}
|
||
|
|
||
|
public bool IsTimeout()
|
||
|
{
|
||
|
return timeoutSource.IsCancellationRequested;
|
||
|
}
|
||
|
|
||
|
public void Reset()
|
||
|
{
|
||
|
timer?.Stop();
|
||
|
}
|
||
|
|
||
|
public void Dispose()
|
||
|
{
|
||
|
if (isDisposed) return;
|
||
|
|
||
|
try
|
||
|
{
|
||
|
// stop timer.
|
||
|
timer?.Dispose();
|
||
|
|
||
|
// cancel and dispose.
|
||
|
timeoutSource.Cancel();
|
||
|
timeoutSource.Dispose();
|
||
|
if (linkedSource != null)
|
||
|
{
|
||
|
linkedSource.Cancel();
|
||
|
linkedSource.Dispose();
|
||
|
}
|
||
|
}
|
||
|
finally
|
||
|
{
|
||
|
isDisposed = true;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|