AsyncReturnBenchmark.cs 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164
  1. // Licensed to the .NET Foundation under one or more agreements.
  2. // The .NET Foundation licenses this file to you under the Apache 2.0 License.
  3. // See the LICENSE file in the project root for more information.
  4. using System;
  5. using System.Collections.Generic;
  6. using System.Linq;
  7. using System.Threading;
  8. using System.Threading.Tasks;
  9. using BenchmarkDotNet.Attributes;
  10. namespace Benchmarks.System.Interactive
  11. {
  12. [MemoryDiagnoser]
  13. public class AsyncReturnBenchmark
  14. {
  15. [Benchmark]
  16. public async ValueTask ToAsyncEnumerable()
  17. {
  18. await new[] { 1 }.ToAsyncEnumerable().ForEachAsync(v => { }).ConfigureAwait(false);
  19. }
  20. [Benchmark]
  21. public async ValueTask Direct()
  22. {
  23. await AsyncEnumerableEx.Return(1).ForEachAsync(v => { }).ConfigureAwait(false);
  24. }
  25. [Benchmark]
  26. public async ValueTask Iterator()
  27. {
  28. await new ReturnIterator<int>(1).ForEachAsync(v => { }).ConfigureAwait(false);
  29. }
  30. }
  31. internal sealed class ReturnIterator<T> : AsyncIterator<T>
  32. {
  33. private readonly T _value;
  34. public override AsyncIterator<T> Clone()
  35. {
  36. return new ReturnIterator<T>(_value);
  37. }
  38. public ReturnIterator(T value)
  39. {
  40. _value = value;
  41. }
  42. protected override async ValueTask<bool> MoveNextCore(CancellationToken cancellationToken)
  43. {
  44. if (state == AsyncIteratorState.Allocated)
  45. {
  46. current = _value;
  47. state = AsyncIteratorState.Disposed;
  48. return true;
  49. }
  50. await DisposeAsync().ConfigureAwait(false);
  51. return false;
  52. }
  53. }
  54. internal abstract class AsyncIterator<TSource> : IAsyncEnumerable<TSource>, IAsyncEnumerator<TSource>
  55. {
  56. private readonly int _threadId;
  57. private bool _currentIsInvalid = true;
  58. internal TSource current;
  59. internal AsyncIteratorState state = AsyncIteratorState.New;
  60. internal CancellationToken token;
  61. protected AsyncIterator()
  62. {
  63. _threadId = Environment.CurrentManagedThreadId;
  64. }
  65. public IAsyncEnumerator<TSource> GetAsyncEnumerator(CancellationToken token)
  66. {
  67. var enumerator = state == AsyncIteratorState.New && _threadId == Environment.CurrentManagedThreadId
  68. ? this
  69. : Clone();
  70. enumerator.state = AsyncIteratorState.Allocated;
  71. enumerator.token = token;
  72. try
  73. {
  74. enumerator.OnGetEnumerator(token);
  75. }
  76. catch
  77. {
  78. enumerator.DisposeAsync(); // REVIEW: fire-and-forget?
  79. throw;
  80. }
  81. return enumerator;
  82. }
  83. public virtual ValueTask DisposeAsync()
  84. {
  85. current = default;
  86. state = AsyncIteratorState.Disposed;
  87. return new ValueTask();
  88. }
  89. public TSource Current
  90. {
  91. get
  92. {
  93. if (_currentIsInvalid)
  94. throw new InvalidOperationException("Enumerator is in an invalid state");
  95. return current;
  96. }
  97. }
  98. public async ValueTask<bool> MoveNextAsync()
  99. {
  100. // Note: MoveNext *must* be implemented as an async method to ensure
  101. // that any exceptions thrown from the MoveNextCore call are handled
  102. // by the try/catch, whether they're sync or async
  103. if (state == AsyncIteratorState.Disposed)
  104. {
  105. return false;
  106. }
  107. try
  108. {
  109. var result = await MoveNextCore(token).ConfigureAwait(false);
  110. _currentIsInvalid = !result; // if move next is false, invalid otherwise valid
  111. return result;
  112. }
  113. catch
  114. {
  115. _currentIsInvalid = true;
  116. await DisposeAsync().ConfigureAwait(false);
  117. throw;
  118. }
  119. }
  120. public abstract AsyncIterator<TSource> Clone();
  121. protected abstract ValueTask<bool> MoveNextCore(CancellationToken cancellationToken);
  122. protected virtual void OnGetEnumerator(CancellationToken cancellationToken)
  123. {
  124. }
  125. }
  126. internal enum AsyncIteratorState
  127. {
  128. New = 0,
  129. Allocated = 1,
  130. Iterating = 2,
  131. Disposed = -1,
  132. }
  133. }