Skip to content

Commit 3e34a66

Browse files
author
Meyn
committed
Implement SubsequentRequest
Fix RequestContainer GetRequests issue Update UnitTests
1 parent 01d5c35 commit 3e34a66

File tree

11 files changed

+244
-73
lines changed

11 files changed

+244
-73
lines changed

Requests/IRequest.cs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,16 @@ public interface IRequest : IDisposable
2222
/// </summary>
2323
public abstract RequestPriority Priority { get; }
2424

25+
/// <summary>
26+
/// Specifies a request that should be executed immediately after this request completes, bypassing the queue.
27+
/// </summary>
28+
/// <remarks>
29+
/// The subsequent request supports auto-starting if enabled, but this behavior can be disabled if not desired.
30+
/// <br/>If the subsequent request is already running, it will not be started again.
31+
/// <br/>If this request fails, the subsequent request will be canceled and disposed.
32+
/// </remarks>
33+
public abstract IRequest? SubsequentRequest { get; }
34+
2535
/// <summary>
2636
/// Gets the <see cref="System.Threading.Tasks.Task"/> representing the completion status of this <see cref="IRequest"/>.
2737
/// </summary>
@@ -35,7 +45,7 @@ public interface IRequest : IDisposable
3545
/// <summary>
3646
/// Starts the execution of the <see cref="IRequest"/> created from this object.
3747
/// </summary>
38-
protected internal Task StartRequestAsync();
48+
public Task StartRequestAsync();
3949

4050
/// <summary>
4151
/// Cancels the execution of the <see cref="IRequest"/>.

Requests/IRequestContainer.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,12 @@ public interface IRequestContainer<TRequest> : IEnumerable<TRequest>, IRequest w
1414
/// </summary>
1515
int Count { get; }
1616

17+
/// <summary>
18+
/// Represents a task that completes when all the requests currently available in the container have completed.
19+
/// This task does not include requests that may be added to the container in the future.
20+
/// </summary>
21+
Task CurrentTask { get; }
22+
1723
/// <summary>
1824
/// Incorporates a <see cref="IRequest"/> into the <see cref="IRequestContainer{TRequest}"/>.
1925
/// </summary>

Requests/Options/IRequestOptions.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,16 @@ public interface IRequestOptions<TCompleated, TFailed>
4242
/// </summary>
4343
public CancellationToken? CancellationToken { get; set; }
4444

45+
/// <summary>
46+
/// Specifies a request that should be executed immediately after this request completes, bypassing the queue.
47+
/// </summary>
48+
/// <remarks>
49+
/// The subsequent request supports auto-starting if enabled, but this behavior can be disabled if not desired.
50+
/// <br/>If the subsequent request is already running, it will not be started again.
51+
/// <br/>If the holding request fails, the subsequent request will be canceled and disposed.
52+
/// </remarks>
53+
public IRequest? SubsequentRequest { get; set; }
54+
4555
/// <summary>
4656
/// An event that will be triggered when the <see cref="IRequest"/> is cancelled.
4757
/// </summary>

Requests/Options/RequestOptions.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,19 @@ public record RequestOptions<TCompleated, TFailed> : IRequestOptions<TCompleated
1818

1919
///<inheritdoc />
2020
public TimeSpan? DeployDelay { get; set; } = null;
21+
2122
///<inheritdoc />
2223
public RequestHandler Handler { get; set; } = RequestHandler.MainRequestHandlers[0];
24+
2325
///<inheritdoc />
2426
public byte NumberOfAttempts { get; set; } = 3;
2527

2628
///<inheritdoc />
2729
public TimeSpan? DelayBetweenAttemps { get; set; } = null;
2830

31+
///<inheritdoc />
32+
public IRequest? SubsequentRequest { get; set; }
33+
2934
///<inheritdoc />
3035
public Notify<IRequest>? RequestStarted { get; set; }
3136

@@ -51,6 +56,7 @@ protected RequestOptions(RequestOptions<TCompleated, TFailed> options)
5156
AutoStart = options.AutoStart;
5257
DelayBetweenAttemps = options.DelayBetweenAttemps;
5358
DeployDelay = options.DeployDelay;
59+
SubsequentRequest = options.SubsequentRequest;
5460
RequestCancelled += options.RequestCancelled;
5561
RequestStarted += options.RequestStarted;
5662
RequestFailed += options.RequestFailed;

Requests/Request.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,9 @@ public virtual RequestState State
117117
/// </summary>
118118
public virtual RequestPriority Priority => Options.Priority;
119119

