TimeoutController.cs 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129
  1. #pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
  2. using System;
  3. using System.Threading;
  4. namespace Cysharp.Threading.Tasks
  5. {
  6. // CancellationTokenSource itself can not reuse but CancelAfter(Timeout.InfiniteTimeSpan) allows reuse if did not reach timeout.
  7. // Similar discussion:
  8. // https://github.com/dotnet/runtime/issues/4694
  9. // https://github.com/dotnet/runtime/issues/48492
  10. // This TimeoutController emulate similar implementation, using CancelAfterSlim; to achieve zero allocation timeout.
  11. public sealed class TimeoutController : IDisposable
  12. {
  13. readonly static Action<object> CancelCancellationTokenSourceStateDelegate = new Action<object>(CancelCancellationTokenSourceState);
  14. static void CancelCancellationTokenSourceState(object state)
  15. {
  16. var cts = (CancellationTokenSource)state;
  17. cts.Cancel();
  18. }
  19. CancellationTokenSource timeoutSource;
  20. CancellationTokenSource linkedSource;
  21. PlayerLoopTimer timer;
  22. bool isDisposed;
  23. readonly DelayType delayType;
  24. readonly PlayerLoopTiming delayTiming;
  25. readonly CancellationTokenSource originalLinkCancellationTokenSource;
  26. public TimeoutController(DelayType delayType = DelayType.DeltaTime, PlayerLoopTiming delayTiming = PlayerLoopTiming.Update)
  27. {
  28. this.timeoutSource = new CancellationTokenSource();
  29. this.originalLinkCancellationTokenSource = null;
  30. this.linkedSource = null;
  31. this.delayType = delayType;
  32. this.delayTiming = delayTiming;
  33. }
  34. public TimeoutController(CancellationTokenSource linkCancellationTokenSource, DelayType delayType = DelayType.DeltaTime, PlayerLoopTiming delayTiming = PlayerLoopTiming.Update)
  35. {
  36. this.timeoutSource = new CancellationTokenSource();
  37. this.originalLinkCancellationTokenSource = linkCancellationTokenSource;
  38. this.linkedSource = CancellationTokenSource.CreateLinkedTokenSource(timeoutSource.Token, linkCancellationTokenSource.Token);
  39. this.delayType = delayType;
  40. this.delayTiming = delayTiming;
  41. }
  42. public CancellationToken Timeout(int millisecondsTimeout)
  43. {
  44. return Timeout(TimeSpan.FromMilliseconds(millisecondsTimeout));
  45. }
  46. public CancellationToken Timeout(TimeSpan timeout)
  47. {
  48. if (originalLinkCancellationTokenSource != null && originalLinkCancellationTokenSource.IsCancellationRequested)
  49. {
  50. return originalLinkCancellationTokenSource.Token;
  51. }
  52. // Timeouted, create new source and timer.
  53. if (timeoutSource.IsCancellationRequested)
  54. {
  55. timeoutSource.Dispose();
  56. timeoutSource = new CancellationTokenSource();
  57. if (linkedSource != null)
  58. {
  59. this.linkedSource.Cancel();
  60. this.linkedSource.Dispose();
  61. this.linkedSource = CancellationTokenSource.CreateLinkedTokenSource(timeoutSource.Token, originalLinkCancellationTokenSource.Token);
  62. }
  63. timer?.Dispose();
  64. timer = null;
  65. }
  66. var useSource = (linkedSource != null) ? linkedSource : timeoutSource;
  67. var token = useSource.Token;
  68. if (timer == null)
  69. {
  70. // Timer complete => timeoutSource.Cancel() -> linkedSource will be canceled.
  71. // (linked)token is canceled => stop timer
  72. timer = PlayerLoopTimer.StartNew(timeout, false, delayType, delayTiming, token, CancelCancellationTokenSourceStateDelegate, timeoutSource);
  73. }
  74. else
  75. {
  76. timer.Restart(timeout);
  77. }
  78. return token;
  79. }
  80. public bool IsTimeout()
  81. {
  82. return timeoutSource.IsCancellationRequested;
  83. }
  84. public void Reset()
  85. {
  86. timer?.Stop();
  87. }
  88. public void Dispose()
  89. {
  90. if (isDisposed) return;
  91. try
  92. {
  93. // stop timer.
  94. timer?.Dispose();
  95. // cancel and dispose.
  96. timeoutSource.Cancel();
  97. timeoutSource.Dispose();
  98. if (linkedSource != null)
  99. {
  100. linkedSource.Cancel();
  101. linkedSource.Dispose();
  102. }
  103. }
  104. finally
  105. {
  106. isDisposed = true;
  107. }
  108. }
  109. }
  110. }