Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ public class IncrementalLoadingCollection<TSource, IType> : ObservableCollection
ISupportIncrementalLoading
where TSource : Collections.IIncrementalSource<IType>
{
private readonly SemaphoreSlim _mutex = new SemaphoreSlim(1);

/// <summary>
/// Gets or sets an <see cref="Action"/> that is called when a retrieval operation begins.
/// </summary>
Expand Down Expand Up @@ -226,7 +228,23 @@ public Task RefreshAsync()
/// </returns>
protected virtual async Task<IEnumerable<IType>> LoadDataAsync(CancellationToken cancellationToken)
{
var result = await Source.GetPagedItemsAsync(CurrentPageIndex++, ItemsPerPage, cancellationToken);
var result = await Source.GetPagedItemsAsync(CurrentPageIndex, ItemsPerPage, cancellationToken)
.ContinueWith(
t =>
{
if(t.IsFaulted)
{
throw t.Exception;
}

if (t.IsCompletedSuccessfully)
{
CurrentPageIndex += 1;
}

return t.Result;
}, cancellationToken);

return result;
}

Expand All @@ -235,6 +253,9 @@ private async Task<LoadMoreItemsResult> LoadMoreItemsAsync(uint count, Cancellat
uint resultCount = 0;
_cancellationToken = cancellationToken;

// TODO (2021.05.05): Make use common AsyncMutex class.
// AsyncMutex is located at Microsoft.Toolkit.Uwp.UI.Media/Extensions/System.Threading.Tasks/AsyncMutex.cs at the time of this note.
await _mutex.WaitAsync();
try
{
if (!_cancellationToken.IsCancellationRequested)
Expand Down Expand Up @@ -278,6 +299,8 @@ private async Task<LoadMoreItemsResult> LoadMoreItemsAsync(uint count, Cancellat
_refreshOnLoad = false;
await RefreshAsync();
}

_mutex.Release();
}

return new LoadMoreItemsResult { Count = resultCount };
Expand Down
59 changes: 59 additions & 0 deletions UnitTests/UnitTests.UWP/UI/Collection/DataSource.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using Microsoft.Toolkit.Collections;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;

namespace UnitTests.UI
{
public class DataSource<T> : IIncrementalSource<T>
{
private readonly IEnumerable<T> items;
private readonly Queue<PageOperation> pageRequestOperations;

public delegate IEnumerable<T> PageOperation(IEnumerable<T> page);

public DataSource(IEnumerable<T> items, IEnumerable<PageOperation> pageOps)
: this(items, new Queue<PageOperation>(pageOps))
{
}

public DataSource(IEnumerable<T> items, params PageOperation[] pageOps)
: this(items, new Queue<PageOperation>(pageOps))
{
}

public DataSource(IEnumerable<T> items, Queue<PageOperation> pageOps = default)
{
this.items = items ?? throw new ArgumentNullException(nameof(items));
this.pageRequestOperations = pageOps ?? new Queue<PageOperation>();
}

public static PageOperation MakeDelayOp(int delay)
=> new (page =>
{
Thread.Sleep(delay);
return page;
});

public static IEnumerable<T> ThrowException(IEnumerable<T> page) => throw new Exception();

public static IEnumerable<T> PassThrough(IEnumerable<T> page) => page;

public async Task<IEnumerable<T>> GetPagedItemsAsync(int pageIndex, int pageSize, CancellationToken cancellationToken = default)
{
// Gets items from the collection according to pageIndex and pageSize parameters.
var result = (from p in items
select p).Skip(pageIndex * pageSize).Take(pageSize);

return this.pageRequestOperations.TryDequeue(out var op)
? await Task.Factory.StartNew(new Func<object, IEnumerable<T>>(o => op(o as IEnumerable<T>)), state: result)
: result;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Toolkit.Uwp;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace UnitTests.UI
{
[TestClass]
public class Test_IncrementalLoadingCollection
{
private const int PageSize = 20;
private const int Pages = 5;

private static readonly DataSource<int>.PageOperation[] FailPassSequence
= new DataSource<int>.PageOperation[]
{
DataSource<int>.ThrowException, DataSource<int>.PassThrough,
DataSource<int>.ThrowException, DataSource<int>.PassThrough,
DataSource<int>.ThrowException, DataSource<int>.PassThrough,
DataSource<int>.ThrowException, DataSource<int>.PassThrough,
DataSource<int>.ThrowException, DataSource<int>.PassThrough,
};

private static readonly int[] AllData
= Enumerable.Range(0, Pages * PageSize).ToArray();

[DataRow]
[DataRow(2500, 1000, 1000, 1000, 1000)]
[TestMethod]
public async Task Requests(params int[] pageDelays)
{
var source = new DataSource<int>(AllData, pageDelays.Select(DataSource<int>.MakeDelayOp));
var collection = new IncrementalLoadingCollection<DataSource<int>, int>(source, PageSize);

for (int pageNum = 1; pageNum <= Pages; pageNum++)
{
await collection.LoadMoreItemsAsync(0);
CollectionAssert.AreEqual(Enumerable.Range(0, PageSize * pageNum).ToArray(), collection);
}
}

[DataRow]
[DataRow(2500, 1000, 1000, 1000, 1000)]
[TestMethod]
public async Task RequestsAsync(params int[] pageDelays)
{
var source = new DataSource<int>(AllData, pageDelays.Select(DataSource<int>.MakeDelayOp));
var collection = new IncrementalLoadingCollection<DataSource<int>, int>(source, PageSize);

var requests = new List<Task>();

for (int pageNum = 1; pageNum <= Pages; pageNum++)
{
requests.Add(collection.LoadMoreItemsAsync(0).AsTask()
.ContinueWith(t => Assert.IsTrue(t.IsCompletedSuccessfully)));
}

await Task.WhenAll(requests);

CollectionAssert.AreEqual(AllData, collection);
}

[TestMethod]
public async Task FirstRequestFails()
{
var source = new DataSource<int>(AllData, DataSource<int>.ThrowException);
var collection = new IncrementalLoadingCollection<DataSource<int>, int>(source, PageSize);

await Assert.ThrowsExceptionAsync<AggregateException>(collection.LoadMoreItemsAsync(0).AsTask);

Assert.IsTrue(!collection.Any());

var requests = new List<Task>();

for (int pageNum = 1; pageNum <= Pages; pageNum++)
{
requests.Add(collection.LoadMoreItemsAsync(0).AsTask()
.ContinueWith(t => Assert.IsTrue(t.IsCompletedSuccessfully)));
}

await Task.WhenAll(requests);

CollectionAssert.AreEqual(AllData, collection);
}

[TestMethod]
public async Task EveryOtherRequestFails()
{
var source = new DataSource<int>(AllData, FailPassSequence);
var collection = new IncrementalLoadingCollection<DataSource<int>, int>(source, PageSize);

var willFail = true;
for (int submitedRequests = 0; submitedRequests < Pages * 2; submitedRequests++)
{
if (willFail)
{
await Assert.ThrowsExceptionAsync<AggregateException>(collection.LoadMoreItemsAsync(0).AsTask);
}
else
{
await collection.LoadMoreItemsAsync(0);
}

willFail = !willFail;
}

CollectionAssert.AreEqual(AllData, collection);
}

[TestMethod]
public async Task EveryOtherRequestFailsAsync()
{
var source = new DataSource<int>(AllData, FailPassSequence);
var collection = new IncrementalLoadingCollection<DataSource<int>, int>(source, PageSize);

var requests = new List<Task>();

var willFail = true;
for (int submitedRequests = 0; submitedRequests < Pages * 2; submitedRequests++)
{
if (willFail)
{
requests.Add(Assert.ThrowsExceptionAsync<AggregateException>(collection.LoadMoreItemsAsync(0).AsTask));
}
else
{
requests.Add(collection.LoadMoreItemsAsync(0).AsTask().ContinueWith(t => Assert.IsTrue(t.IsCompletedSuccessfully)));
}

willFail = !willFail;
}

await Task.WhenAll(requests);

CollectionAssert.AreEqual(AllData, collection);
}
}
}
4 changes: 3 additions & 1 deletion UnitTests/UnitTests.UWP/UnitTests.UWP.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,8 @@
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Helpers\Test_WeakEventListener.cs" />
<Compile Include="UI\Animations\Test_AnimationBuilderStart.cs" />
<Compile Include="UI\Collection\DataSource.cs" />
<Compile Include="UI\Collection\Test_IncrementalLoadingCollection.cs" />
<Compile Include="UI\Controls\Test_Carousel.cs" />
<Compile Include="UI\Controls\Test_BladeView.cs" />
<Compile Include="UI\Controls\Test_RadialGauge.cs" />
Expand Down Expand Up @@ -548,4 +550,4 @@
<Target Name="AfterBuild">
</Target>
-->
</Project>
</Project>