120+
/// <inheritdoc/>
121+
IRequest? IRequest.SubsequentRequest => Options.SubsequentRequest;
122+
120123
/// <summary>
121124
/// Constructor for the <see cref="Request{TOptions, TCompleted, TFailed}"/> class.
122125
/// </summary>
@@ -166,6 +169,7 @@ public virtual void Cancel()
166169
if (!_disposed)
167170
_cts.Cancel();
168171
_isFinished.TrySetCanceled();
172+
Options.SubsequentRequest?.Cancel();
169173
}
170174

171175
/// <summary>

Requests/RequestContainer.cs

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,15 @@ protected set
6565
/// </summary>
6666
public AggregateException? Exception => new(GetStored().Where(x => x?.Exception != null).Select(x => x!.Exception!));
6767

68+
/// <summary>
69+
/// Represents a task that completes when all the requests currently available in the container have completed.
70+
/// This task does not include requests that may be added to the container in the future.
71+
/// </summary>
72+
public Task CurrentTask => Task.WhenAll(GetStored().Select(request => request.Task));
73+
74+
/// <inheritdoc/>
75+
IRequest? IRequest.SubsequentRequest => null;
76+
6877
/// <summary>
6978
/// Constructor that merges <see cref="IRequest"/> instances together.
7079
/// </summary>
@@ -275,14 +284,15 @@ public virtual void Remove(params TRequest[] requests)
275284
while (Interlocked.CompareExchange(ref _writeInProgress, 1, 0) == 1)
276285
Thread.Yield();
277286

278-
var storedRequests = GetStored().Where(x => !requests.Any(y => y.Equals(x))).ToArray();
287+
TRequest[] storedRequests = GetStored().Where(x => !requests.Any(y => y.Equals(x))).ToArray();
279288

280-
int newSize = storedRequests.Length + 32;
289+
int size = storedRequests.Length;
290+
int newSize = size + 32;
281291

282292
Array.Resize(ref storedRequests, newSize);
283293

284294
_requests = storedRequests;
285-
_count = _requests.Length;
295+
_count = size;
286296

287297
Interlocked.Exchange(ref _writeInProgress, 0);
288298

@@ -300,7 +310,7 @@ public virtual void Remove(params TRequest[] requests)
300310
public void Cancel()
301311
{
302312
_isCanceled = true;
303-
foreach (var request in GetStored())
313+
foreach (TRequest request in GetStored())
304314
request.Cancel();
305315

306316
}

Requests/RequestHandler.cs

Lines changed: 53 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,20 @@ public int? StaticDegreeOfParallelism
103103
/// </summary>
104104
public int Count => _requestsChannel.Count;
105105

106+
/// <summary>
107+
/// Represents a task that completes when all the requests currently present in the handler have finished processing.
108+
/// This task does not account for any requests that may be added to the handler after its creation.
109+
/// </summary>
110+
/// <remarks>
111+
/// <strong>Warning:</strong> This operation may block the handler for a period of time.
112+
/// </remarks>
113+
public Task CurrentTask => Task.WhenAll(_requestsChannel.ToArray().Select(requestPair => requestPair.Item.Task));
114+
115+
/// <summary>
116+
/// Specifies a request that should be executed immediately after this request completes, bypassing the queue.
117+
/// </summary>
118+
IRequest? IRequest.SubsequentRequest => null;
119+
106120
/// <summary>
107121
/// Initializes a new instance of the <see cref="RequestHandler"/> class with a priority channel.
108122
/// If the priority count is zero, an exception is thrown; otherwise, a fixed-size priority channel is created.
@@ -285,11 +299,38 @@ private async Task HandleRequests(PriorityItem<IRequest> pair)
285299
await request.StartRequestAsync();
286300

287301
if (request.State is RequestState.Compleated or RequestState.Failed or RequestState.Cancelled)
302+
{
288303
request.Dispose();
304+
305+
if (request.SubsequentRequest != null)
306+
await SubsequentRequest(request);
307+
}
289308
else if (request.State == RequestState.Idle)
290309
await _requestsChannel.Writer.WriteAsync(pair);
291310
}
292311

