public async Task DisposeAfterPartialEnumeration()
        {
            // ARRANGE

            var testDisposable = new TestDisposable();
            var enumerator     = new AsyncEnumerator <int>(async yield =>
            {
                using (testDisposable)
                {
                    await yield.ReturnAsync(1);
                    await yield.ReturnAsync(2);
                    await yield.ReturnAsync(3);
                }
            });

            // ACT

            await enumerator.MoveNextAsync();

            enumerator.Dispose();

            // ASSERT

            Assert.IsTrue(testDisposable.HasDisposed);
        }
        public async Task DisposeByGCAfterPartialEnumeration()
        {
            // ARRANGE

            var testDisposable = new TestDisposable();

            var enumerator = new AsyncEnumerator <int>(async yield =>
            {
                using (testDisposable)
                {
                    await yield.ReturnAsync(1);
                    await yield.ReturnAsync(2);
                    await yield.ReturnAsync(3);
                }
            });

            // ACT

            // Do partial enumeration.
            await enumerator.MoveNextAsync();

            // Instead of calling enumerator.Dispose(), do garbage collection.
            enumerator = null;
            GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced, blocking: true);

            // Give some time to other thread that does the disposal of the enumerator.
            // (see finalizer of the AsyncEnumerator for details)
            await Task.Delay(16);

            // ASSERT

            Assert.IsTrue(testDisposable.HasDisposed);
        }
        public async Task YieldBreak()
        {
            // ARRANGE

            var asyncEnumerationCanceledExceptionRaised = false;

            var enumerator = new AsyncEnumerator <int>(async yield =>
            {
                try
                {
                    yield.Break();
                }
                catch (AsyncEnumerationCanceledException)
                {
                    asyncEnumerationCanceledExceptionRaised = true;
                }

                await yield.ReturnAsync(1);
            });

            // ACT

            var result = await enumerator.MoveNextAsync();

            await Task.Yield();

            // ASSERT

            Assert.IsFalse(result, "MoveNextAsync must return False due to Yield.Break");
            Assert.IsTrue(asyncEnumerationCanceledExceptionRaised, "Yield.Break must throw AsyncEnumerationCanceledException so the enumerator body can perform finalization");
        }
        public async Task RaceConditionOnEndOfEnumeration()
        {
            var enumerator = new AsyncEnumerator <int>(async yield =>
            {
                await Task.Run(async() =>
                {
                    await yield.ReturnAsync(1);
                });
            });

            var moveResult1 = await enumerator.MoveNextAsync();

            var moveResult2 = await enumerator.MoveNextAsync();

            var moveResult3 = await enumerator.MoveNextAsync();

            Assert.IsTrue(moveResult1);
            Assert.IsFalse(moveResult2);
            Assert.IsFalse(moveResult3);
        }
        public void CancelEnumeration()
        {
            var cts = new CancellationTokenSource();

            var enumerator = new AsyncEnumerator <int>(yield =>
            {
                cts.Cancel();
                yield.CancellationToken.ThrowIfCancellationRequested();
                return(Task.FromResult(0));
            });

            Assert.ThrowsAsync <OperationCanceledException>(() => enumerator.MoveNextAsync(cts.Token));
        }
        public async Task EnumerationMustEndAfterDispose()
        {
            // ARRANGE

            var enumerator = new AsyncEnumerator <int>(async yield =>
            {
                await yield.ReturnAsync(1);
                await yield.ReturnAsync(2);
            });

            await enumerator.MoveNextAsync();

            // ACT

            enumerator.Dispose();
            bool moveNextResult = await enumerator.MoveNextAsync();

            int currentElement = enumerator.Current;

            // ASSERT

            Assert.IsFalse(moveNextResult, "MoveNextAsync must return False after Dispose");
            Assert.AreEqual(1, currentElement, "Current must not change after Dispose");
        }
        public async Task MeasureEnumerationTime()
        {
            var iterations = 1000000;
            var enumerator = new AsyncEnumerator <int>(async yield =>
            {
                for (int i = 0; i < iterations; i++)
                {
                    await yield.ReturnAsync(1);
                }
            });

            var sw  = Stopwatch.StartNew();
            int sum = 0;

            while (await enumerator.MoveNextAsync())
            {
                sum += enumerator.Current;
            }

            var time = sw.Elapsed;

            Console.WriteLine($"Time taken: {time},   Sum: {sum}");



            sw  = Stopwatch.StartNew();
            sum = 0;

            foreach (var number in EnumerateNumbers())
            {
                sum += number;
            }

            time = sw.Elapsed;
            Console.WriteLine($"Time taken: {time},   Sum: {sum}");



            sw  = Stopwatch.StartNew();
            sum = 0;

            int _lock = 0;

            for (int i = 0; i < iterations; i++)
            {
                Interlocked.CompareExchange(ref _lock, 1, 0);
                var tcs = new TaskCompletionSource <int>();
                tcs.TrySetResult(1);
                sum += await tcs.Task;
                //await Task.Yield();
                //await Task.Yield();
                Interlocked.Exchange(ref _lock, 0);
            }

            time = sw.Elapsed;
            Console.WriteLine($"Time taken: {time},   Sum: {sum}");



            sw  = Stopwatch.StartNew();
            sum = 0;

            var t = Task.FromResult(1);

            for (int i = 0; i < iterations; i++)
            {
                sum += await t;
            }

            time = sw.Elapsed;
            Console.WriteLine($"Time taken: {time},   Sum: {sum}");
        }