Skip to content

Commit 9c34dda

Browse files
committed
#9 Extending the builder API to support optionally configurable page request delaying behavior
1 parent 4385805 commit 9c34dda

11 files changed

+100
-54
lines changed

BFF.DataVirtualizingCollection/DataVirtualizingCollectionBuilderBase.cs

+31-7
Original file line numberDiff line numberDiff line change
@@ -146,12 +146,36 @@ public TVirtualizationKind SyncIndexAccess()
146146
return GenerateCollection();
147147
}
148148

149+
public IAsyncOnlyIndexAccessBehaviorCollectionBuilder<TItem, TVirtualizationKind> ImmediatePageRequests()
150+
{
151+
_asyncPageFetchScheduler = new ImmediateAsyncPageFetchScheduler();
152+
return this;
153+
}
154+
155+
public IAsyncOnlyIndexAccessBehaviorCollectionBuilder<TItem, TVirtualizationKind> ThrottledLifoPageRequests() =>
156+
ThrottledLifoPageRequests(_pageBackgroundScheduler);
157+
158+
public IAsyncOnlyIndexAccessBehaviorCollectionBuilder<TItem, TVirtualizationKind> ThrottledLifoPageRequests(
159+
TimeSpan throttleDueTime) =>
160+
ThrottledLifoPageRequests(throttleDueTime, _pageBackgroundScheduler);
161+
162+
public IAsyncOnlyIndexAccessBehaviorCollectionBuilder<TItem, TVirtualizationKind> ThrottledLifoPageRequests(
163+
IScheduler pageRequestBackgroundScheduler) =>
164+
ThrottledLifoPageRequests(TimeSpan.FromMilliseconds(200), pageRequestBackgroundScheduler);
165+
166+
public IAsyncOnlyIndexAccessBehaviorCollectionBuilder<TItem, TVirtualizationKind> ThrottledLifoPageRequests(
167+
TimeSpan throttleDueTime,
168+
IScheduler pageRequestBackgroundScheduler)
169+
{
170+
_asyncPageFetchScheduler = new LifoAsyncPageFetchScheduler(throttleDueTime, pageRequestBackgroundScheduler);
171+
return this;
172+
}
173+
149174
public TVirtualizationKind AsyncIndexAccess(
150175
Func<int, int, TItem> placeholderFactory)
151176
{
152177
_indexAccessBehavior = IndexAccessBehavior.Asynchronous;
153178
_placeholderFactory = placeholderFactory;
154-
_asyncPageFetchScheduler = new LifoAsyncPageFetchScheduler();
155179
return GenerateCollection();
156180
}
157181

