Reputation: 409
I am trying to use the Polly package in C#. I want to run some code and then, if it fails, wait and retry. Currently my loop looks similar to this:
var successful = false
while (!successful){
// Try to perform operation.
successful = TryToDoStuff()
if (!successful){
// Wait, then retry.
await Task.WhenAny(
taskCompletionSource1.Task,
taskCompletionSource1.Task,
Task.Delay(TimeSpan.FromSeconds(10)));
}
}
I.e.: Wait 10 seconds OR until one of these task completion sources gets a signal and terminates. Then retry.
What I want to do is something like this (which is not supported by the Polly API):
Policy
.Handle<RetryException>()
.WaitAndRetryForever(
Task.WhenAny(
taskCompletionSource1.Task,
taskCompletionSource1.Task,
Task.Delay(TimeSpan.FromSeconds(10))))
.Execute(TryToDoStuff); // Method TryToDoStuff will throw RetryException if it fails
Is it possible to do something like this with Polly? Can I wait for anything other than a TimeSpan?
Regarding the two tasks I await in the above example: One task is a cancellation indicating that the entire thing should shut down. The other is a "wake up for connection attempt" task whose termination indicates that "this object's state has changed; try to call it again". In both cases I want my loop to continue to the next iteration immediately instead of waiting for the timeout to elapse.
Currently waiting for the timeout is not so bad since it's only 10 seconds, but if I change it to exponential backoff, then suddenly the timeout can be very long. Hence my desire to interrupt the timeout and proceed straight to the next iteration.
Note: It is not imperative that my retrying loop follow the async-await pattern. It is OK if the waiting part is synchronous and blocking. I just want to be able to cancel the wait using a task completion source.
Upvotes: 1
Views: 6334
Reputation: 8156
All Polly policies and executions can respond to CancellationToken
s to signal cancellation.
If I understand correctly, there are two requirements:
RetryException
occursYou can express this with a Polly policy for each:
var retryImmediatelyOnCancellation = Policy
.Handle<OperationCanceledException>()
.RetryForever();
var retryWithDelay = Policy
.Handle<RetryException>()
.WaitAndRetryForever(/* specify your desired delay or delay sequence for retries */);
and then execute through the two retry policies in a nested fashion.
Something like:
retryImmediatelyOnCancellation.Execute(() =>
{
CancellationTokenSource externalCancellation = ... // Get the CancellationTokenSource signalling external cancellation.
CancellationTokenSource wakeUpConnectionAttemptCancellation = ... // Get the CancellationTokenSource signalling "wake up connection attempt".
CancellationTokenSource combinedCancellationSource = CancellationTokenSource.CreateLinkedTokenSource(externalCancellation.Token, wakeUpConnectionAttemptCancellation.Token);
retryWithDelay.Execute(ct => TryDoStuff(), combinedCancellationSource.Token);
});
The delay made by the retryWithDelay
policy cancels immediately if the CancellationToken
is signalled.
CancellationToken
is a more natural cancellation signal than TaskCompletionSource
. However, if you need (for whatever reason) to stick with using a TaskCompletionSource
as the signal of those events, you can convert that to cancel a CancellationTokenSource
with something simple like:
taskCompletionSource.Task.ContinueWith(t => cancellationTokenSource.Cancel());
Note that both TaskCompletionSource
and CancellationToken
are single-use only: once completed or cancelled, they cannot be reset (uncompleted or uncancelled). The code sample above moves creation of the CancellationTokenSource
s inside the retry-on-cancellation loop in order that you do get fresh CancellationTokenSource
s after each cancellation signal.
All of this also works with async Polly policies if you switch to async.
Upvotes: 3
Reputation: 409
The best solution I could come up with was this:
var successful = false
while (!successful){
// Create cancellation token that gets cancelled when one of the tasks terminates.
var cts = new CancellationTokenSource();
_ = Task.Run(async () =>
{
await Task.WhenAny(
taskCompletionSource1.Task,
taskCompletionSource1.Task);
cts.Cancel();
});
// Try to perform operation.
Policy
.Handle<RetryException>()
.WaitAndRetryForever(
TimeSpan.FromSeconds(10))
.Execute(
// Method TryToDoStuff will throw RetryException if it fails
ct => TryToDoStuff(),
// Pass in cancellation token.
cts.Token);
}
This seems to work. But I might end up doing this without Polly.
Upvotes: 0