public async Task BufferMore(int maxPageSize, int[] pageSizes)
        {
            (ItemProducer itemProducer, ReadOnlyCollection <ToDoItem> allItems)itemFactory = MockItemProducerFactory.Create(
                responseMessagesPageSize: pageSizes,
                maxPageSize: maxPageSize,
                cancellationToken: this.cancellationToken);

            ItemProducer itemProducer     = itemFactory.itemProducer;
            int          currentItemCount = pageSizes[0];

            // Single thread
            for (int iterations = 0; iterations < 10; iterations++)
            {
                // Only first buffer more should go through
                await itemProducer.BufferMoreIfEmptyAsync(this.cancellationToken);

                Assert.AreEqual(currentItemCount, itemProducer.BufferedItemCount);
            }

            for (int roundTrip = 1; roundTrip < pageSizes.Length; roundTrip++)
            {
                await itemProducer.BufferMoreDocumentsAsync(this.cancellationToken);

                currentItemCount += pageSizes[roundTrip];
                Assert.AreEqual(currentItemCount, itemProducer.BufferedItemCount);
            }

            Assert.AreEqual(itemFactory.allItems.Count, itemProducer.BufferedItemCount);

            // Verify that Buffer More does nothing after all pages are already loaded
            for (int roundTrip = 0; roundTrip < pageSizes.Length; roundTrip++)
            {
                await itemProducer.BufferMoreDocumentsAsync(this.cancellationToken);

                Assert.AreEqual(itemFactory.allItems.Count, itemProducer.BufferedItemCount);
            }

            // Parallel
            itemFactory = MockItemProducerFactory.Create(
                responseMessagesPageSize: pageSizes,
                maxPageSize: 10,
                cancellationToken: this.cancellationToken);
            itemProducer = itemFactory.itemProducer;

            List <Task> tasks = new List <Task>();

            for (int i = 0; i < pageSizes.Length; i++)
            {
                tasks.Add(Task.Run(async() => { await itemProducer.BufferMoreDocumentsAsync(this.cancellationToken); }));
            }

            await Task.WhenAll(tasks);

            Assert.AreEqual(currentItemCount, itemProducer.BufferedItemCount);
        }
        public async Task ConcurrentMoveNextAndBufferMore()
        {
            int[] pageSizes = new int[] { 2, 3, 1, 4 };
            (ItemProducer itemProducer, ReadOnlyCollection <ToDoItem> allItems)itemFactory = MockItemProducerFactory.Create(
                responseMessagesPageSize: pageSizes,
                maxPageSize: 10,
                responseDelay: TimeSpan.FromSeconds(1),
                cancellationToken: this.cancellationToken);

            ItemProducer itemProducer = itemFactory.itemProducer;

            // BufferMore
            // Fire and Forget this task.
#pragma warning disable 4014
            Task.Run(() => itemProducer.BufferMoreDocumentsAsync(this.cancellationToken));
#pragma warning restore 4014

            List <ToDoItem> itemsRead = new List <ToDoItem>();
            Assert.AreEqual(0, itemProducer.BufferedItemCount, "Mocked response should be delayed until after move next is called.");

            int itemsToRead = pageSizes[0] + pageSizes[1];
            // Call move next while buffer more is waiting for response.
            // Move next should wait for buffer more to complete then use the results of the buffer more.
            List <ToDoItem> currentPage = await this.ReadItemProducer(itemProducer, itemsToRead);

            itemsRead.AddRange(currentPage);

            Assert.AreEqual(itemsToRead, itemsRead.Count, "All of the first and 2nd page should be read.");
            Assert.AreEqual(1, itemProducer.BufferedItemCount, "The last element should still be buffered. Moving next will cause another buffer.");

            itemsToRead = pageSizes[2] + pageSizes[3];
#pragma warning disable 4014
            Task.Run(() => itemProducer.MoveNextAsync(this.cancellationToken));
#pragma warning restore 4014

            await itemProducer.BufferMoreDocumentsAsync(this.cancellationToken);

            Assert.AreEqual(itemsToRead, itemProducer.BufferedItemCount, "2nd Page should be loaded.");
        }
        public async Task TestMoveNextAsync(int pageSize, int maxPageSize)
        {
            List <int[]> combinations = new List <int[]>()
            {
                // Create all combination with empty pages
                new int[] { pageSize },
                new int[] { pageSize, 0 },
                new int[] { 0, pageSize },
                new int[] { pageSize, pageSize },
                new int[] { pageSize, 0, 0 },
                new int[] { 0, pageSize, 0 },
                new int[] { 0, 0, pageSize },
                new int[] { pageSize, 0, pageSize },
                new int[] { pageSize, pageSize, pageSize },
            };

            foreach (int[] combination in combinations)
            {
                (ItemProducer itemProducer, ReadOnlyCollection <ToDoItem> allItems)itemFactory = MockItemProducerFactory.Create(
                    responseMessagesPageSize: combination,
                    maxPageSize: maxPageSize,
                    cancellationToken: this.cancellationToken);

                ItemProducer itemProducer = itemFactory.itemProducer;

                List <ToDoItem> itemsRead = new List <ToDoItem>();

                Assert.IsTrue(itemProducer.HasMoreResults);

                while ((await itemProducer.MoveNextAsync(this.cancellationToken)).successfullyMovedNext)
                {
                    Assert.IsTrue(itemProducer.HasMoreResults);
                    string   jsonValue = itemProducer.Current.ToString();
                    ToDoItem item      = JsonConvert.DeserializeObject <ToDoItem>(jsonValue);
                    itemsRead.Add(item);
                }

                Assert.IsFalse(itemProducer.HasMoreResults);

                Assert.AreEqual(itemFactory.allItems.Count, itemsRead.Count);

                CollectionAssert.AreEqual(itemsRead, itemFactory.allItems, new ToDoItemComparer());
            }
        }
        public async Task ConcurrentMoveNextAndBufferMore()
        {
            bool   blockExecute  = true;
            int    callBackCount = 0;
            Action callbackBlock = () =>
            {
                int callBackWaitCount = 0;
                callBackCount++;
                while (blockExecute)
                {
                    if (callBackWaitCount++ > 200)
                    {
                        Assert.Fail("The task never started to buffer the items. The callback was never called");
                    }

                    Thread.Sleep(TimeSpan.FromSeconds(.1));
                }

                // Reset the block for the  next call
                blockExecute = true;
            };

            int[] pageSizes = new int[] { 2, 3, 1, 4 };
            (ItemProducer itemProducer, ReadOnlyCollection <ToDoItem> allItems)itemFactory = MockItemProducerFactory.Create(
                responseMessagesPageSize: pageSizes,
                maxPageSize: 10,
                executeCallback: callbackBlock,
                cancellationToken: this.cancellationToken);

            ItemProducer itemProducer = itemFactory.itemProducer;

            // BufferMore
            // Fire and Forget this task.
#pragma warning disable 4014
            Task bufferTask = Task.Run(() => itemProducer.BufferMoreDocumentsAsync(this.cancellationToken));

            // Verify the task started
            int waitCount = 0;
            while (callBackCount == 0)
            {
                if (waitCount++ > 100)
                {
                    Assert.Fail("The task never started to buffer the items. The callback was never called");
                }

                Thread.Sleep(TimeSpan.FromSeconds(.1));
            }

            List <ToDoItem> itemsRead = new List <ToDoItem>();
            Assert.AreEqual(0, itemProducer.BufferedItemCount, "Mocked response should be delayed until after move next is called.");

            int itemsToRead = pageSizes[0] + pageSizes[1];
            // Call move next while buffer more is waiting for response.
            // Move next should wait for buffer more to complete then use the results of the buffer more.
#pragma warning disable 4014
            bool readTaskRunning = false;
            Task readTask        = Task.Run(async() =>
            {
                readTaskRunning             = true;
                List <ToDoItem> currentPage = await this.ReadItemProducer(itemProducer, itemsToRead);
                itemsRead.AddRange(currentPage);
            });
#pragma warning restore 4014

            // Verify the task started
            while (readTaskRunning == false)
            {
                Thread.Sleep(TimeSpan.FromSeconds(.1));
            }

            Assert.AreEqual(0, itemProducer.BufferedItemCount, "The call back block will prevent any item from being buffered");
            Assert.AreEqual(1, callBackCount, "Buffer more should have a lock which prevents multiple executes.");

            // Unblock the buffer task
            blockExecute = false;
            await bufferTask;
            Assert.AreEqual(2, itemProducer.BufferedItemCount, "Buffer should be completed and have 2 items from first page");

            // Unblock the read task
            blockExecute = false;
            await readTask;
            Assert.AreEqual(itemsToRead, itemsRead.Count, "All of the first and 2nd page should be read.");
            Assert.AreEqual(1, itemProducer.BufferedItemCount, "The last element should still be buffered. Moving next will cause another buffer.");

            itemsToRead = pageSizes[2] + pageSizes[3];
#pragma warning disable 4014
            Task moveNext = Task.Run(() => itemProducer.MoveNextAsync(this.cancellationToken));
#pragma warning restore 4014
            while (callBackCount == 2)
            {
                if (waitCount++ > 100)
                {
                    Assert.Fail("The task never started to buffer the items. The callback was never called");
                }

                Thread.Sleep(TimeSpan.FromSeconds(.1));
            }

            bufferTask = Task.Run(() => itemProducer.BufferMoreDocumentsAsync(this.cancellationToken));

            Assert.AreEqual(3, callBackCount, "Buffer more should have a lock which prevents multiple executes.");

            blockExecute = false;
            await moveNext;

            blockExecute = false;
            await bufferTask;
            Assert.AreEqual(itemsToRead, itemProducer.BufferedItemCount, "2nd Page should be loaded.");
        }