@@ -275,7 +299,7 @@ IPage<TItem> NonPreloadingPageFetcherFactory(
275299
{
276300
var taskBasedPageFetcher = _taskBasedPageFetcher ?? throw new NullReferenceException(UninitializedElementsExceptionMessage);
277301
var placeholderFactory = _placeholderFactory ?? throw new NullReferenceException(UninitializedElementsExceptionMessage);
278-
var asyncPageFetchScheduler = _asyncPageFetchScheduler ?? throw new NullReferenceException(UninitializedElementsExceptionMessage);
302+
var asyncPageFetchScheduler = _asyncPageFetchScheduler ?? new ImmediateAsyncPageFetchScheduler();
279303
return new AsyncTaskBasedPage<TItem>(
280304
pageKey,
281305
offset,
@@ -297,7 +321,7 @@ IPage<TItem> PreloadingPageFetcherFactory(
297321
var taskBasedPageFetcher = _taskBasedPageFetcher ?? throw new NullReferenceException(UninitializedElementsExceptionMessage);
298322
var preloadingPlaceholderFactory = _preloadingPlaceholderFactory ??
299323
throw new NullReferenceException(UninitializedElementsExceptionMessage);
300-
var asyncPageFetchScheduler = _asyncPageFetchScheduler ?? throw new NullReferenceException(UninitializedElementsExceptionMessage);
324+
var asyncPageFetchScheduler = _asyncPageFetchScheduler ?? new ImmediateAsyncPageFetchScheduler();
301325
return new AsyncTaskBasedPage<TItem>(
302326
pageKey,
303327
offset,
@@ -329,7 +353,7 @@ IPage<TItem> NonPreloadingPageFetcherFactory(
329353
{
330354
var asyncEnumerableBasedPageFetcher = _asyncEnumerableBasedPageFetcher ?? throw new NullReferenceException(UninitializedElementsExceptionMessage);
331355
var placeholderFactory = _placeholderFactory ?? throw new NullReferenceException(UninitializedElementsExceptionMessage);
332-
var asyncPageFetchScheduler = _asyncPageFetchScheduler ?? throw new NullReferenceException(UninitializedElementsExceptionMessage);
356+
var asyncPageFetchScheduler = _asyncPageFetchScheduler ?? new ImmediateAsyncPageFetchScheduler();
333357
return new AsyncEnumerableBasedPage<TItem>(
334358
pageKey,
335359
offset,
@@ -351,7 +375,7 @@ IPage<TItem> PreloadingPageFetcherFactory(
351375
var asyncEnumerableBasedPageFetcher = _asyncEnumerableBasedPageFetcher ?? throw new NullReferenceException(UninitializedElementsExceptionMessage);
352376
var preloadingPlaceholderFactory = _preloadingPlaceholderFactory ??
353377
throw new NullReferenceException(UninitializedElementsExceptionMessage);
354-
var asyncPageFetchScheduler = _asyncPageFetchScheduler ?? throw new NullReferenceException(UninitializedElementsExceptionMessage);
378+
var asyncPageFetchScheduler = _asyncPageFetchScheduler ?? new ImmediateAsyncPageFetchScheduler();
355379
return new AsyncEnumerableBasedPage<TItem>(
356380
pageKey,
357381
offset,
@@ -383,7 +407,7 @@ IPage<TItem> NonPreloadingPageFetcherFactory(
383407
{
384408
var pageFetcher = _pageFetcher ?? throw new NullReferenceException(UninitializedElementsExceptionMessage);
385409
var placeholderFactory = _placeholderFactory ?? throw new NullReferenceException(UninitializedElementsExceptionMessage);
386-
var asyncPageFetchScheduler = _asyncPageFetchScheduler ?? throw new NullReferenceException(UninitializedElementsExceptionMessage);
410+
var asyncPageFetchScheduler = _asyncPageFetchScheduler ?? new ImmediateAsyncPageFetchScheduler();
387411
return new AsyncNonTaskBasedPage<TItem>(
388412
pageKey,
389413
offset,
@@ -405,7 +429,7 @@ IPage<TItem> PreloadingPageFetcherFactory(
405429
var pageFetcher = _pageFetcher ?? throw new NullReferenceException(UninitializedElementsExceptionMessage);
406430
var preloadingPlaceholderFactory = _preloadingPlaceholderFactory ??
407431
throw new NullReferenceException(UninitializedElementsExceptionMessage);
408-
var asyncPageFetchScheduler = _asyncPageFetchScheduler ?? throw new NullReferenceException(UninitializedElementsExceptionMessage);
432+
var asyncPageFetchScheduler = _asyncPageFetchScheduler ?? new ImmediateAsyncPageFetchScheduler();
409433
return new AsyncNonTaskBasedPage<TItem>(
410434
pageKey,
411435
offset,

BFF.DataVirtualizingCollection/IBuilderInterfaces.cs

+37
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,43 @@ IAsyncOnlyIndexAccessBehaviorCollectionBuilder<TItem, TVirtualizationKind> Async
143143
/// <typeparam name="TVirtualizationKind">IDataVirtualizingCollection or ISlidingWindow.</typeparam>
144144
public interface IAsyncOnlyIndexAccessBehaviorCollectionBuilder<TItem, TVirtualizationKind>
145145
{
146+
/// <summary>
147+
/// Page request are triggered right after the generation of the page's placeholders.
148+
/// <para/>
149+
/// This is the default behavior. You won't need to call it explicitly.
150+
/// </summary>
151+
IAsyncOnlyIndexAccessBehaviorCollectionBuilder<TItem, TVirtualizationKind> ImmediatePageRequests();
152+
153+
/// <summary>
154+
/// Instead of triggering the page requests right away, they are delayed in throttling (Rx) manner and then triggered in Last In First Out (LIFO)/ stack-based manner.
155+
/// <para/>
156+
/// Default throttling due time is set to 200 ms and default scheduler is the same as for the page requests.
157+
/// </summary>
158+
IAsyncOnlyIndexAccessBehaviorCollectionBuilder<TItem, TVirtualizationKind> ThrottledLifoPageRequests();
159+
160+
/// <summary>
161+
/// Instead of triggering the page requests right away, they are delayed in throttling (Rx) manner and then triggered in Last In First Out (LIFO)/ stack-based manner.
162+
/// <para/>
163+
/// Default scheduler is the same as for the page requests.
164+
/// </summary>
165+
/// <param name="throttleDueTime">Time span which has to pass by without new page request until the collected page requests are triggered.</param>
166+
IAsyncOnlyIndexAccessBehaviorCollectionBuilder<TItem, TVirtualizationKind> ThrottledLifoPageRequests(TimeSpan throttleDueTime);
167+
168+
/// <summary>
169+
/// Instead of triggering the page requests right away, they are delayed in throttling (Rx) manner and then triggered in Last In First Out (LIFO)/ stack-based manner.
170+
/// <para/>
171+
/// Default throttling due time is set to 200 ms.
172+
/// </summary>
173+
/// <param name="pageRequestBackgroundScheduler">A scheduler exclusively for page request delaying.</param>
174+
IAsyncOnlyIndexAccessBehaviorCollectionBuilder<TItem, TVirtualizationKind> ThrottledLifoPageRequests(IScheduler pageRequestBackgroundScheduler);
175+
176+
/// <summary>
177+
/// Instead of triggering the page requests right away, they are delayed in throttling (Rx) manner and then triggered in Last In First Out (LIFO)/ stack-based manner.
178+
/// </summary>
179+
/// <param name="throttleDueTime">Time span which has to pass by without new page request until the collected page requests are triggered.</param>
180+
/// <param name="pageRequestBackgroundScheduler">A scheduler exclusively for page request delaying.</param>
181+
IAsyncOnlyIndexAccessBehaviorCollectionBuilder<TItem, TVirtualizationKind> ThrottledLifoPageRequests(TimeSpan throttleDueTime, IScheduler pageRequestBackgroundScheduler);
182+
146183
/// <summary>
147184
/// If item of requested index isn't loaded yet the collections will return a placeholder instead and emit a notification as soon as it arrives.
148185
/// Per default the initially configured background scheduler is taken for page and count fetches.

BFF.DataVirtualizingCollection/PageStorage/AsyncPageBase.cs

+1-2
Original file line numberDiff line numberDiff line change
@@ -47,8 +47,7 @@ internal AsyncPageBase(
4747

4848
async Task FetchPage(CancellationToken ct)
4949
{
50-
await Task.Delay(1, ct);
51-
await asyncPageFetchScheduler.Schedule(offset, ct);
50+
await asyncPageFetchScheduler.Schedule().ConfigureAwait(false);
5251
ct.ThrowIfCancellationRequested();
5352
await FetchPageInner(ct).ConfigureAwait(false);
5453
}
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
1-
using System.Threading;
21
using System.Threading.Tasks;
32

43
namespace BFF.DataVirtualizingCollection.PageStorage
54
{
65
internal interface IAsyncPageFetchScheduler
76
{
8-
Task Schedule(int offset, CancellationToken ct);
7+
Task Schedule();
98
}
109
}
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
1-
using System.Threading;
21
using System.Threading.Tasks;
32

43
namespace BFF.DataVirtualizingCollection.PageStorage
54
{
65
internal class ImmediateAsyncPageFetchScheduler : IAsyncPageFetchScheduler
76
{
8-
public Task Schedule(int offset, CancellationToken ct) => Task.CompletedTask;
7+
public Task Schedule() => Task.CompletedTask;
98
}
109
}

BFF.DataVirtualizingCollection/PageStorage/LifoAsyncPageFetchScheduler.cs

+12-13
Original file line numberDiff line numberDiff line change
@@ -2,39 +2,38 @@
22
using System.Collections.Generic;
33
using System.Linq;
44
using System.Reactive;
5+
using System.Reactive.Concurrency;
56
using System.Reactive.Linq;
67
using System.Reactive.Subjects;
7-
using System.Threading;
88
using System.Threading.Tasks;
99

1010
namespace BFF.DataVirtualizingCollection.PageStorage
1111
{
1212
internal class LifoAsyncPageFetchScheduler : IAsyncPageFetchScheduler
1313
{
14-
private readonly Stack<(int Offset, TaskCompletionSource<Unit> TCS)> _stack = new();
15-
private readonly Subject<(int Offset, TaskCompletionSource<Unit> TCS)> _subject = new();
14+
private readonly Stack<TaskCompletionSource<Unit>> _stack = new();
15+
private readonly Subject<TaskCompletionSource<Unit>> _subject = new();
1616

17-
public LifoAsyncPageFetchScheduler()
18-
{
17+
public LifoAsyncPageFetchScheduler(
18+
TimeSpan throttleDueTime,
19+
IScheduler pageRequestBackgroundScheduler) =>
1920
_subject
21+
.Synchronize()
2022
.Do(tcs => _stack.Push(tcs))
21-
.Throttle(TimeSpan.FromMilliseconds(1000))
23+
.Throttle(throttleDueTime, pageRequestBackgroundScheduler)
2224
.Subscribe(_ =>
2325
{
24-
Console.WriteLine("Throttle Emit");
2526
while (_stack.Any())
2627
{
27-
var (offset, tcs) = _stack.Pop();
28-
Console.WriteLine($"Pop: {offset}");
28+
var tcs = _stack.Pop();
2929
tcs.SetResult(Unit.Default);
3030
}
3131
});
32-
}
33-
34-
public Task Schedule(int offset, CancellationToken ct)
32+
33+
public Task Schedule()
3534
{
3635
var taskCompletionSource = new TaskCompletionSource<Unit>();
37-
_subject.OnNext((offset, taskCompletionSource));
36+
_subject.OnNext(taskCompletionSource);
3837
return taskCompletionSource.Task;
3938
}
4039
}

Sample.Model/BackendAccesses/AllNumbersFakeBackendAccess.cs

+1-5
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,7 @@ internal class AllNumbersFakeBackendAccess : IAllNumbersFakeBackendAccess
1111
{
1212
public string Name => "All Positive Numbers";
1313

14-
public int[] PageFetch(int pageOffset, int pageSize)
15-
{
16-
Console.WriteLine(pageOffset);
17-
return Enumerable.Range(pageOffset, pageSize).ToArray();
18-
}
14+
public int[] PageFetch(int pageOffset, int pageSize) => Enumerable.Range(pageOffset, pageSize).ToArray();
1915

2016
public int PlaceholderFetch(int _, int __)
2117
{

Sample.Model/BackendAccesses/HighWorkloadFakeBackendAccess.cs

+3-6
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,12 @@ internal class HighWorkloadFakeBackendAccess : IHighWorkloadFakeBackendAccess
1616

1717
public string Name => "High Workload Simulation";
1818

19-
public ISomeWorkloadObject[] PageFetch(int pageOffset, int pageSize)
20-
{
21-
Console.WriteLine(pageOffset);
22-
return Enumerable
19+
public ISomeWorkloadObject[] PageFetch(int pageOffset, int pageSize) =>
20+
Enumerable
2321
.Range(pageOffset, pageSize)
2422
.Select(i => (ISomeWorkloadObject) new HighWorkloadObject(i))
2523
.ToArray();
26-
}
27-
24+
2825
public ISomeWorkloadObject PlaceholderFetch(int _, int __)
2926
{
3027
return Placeholder;

Sample.Model/BackendAccesses/MillionNumbersBackendAccess.cs

+1-5
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,7 @@ public MillionNumbersBackendAccess(
1919

2020
public string Name => "A Million Numbers Accessed Through Sqlite";
2121

22-
public long[] PageFetch(int pageOffset, int pageSize)
23-
{
24-
Console.WriteLine(pageOffset);
25-
return _fetchMillionNumbersFromBackend.FetchPage(pageOffset, pageSize);
26-
}
22+
public long[] PageFetch(int pageOffset, int pageSize) => _fetchMillionNumbersFromBackend.FetchPage(pageOffset, pageSize);
2723

2824
public long PlaceholderFetch(int _, int __)
2925
{

Sample.Model/BackendAccesses/ProfilesFakeBackendAccess.cs

+2-5
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,11 @@ internal class ProfilesFakeBackendAccess : IProfilesFakeBackendAccess
1313

1414
public string Name => "Profiles";
1515

16-
public IProfile[] PageFetch(int pageOffset, int pageSize)
17-
{
18-
Console.WriteLine(pageOffset);
19-
return Enumerable
16+
public IProfile[] PageFetch(int pageOffset, int pageSize) =>
17+
Enumerable
2018
.Range(pageOffset, pageSize)
2119
.Select(i => ProfileStatic.ProfilePool[i % ProfileStatic.ProfilePool.Count])
2220
.ToArray();
23-
}
2421

2522
public IProfile PlaceholderFetch(int _, int __)
2623
{

Sample.ViewModel/ViewModels/Decisions/FetcherKindViewModel.cs

+10-7
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ IAsyncOnlyIndexAccessBehaviorCollectionBuilder<T, TVirtualizationKind> Configure
3535
FetcherKind.NonTaskBased => builder.NonTaskBasedFetchers(
3636
(offset, size, _) =>
3737
{
38+
Console.WriteLine($"Client page fetch: {offset}");
3839
Thread.Sleep(DelayPageFetcherInMilliseconds);
3940
return backendAccess.PageFetch(offset, size);
4041
},
@@ -44,27 +45,29 @@ IAsyncOnlyIndexAccessBehaviorCollectionBuilder<T, TVirtualizationKind> Configure
4445
return backendAccess.CountFetch();
4546
}),
4647
FetcherKind.TaskBased => builder.TaskBasedFetchers(
47-
async (offset, size, _) =>
48+
async (offset, size, ct) =>
4849
{
49-
await Task.Delay(DelayPageFetcherInMilliseconds);
50+
Console.WriteLine($"Client page fetch: {offset}");
51+
await Task.Delay(DelayPageFetcherInMilliseconds, ct);
5052
return backendAccess.PageFetch(offset, size);
5153
},
52-
async _ =>
54+
async ct =>
5355
{
54-
await Task.Delay(DelayCountFetcherInMilliseconds);
56+
await Task.Delay(DelayCountFetcherInMilliseconds, ct);
5557
return backendAccess.CountFetch();
5658
}),
5759
FetcherKind.AsyncEnumerableBased => builder.AsyncEnumerableBasedFetchers(
58-
(offset, size, _) =>
60+
(offset, size, ct) =>
5961
{
62+
Console.WriteLine($"Client page fetch: {offset}");
6063
var delay = DelayCountFetcherInMilliseconds / size - 1;
6164
return Fetch();
6265

6366
async IAsyncEnumerable<T> Fetch()
6467
{
65-
await foreach (var item in backendAccess.AsyncEnumerablePageFetch(offset, size))
68+
await foreach (var item in backendAccess.AsyncEnumerablePageFetch(offset, size).WithCancellation(ct).ConfigureAwait(false))
6669
{
67-
await Task.Delay(delay);
70+
await Task.Delay(delay, ct);
6871
yield return item;
6972
}
7073
}

0 commit comments

Comments
 (0)