CommunityToolkit / CommunityToolkit/Windows

Implement IncrementalLoadingCollection from IAsyncEnumerable

Đang mở
#167 4 bình luận 0 reaction 0 người được giao Xem trên GitHub
components::collections enhancement feature request :mailbox_with_mail:
Ngôn ngữ chính
C#
Star
1.1k
Fork
166
Chỉ số merge pull request
Không có pull request nào được merge trong 30 ngày

Mô tả

### Overview

To implement an IncrementalLoadingCollection, you need:

1. [IIncrementalSource](https://github.com/CommunityToolkit/dotnet/blob/main/src/CommunityToolkit.Common/IncrementalLoadingCollection/IIncrementalSource.cs) from the dotnet repo, `CommunityToolkit.Common` namespace
2. [IncrementalLoadingCollection](https://github.com/CommunityToolkit/WindowsCommunityToolkit/blob/main/Microsoft.Toolkit.Uwp/IncrementalLoadingCollection/IncrementalLoadingCollection.cs) from the WindowsCommunityToolkit repo, `Microsoft.Toolkit.Uwp` namespace.

The API design is quite dated and requires creating a source class implementing `IIncrementalSource`, and creating `IncrementalLoadingCollection`.

IIncrementalSource requires to implement a method returning `Task>` that is modernly expressed with `IAsyncEnumerable`.

`IAsyncEnumerable` allows cycling over the collection in a fully asynchronous way and it's the perfect fit for a modern IncrementalLoadingCollection. I called this new type `AsyncLoadingCollection`

### API breakdown

```csharp
public class AsyncLoadingCollection(IAsyncEnumerable source, uint itemsPerPage = 25) : ObservableCollection, ISupportIncrementalLoading
{
private IAsyncEnumerator? _asyncEnumerator = source.GetAsyncEnumerator();
private readonly SemaphoreSlim _mutex = new(1, 1);

public bool HasMoreItems => _asyncEnumerator != null;

public IAsyncOperation LoadMoreItemsAsync(uint count = 0) =>
LoadMoreItemsAsync(count == 0 ? itemsPerPage : count, default)
.AsAsyncOperation();

private async Task LoadMoreItemsAsync(uint count, CancellationToken cancellationToken)
{
await _mutex.WaitAsync(cancellationToken);

if (cancellationToken.IsCancellationRequested || !HasMoreItems)
return new LoadMoreItemsResult(0);

uint itemsLoaded = 0;
var itemsToLoad = Math.Min(itemsPerPage, count);

try
{
while (itemsLoaded < itemsToLoad)
{
if (await _asyncEnumerator!.MoveNextAsync(cancellationToken).ConfigureAwait(false))
{
Add(_asyncEnumerator!.Current);
itemsLoaded++;
}
else
{
// Dispose the enumerator when we're done
await _asyncEnumerator!.DisposeAsync();
_asyncEnumerator = null;
break;
}
}
}
catch (OperationCanceledException)
{
// The operation has been canceled using the Cancellation Token.
await _asyncEnumerator!.DisposeAsync();
_asyncEnumerator = null;
}
catch (Exception)
{
await _asyncEnumerator!.DisposeAsync();
_asyncEnumerator = null;
throw;
}
finally
{
_mutex.Release();
}

return new LoadMoreItemsResult(itemsLoaded);
}
}
```

### Usage example

```csharp
public static async IAsyncEnumerable GetSampleData()
{
for (var i = 0; i < 100; i++)
{
await Task.Delay(i * 10);
yield return $"string {i}";
}
}

var myAsyncCollection = new AsyncLoadingCollection(GetSampleData());

// this is needed for the initial load (viewControls like DataGrid don't call it automatically after DataContext change)
await myAsyncCollection.LoadMoreItemsAsync();
```

Then you can bind myAsyncCollection to a DataGrid as ItemsSource

### Breaking change?

No, it's an addition to IncrementalLoadingCollection.

### Alternatives

After few days of research, I couldn't find any other implementation of an IncrementalLoadingCollection using IAsyncEnumerable.

Hướng dẫn đóng góp

Mở hướng dẫn đóng góp

Đánh giá

Issue này chưa được đánh giá.

Nhận issue mới trong hộp thư của bạn

Bản tóm tắt ngắn những issue GitHub phù hợp với người mới.