CancellationTokenRegistration.cs 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170
  1. // Licensed to the .NET Foundation under one or more agreements.
  2. // The .NET Foundation licenses this file to you under the MIT license.
  3. // See the LICENSE file in the project root for more information.
  4. using System.Threading.Tasks;
  5. namespace System.Threading
  6. {
  7. /// <summary>
  8. /// Represents a callback delegate that has been registered with a <see cref="T:System.Threading.CancellationToken">CancellationToken</see>.
  9. /// </summary>
  10. /// <remarks>
  11. /// To unregister a callback, dispose the corresponding Registration instance.
  12. /// </remarks>
  13. public readonly struct CancellationTokenRegistration : IEquatable<CancellationTokenRegistration>, IDisposable, IAsyncDisposable
  14. {
  15. private readonly long _id;
  16. private readonly CancellationTokenSource.CallbackNode _node;
  17. internal CancellationTokenRegistration(long id, CancellationTokenSource.CallbackNode node)
  18. {
  19. _id = id;
  20. _node = node;
  21. }
  22. /// <summary>
  23. /// Disposes of the registration and unregisters the target callback from the associated
  24. /// <see cref="T:System.Threading.CancellationToken">CancellationToken</see>.
  25. /// If the target callback is currently executing, this method will wait until it completes, except
  26. /// in the degenerate cases where a callback method unregisters itself.
  27. /// </summary>
  28. public void Dispose()
  29. {
  30. CancellationTokenSource.CallbackNode node = _node;
  31. if (node != null && !node.Partition.Unregister(_id, node))
  32. {
  33. WaitForCallbackIfNecessary();
  34. }
  35. }
  36. /// <summary>
  37. /// Disposes of the registration and unregisters the target callback from the associated
  38. /// <see cref="T:System.Threading.CancellationToken">CancellationToken</see>.
  39. /// The returned <see cref="ValueTask"/> will complete once the associated callback
  40. /// is unregistered without having executed or once it's finished executing, except
  41. /// in the degenerate case where the callback itself is unregistering itself.
  42. /// </summary>
  43. public ValueTask DisposeAsync()
  44. {
  45. CancellationTokenSource.CallbackNode node = _node;
  46. return node != null && !node.Partition.Unregister(_id, node) ?
  47. WaitForCallbackIfNecessaryAsync() :
  48. default;
  49. }
  50. /// <summary>
  51. /// Gets the <see cref="CancellationToken"/> with which this registration is associated. If the
  52. /// registration isn't associated with a token (such as after the registration has been disposed),
  53. /// this will return a default token.
  54. /// </summary>
  55. public CancellationToken Token
  56. {
  57. get
  58. {
  59. CancellationTokenSource.CallbackNode node = _node;
  60. return node != null ?
  61. new CancellationToken(node.Partition.Source) : // avoid CTS.Token, which throws after disposal
  62. default;
  63. }
  64. }
  65. /// <summary>
  66. /// Disposes of the registration and unregisters the target callback from the associated
  67. /// <see cref="T:System.Threading.CancellationToken">CancellationToken</see>.
  68. /// </summary>
  69. public bool Unregister()
  70. {
  71. CancellationTokenSource.CallbackNode node = _node;
  72. return node != null && node.Partition.Unregister(_id, node);
  73. }
  74. private void WaitForCallbackIfNecessary()
  75. {
  76. // We're a valid registration but we were unable to unregister, which means the callback wasn't in the list,
  77. // which means either it already executed or it's currently executing. We guarantee that we will not return
  78. // if the callback is being executed (assuming we are not currently called by the callback itself)
  79. // We achieve this by the following rules:
  80. // 1. If we are called in the context of an executing callback, no need to wait (determined by tracking callback-executor threadID)
  81. // - if the currently executing callback is this CTR, then waiting would deadlock. (We choose to return rather than deadlock)
  82. // - if not, then this CTR cannot be the one executing, hence no need to wait
  83. // 2. If unregistration failed, and we are on a different thread, then the callback may be running under control of cts.Cancel()
  84. // => poll until cts.ExecutingCallback is not the one we are trying to unregister.
  85. CancellationTokenSource source = _node.Partition.Source;
  86. if (source.IsCancellationRequested && // Running callbacks has commenced.
  87. !source.IsCancellationCompleted && // Running callbacks hasn't finished.
  88. source.ThreadIDExecutingCallbacks != Environment.CurrentManagedThreadId) // The executing thread ID is not this thread's ID.
  89. {
  90. // Callback execution is in progress, the executing thread is different from this thread and has taken the callback for execution
  91. // so observe and wait until this target callback is no longer the executing callback.
  92. source.WaitForCallbackToComplete(_id);
  93. }
  94. }
  95. private ValueTask WaitForCallbackIfNecessaryAsync()
  96. {
  97. // Same as WaitForCallbackIfNecessary, except returning a task that'll be completed when callbacks complete.
  98. CancellationTokenSource source = _node.Partition.Source;
  99. if (source.IsCancellationRequested && // Running callbacks has commenced.
  100. !source.IsCancellationCompleted && // Running callbacks hasn't finished.
  101. source.ThreadIDExecutingCallbacks != Environment.CurrentManagedThreadId) // The executing thread ID is not this thread's ID.
  102. {
  103. // Callback execution is in progress, the executing thread is different from this thread and has taken the callback for execution
  104. // so get a task that'll complete when this target callback is no longer the executing callback.
  105. return source.WaitForCallbackToCompleteAsync(_id);
  106. }
  107. // Callback is either already completed, won't execute, or the callback itself is calling this.
  108. return default;
  109. }
  110. /// <summary>
  111. /// Determines whether two <see
  112. /// cref="T:System.Threading.CancellationTokenRegistration">CancellationTokenRegistration</see>
  113. /// instances are equal.
  114. /// </summary>
  115. /// <param name="left">The first instance.</param>
  116. /// <param name="right">The second instance.</param>
  117. /// <returns>True if the instances are equal; otherwise, false.</returns>
  118. public static bool operator ==(CancellationTokenRegistration left, CancellationTokenRegistration right) => left.Equals(right);
  119. /// <summary>
  120. /// Determines whether two <see cref="T:System.Threading.CancellationTokenRegistration">CancellationTokenRegistration</see> instances are not equal.
  121. /// </summary>
  122. /// <param name="left">The first instance.</param>
  123. /// <param name="right">The second instance.</param>
  124. /// <returns>True if the instances are not equal; otherwise, false.</returns>
  125. public static bool operator !=(CancellationTokenRegistration left, CancellationTokenRegistration right) => !left.Equals(right);
  126. /// <summary>
  127. /// Determines whether the current <see cref="T:System.Threading.CancellationTokenRegistration">CancellationTokenRegistration</see> instance is equal to the
  128. /// specified <see cref="T:System.Object"/>.
  129. /// </summary>
  130. /// <param name="obj">The other object to which to compare this instance.</param>
  131. /// <returns>True, if both this and <paramref name="obj"/> are equal. False, otherwise.
  132. /// Two <see cref="T:System.Threading.CancellationTokenRegistration">CancellationTokenRegistration</see> instances are equal if
  133. /// they both refer to the output of a single call to the same Register method of a
  134. /// <see cref="T:System.Threading.CancellationToken">CancellationToken</see>.
  135. /// </returns>
  136. public override bool Equals(object obj) => obj is CancellationTokenRegistration && Equals((CancellationTokenRegistration)obj);
  137. /// <summary>
  138. /// Determines whether the current <see cref="T:System.Threading.CancellationToken">CancellationToken</see> instance is equal to the
  139. /// specified <see cref="T:System.Object"/>.
  140. /// </summary>
  141. /// <param name="other">The other <see cref="T:System.Threading.CancellationTokenRegistration">CancellationTokenRegistration</see> to which to compare this instance.</param>
  142. /// <returns>True, if both this and <paramref name="other"/> are equal. False, otherwise.
  143. /// Two <see cref="T:System.Threading.CancellationTokenRegistration">CancellationTokenRegistration</see> instances are equal if
  144. /// they both refer to the output of a single call to the same Register method of a
  145. /// <see cref="T:System.Threading.CancellationToken">CancellationToken</see>.
  146. /// </returns>
  147. public bool Equals(CancellationTokenRegistration other) => _node == other._node && _id == other._id;
  148. /// <summary>
  149. /// Serves as a hash function for a <see cref="T:System.Threading.CancellationTokenRegistration">CancellationTokenRegistration.</see>.
  150. /// </summary>
  151. /// <returns>A hash code for the current <see cref="T:System.Threading.CancellationTokenRegistration">CancellationTokenRegistration</see> instance.</returns>
  152. public override int GetHashCode() => _node != null ? _node.GetHashCode() ^ _id.GetHashCode() : _id.GetHashCode();
  153. }
  154. }