312+
/// <summary>
313+
/// Processes the subsequent request of the given request. Starts it if the current request completes,
314+
/// or disposes it and processes the chain if the current request fails or is canceled.
315+
/// </summary>
316+
/// <param name="request">The request to process.</param>
317+
/// <returns>A task representing the operation.</returns>
318+
private async Task SubsequentRequest(IRequest request)
319+
{
320+
IRequest subRequest = request.SubsequentRequest!;
321+
if (request.State == RequestState.Compleated)
322+
{
323+
if (subRequest.State != RequestState.Running && subRequest.TrySetIdle())
324+
await HandleRequests(new PriorityItem<IRequest>(subRequest.Priority, subRequest));
325+
}
326+
else
327+
{
328+
subRequest.Dispose();
329+
if (subRequest.SubsequentRequest != null)
330+
await SubsequentRequest(subRequest.SubsequentRequest);
331+
}
332+
}
333+
293334
/// <summary>
294335
/// Sets the priority for the <see cref="RequestContainer{TRequest}"/>.
295336
/// Not to the contained <see cref="IRequest"/> objects.
@@ -313,7 +354,7 @@ public void UpdateAutoParallelism()
313354
public bool TrySetIdle()
314355
{
315356
Pause();
316-
var requests = _requestsChannel.ToArray();
357+
PriorityItem<IRequest>[] requests = _requestsChannel.ToArray();
317358
foreach (PriorityItem<IRequest> priorityItem in requests)
318359
_ = priorityItem.Item.TrySetIdle();
319360
return requests.All(x => x.Item.State == RequestState.Idle);
@@ -339,7 +380,7 @@ public void Dispose()
339380
/// <returns>A string that represents the current state of the <see cref="RequestHandler"/>.</returns>
340381
public override string ToString()
341382
{
342-
var sb = new StringBuilder();
383+
StringBuilder sb = new();
343384

344385
sb.AppendLine("RequestHandler State:");
345386
sb.AppendLine($" Disposed: {_disposed}");
@@ -369,21 +410,25 @@ public void Remove(params IRequest[] requests)
369410
if (requests == null || requests.Length == 0)
370411
throw new ArgumentNullException(nameof(requests), "Requests cannot be null or empty.");
371412

372-
foreach (var request in requests)
413+
foreach (IRequest request in requests)
373414
if (!_requestsChannel.TryRemove(new(request.Priority, request)))
374415
throw new InvalidOperationException($"Failed to remove request: {request}");
375416
}
376417

377418
/// <summary>
378-
/// Returns an enumerator that iterates through the collection of requests.
379-
/// Blocks the Handler for a amout of time.
419+
/// Returns an enumerator that iterates through the collection of requests.
420+
/// <remarks>
421+
/// <strong>Warning:</strong> This operation may block the handler for a period of time.
422+
/// </remarks>
380423
/// </summary>
381-
/// <returns>An enumerator that can be used to iterate through the collection.</returns>
424+
/// <returns>An enumerator that can be used to iterate through the collection of requests.</returns>
382425
public IEnumerator<IRequest> GetEnumerator() => _requestsChannel.ToArray().Select(pair => pair.Item).GetEnumerator();
383426

384427
/// <summary>
385-
/// Returns an enumerator that iterates through a collection.
386-
/// Blocks the Handler for a amout of time.
428+
/// Returns an enumerator that iterates through the collection of requests.
429+
/// <remarks>
430+
/// <strong>Warning:</strong> This operation may block the handler for a period of time.
431+
/// </remarks>
387432
/// </summary>
388433
/// <returns>An <see cref="System.Collections.IEnumerator"/> object that can be used to iterate through the collection.</returns>
389434
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() => GetEnumerator();

Requests/Requests.csproj

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,12 @@
1616
<PackageTags>async; channel; priority; request; parallel; </PackageTags>
1717
<RepositoryUrl>https://github.com/TypNull/Requests</RepositoryUrl>
1818
<PackageIcon>logo.png</PackageIcon>
19-
<Version>2.1.6</Version>
19+
<Version>2.2.0</Version>
2020
<PackageRequireLicenseAcceptance>True</PackageRequireLicenseAcceptance>
2121
<PackageLicenseFile>LICENSE.txt</PackageLicenseFile>
2222
<PackageId>Shard.Requests</PackageId>
23-
<PackageReleaseNotes>Introduced DynamicPriorityChannel to manage dynamic priority handling.
24-
Implemented RequestContainer interface into RequestHandler to add request logic.
25-
Changed RequestPriority to a floating type for enhanced flexibility.
26-
RequestHandler minor improvements</PackageReleaseNotes>
23+
<PackageReleaseNotes>Implement SubsequentRequest
24+
Fix RequestContainer GetRequests issue</PackageReleaseNotes>
2725
</PropertyGroup>
2826

2927
<ItemGroup>

0 commit comments

Comments
 (0)