AsyncIterator.cs 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103
  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.Collections.Generic;
  5. using System.Threading;
  6. using System.Threading.Tasks;
  7. namespace System.Linq
  8. {
  9. // REVIEW: The base class below was introduced to avoid the overhead of storing a field of type TSource if the
  10. // value of the iterator can trivially be inferred from another field (e.g. in Repeat). It is also used
  11. // by the Defer operator in System.Interactive.Async. For some operators such as Where, Skip, Take, and
  12. // Concat, it could be used to retrieve the value from the underlying enumerator. However, performance
  13. // of this approach is a bit worse in some cases, so we don't go ahead with it for now. One decision to
  14. // make is whether it's okay for Current to throw an exception when MoveNextAsync returns false, e.g.
  15. // by omitting a null check for an enumerator field.
  16. internal abstract partial class AsyncIteratorBase<TSource> : IAsyncEnumerable<TSource>, IAsyncEnumerator<TSource>
  17. {
  18. private readonly int _threadId;
  19. protected AsyncIteratorState _state = AsyncIteratorState.New;
  20. protected CancellationToken _cancellationToken;
  21. protected AsyncIteratorBase()
  22. {
  23. _threadId = Environment.CurrentManagedThreadId;
  24. }
  25. public IAsyncEnumerator<TSource> GetAsyncEnumerator(CancellationToken cancellationToken)
  26. {
  27. var enumerator = _state == AsyncIteratorState.New && _threadId == Environment.CurrentManagedThreadId
  28. ? this
  29. : Clone();
  30. enumerator._state = AsyncIteratorState.Allocated;
  31. enumerator._cancellationToken = cancellationToken;
  32. // REVIEW: If the final interface contains a CancellationToken here, should we check for a cancellation request
  33. // either here or in the first call to MoveNextAsync?
  34. return enumerator;
  35. }
  36. public virtual ValueTask DisposeAsync()
  37. {
  38. _state = AsyncIteratorState.Disposed;
  39. return default;
  40. }
  41. public abstract TSource Current { get; }
  42. public async ValueTask<bool> MoveNextAsync()
  43. {
  44. // Note: MoveNext *must* be implemented as an async method to ensure
  45. // that any exceptions thrown from the MoveNextCore call are handled
  46. // by the try/catch, whether they're sync or async
  47. if (_state == AsyncIteratorState.Disposed)
  48. {
  49. return false;
  50. }
  51. try
  52. {
  53. return await MoveNextCore().ConfigureAwait(false);
  54. }
  55. catch
  56. {
  57. await DisposeAsync().ConfigureAwait(false);
  58. throw;
  59. }
  60. }
  61. public abstract AsyncIteratorBase<TSource> Clone();
  62. protected abstract ValueTask<bool> MoveNextCore();
  63. }
  64. internal abstract class AsyncIterator<TSource> : AsyncIteratorBase<TSource>
  65. {
  66. protected TSource _current;
  67. public override TSource Current => _current;
  68. public override ValueTask DisposeAsync()
  69. {
  70. _current = default;
  71. return base.DisposeAsync();
  72. }
  73. }
  74. internal enum AsyncIteratorState
  75. {
  76. New = 0,
  77. Allocated = 1,
  78. Iterating = 2,
  79. Disposed = -1,
  80. }
  81. }