[英]Implementing timeout in async retry operations
I wrote an async method with retry logic.我写了一个带有重试逻辑的异步方法。 It works just fine, however recently I wanted to add a timeout for each try in case the operation takes too long.它工作得很好,但是最近我想为每次尝试添加一个超时,以防操作花费太长时间。
public static async Task<Result> PerformAsync(Func<Task> Delegate,
Func<Exception, bool> FailureCallback = null, int Timeout = 30000,
int Delay = 1000, int Threshold = 10)
{
if (Delegate == null)
{
throw new ArgumentNullException(nameof(Delegate));
}
if (Threshold < 1)
{
throw new ArgumentOutOfRangeException(nameof(Threshold));
}
CancellationTokenSource Source = new CancellationTokenSource();
CancellationToken Token = Source.Token;
bool IsSuccess = false;
for (int Attempt = 0; Attempt <= Threshold && !Source.IsCancellationRequested;
Attempt++)
{
try
{
await Delegate();
Source.Cancel();
IsSuccess = true;
break;
}
catch (Exception E)
{
Exceptions.Add(E);
if (FailureCallback != null)
{
bool IsCanceled =
Application.Current.Dispatcher.Invoke(new Func<bool>(() =>
{
return !FailureCallback(E);
}));
if (IsCanceled)
{
Source.Cancel();
IsSuccess = false;
break;
}
}
}
await Task.Delay(Delay);
}
return new Result(IsSuccess, new AggregateException(Exceptions));
}
I've been trying various solutions all over the web, but for whatever reason I've never managed to set timeout for each try individually.我一直在 web 上尝试各种解决方案,但无论出于何种原因,我从未设法为每次尝试单独设置超时。
I tried to do this using Task.WhenAny()
with Task.Delay(Timeout)
, but when I launch my program, FailureCallback
is called only once and if another try fails, FailureCallback
is not called.我尝试使用Task.WhenAny()
和Task.Delay(Timeout)
来做到这一点,但是当我启动我的程序时, FailureCallback
只被调用一次,如果另一次尝试失败, FailureCallback
不会被调用。
Ok, lets start.好的,让我们开始吧。 First of all, the intended usage of a CancellationToken isn't to cancel locally a loop, that's a waste, a CancellationToken reserves some resources and in your case you can simply usea boolean.首先,CancellationToken 的预期用途不是在本地取消循环,这是一种浪费,CancellationToken 保留一些资源,在您的情况下,您可以简单地使用 boolean。
bool IsSuccess = false;
bool IsCancelled = false;
for (int Attempt = 0; Attempt <= Threshold; Attempt++)
{
try
{
await Delegate();
IsSuccess = true;
//You are breaking the for loop, no need to test the boolean
//in the for conditions
break;
}
catch (Exception E)
{
Exceptions.Add(E);
if (FailureCallback != null)
{
IsCancelled = Application.Current.Dispatcher.Invoke(new Func<bool>(() =>
{
return !FailureCallback(E);
}));
//You are breaking the for loop, no need to test the boolean
//in the for conditions
if(IsCancelled)
break;
}
}
await Task.Delay(Delay);
}
//Here you have "IsSuccess" and "IsCancelled" to know what happened in the loop
//If IsCancelled is true the operation was cancelled, if IsSuccess is true
//the operation was success, if both are false the attempt surpased threshold.
Second, you must update your delegate to be cancellable, that's the real intended usage of CancellationToken
, make your delegate to expect a CancellationToken
and use it properly inside the function.其次,您必须将您的委托更新为可取消,这是CancellationToken
的真正预期用途,让您的委托期待一个CancellationToken
并在 function 中正确使用它。
public static async Task<Result> PerformAsync(Func<CancellationToken, Task> Delegate, //..
//This is an example of the Delegate function
public Task MyDelegateImplemented(CancellationToken Token)
{
//If you have a loop check if it's cancelled in each iteration
while(true)
{
//Throw a TaskCanceledException if the cancellation has been requested
Token.ThrowIfCancellationRequested();
//Now you must propagate the token to any async function
//that accepts it
//Let's suppose you are downloading a web page
HttpClient client;
//...
await client.SendAsync(message, Token)
}
}
Finally, now that your task is cancellable you can implement the timeout like this:最后,既然您的任务是可取消的,您可以像这样实现超时:
//This is the "try" in your loop
try
{
var tokenSource = new CancellationTokenSource();
var call = Delegate(tokenSource.Token);
var delay = Task.Delay(timeout, tokenSource.Token);
var finishedTask = await Task.WaitAny(new Task[]{ call, delay });
//Here call has finished or delay has finished, one will
//still be running so you need to cancel it
tokenSource.Cancel();
tokenSource.Dispose();
//WaitAny will return the task index that has finished
//so if it's 0 is the call to your function, else it's the timeout
if(finishedTask == 0)
{
IsSuccess = true;
break;
}
else
{
//Task has timed out, handle the retry as you need.
}
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.