AsyncGateExtensions.cs 2.3 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243
  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.Reactive.Threading;
  6. /// <summary>
  7. /// Extension methods for <see cref="IAsyncGate"/>.
  8. /// </summary>
  9. public static class AsyncGateExtensions
  10. {
  11. /// <summary>
  12. /// Acquires an <see cref="IAsyncGate"/> in a way enables the gate to be released with a <see langword="using" />
  13. /// statement or declaration.
  14. /// </summary>
  15. /// <param name="gate">The gate to lock.</param>
  16. /// <returns>
  17. /// A <see cref="ValueTask{TResult}"/> that produces a <see cref="DisposableGateReleaser"/> that will call
  18. /// <see cref="IAsyncGateReleaser.Release"/> when disposed.
  19. /// </returns>
  20. public static ValueTask<DisposableGateReleaser> LockAsync(this IAsyncGate gate)
  21. {
  22. // Note, we are avoiding async/await here because we MUST NOT create a new child ExecutionContext
  23. // (The AsyncGate.LockAsync method does not use async/await either, and for the same reason.)
  24. //
  25. // IAsyncGate implementations are allowed to require that their LockAsync method is called from the same
  26. // execution context as Release will be called. For example, AsyncGate uses an AsyncLocal<int> to track
  27. // the recursion count, and when you update an AsyncLocal<T>'s value, that modified value is visible only
  28. // in the current ExecutionContext and its descendants. An async method effectively introduces a new child
  29. // context, so any AsyncLocal<T> value changes are lost when an async method returns, but we need the
  30. // recursion count to live in our caller's context, which is why we must make sure we don't introduce a
  31. // new child context here. That's why this needs to be old-school manual task management, and not async/await.
  32. ValueTask<IAsyncGateReleaser> releaserValueTask = gate.AcquireAsync();
  33. if (releaserValueTask.IsCompleted)
  34. {
  35. return new ValueTask<DisposableGateReleaser>(new DisposableGateReleaser(releaserValueTask.Result));
  36. }
  37. return new ValueTask<DisposableGateReleaser>(releaserValueTask.AsTask().ContinueWith(t => new DisposableGateReleaser(t.Result)));
  38. }
  39. }