重试上一个任务操作 TPL
Retry previous task action TPL
我想实现一个重试任务,它采用之前失败的任务操作并重复它。
这就是我目前所拥有的。然而,它只是重复任务出错的事实,而不是再次触发任务的操作。
public static async Task<T> Retry<T>(this Task<T> task, int retryCount, int delay, TaskCompletionSource<T> tcs = null)
{
if (tcs == null)
{
tcs = new TaskCompletionSource<T>();
}
await task.ContinueWith(async _original =>
{
if (_original.IsFaulted)
{
if (retryCount == 0)
{
tcs.SetException(_original.Exception.InnerExceptions);
}
else
{
Console.WriteLine("Unhandled exception. Retrying...");
await Task.Delay(delay).ContinueWith(async t =>
{
await Retry(task, retryCount - 1, delay, tcs);
});
}
}
else
tcs.SetResult(_original.Result);
});
return await tcs.Task;
}
我试图通过一点思考来获得动作。然而,一旦任务完成,操作似乎设置为空。
var action = task
.GetType()
.GetField("m_action", BindingFlags.NonPublic | BindingFlags.Instance)
.GetValue(task) as Action;
理想情况下,我希望我的实现看起来像这样:
try
{
await MakeFailure().Retry(5, 1000);
}
catch (Exception ex)
{
Console.WriteLine("I had an exception");
}
这可能不可能,但我想在将代码重构为 Retry(Func<T> task)
之前确定
那里有一个非常棒的库,您可以在不编写自己的代码的情况下使用它。它被称为瞬态故障应用程序块。但我会首先评估块中名为 TransientFaultHandling.Core.
的单个库
它的使用方式与您上面的代码非常相似。这是一个简单的例子:
using System;
using Microsoft.Practices.TransientFaultHandling;
namespace Whosebug
{
class Program
{
internal class MyTransientErrorDetectionStrategy : ITransientErrorDetectionStrategy
{
public bool IsTransient(Exception ex)
{
return true;
}
}
private static void Main(string[] args)
{
const int retryCount = 5;
const int retryIntervalInSeconds = 1;
// define the strategy for retrying
var retryStrategy = new FixedInterval(
retryCount,
TimeSpan.FromSeconds(retryIntervalInSeconds));
// define the policy
var retryPolicy =
new RetryPolicy<MyTransientErrorDetectionStrategy>(retryStrategy);
retryPolicy.Retrying += retryPolicy_Retrying;
for (int i = 0; i < 50; i++)
{
// try this a few times just to illustrate
try
{
retryPolicy.ExecuteAction(SomeMethodThatCanSometimesFail);
// (the retry policy has async support as well)
}
catch (Exception)
{
// if it got to this point, your retries were exhausted
// the original exception is rethrown
throw;
}
}
Console.WriteLine("Press Enter to Exit");
Console.ReadLine();
}
private static void SomeMethodThatCanSometimesFail()
{
var random = new Random().Next(1, 4);
if (random == 2)
{
const string msg = "randomFailure";
Console.WriteLine(msg);
throw new Exception(msg);
}
}
private static void retryPolicy_Retrying(object sender, RetryingEventArgs e)
{
Console.WriteLine("retrying");
}
}
}
您遇到的问题是,一旦您的 Task<T>
处于飞行状态,就无法撤消或重试。您必须以 Func<Task<T>>
开头才能重试。
现在您可以直接使用 TPL,但我建议您使用 Microsoft 的 Reactive Framework 来构建您需要的功能。比TPL强多了。
鉴于 Func<Task<T>>
这就是您需要的:
Func<Task<T>> taskFactory = ...
int retryCount = 5;
Task<T> retryingTask = Observable.FromAsync(taskFactory).Retry(retryCount).ToTask();
我用这段代码测试了这个:
var i = 0;
Func<Task<int>> taskFactory = () => Task.Run(() =>
{
if (i++ == 0)
throw new Exception("Foo");
return i;
});
int retryCount = 5;
Task<int> retryingTask = Observable.FromAsync(taskFactory).Retry(retryCount).ToTask();
Console.WriteLine(retryingTask.Result);
Reactive Framework 可以让您做更多的事情——它是一个非常强大的库——但它确实使这项任务变得非常简单。您可以通过 NuGet "Rx-Main" 获取位。
Not completely against it. But it changes the flow of the code to a fault first layout which I don't like
考虑你的类型。 Task
代表一个异步操作。在异步世界中,Task
代表一个异步操作已经开始。 Task
不是你能做到的 "retry".
另一方面,Func<Task>
表示可以启动的异步操作。或者重新启动。这就是您需要处理的问题。
一旦您使用了合适的类型,代码就很简单了:
public static async Task<T> Retry<T>(Func<Task<T>> action, int retryCount, int delay)
{
while (retryCount > 0)
{
try
{
return await action().ConfigureAwait(false);
}
catch (Exception ex)
{
await Task.Delay(delay).ConfigureAwait(false);
--retryCount;
}
}
return await action().ConfigureAwait(false);
}
与其他回答者一样,我建议您使用专门为此设计的库。 Transient Fault Handling Application Block and Polly 是两个很好的例子。
我想实现一个重试任务,它采用之前失败的任务操作并重复它。
这就是我目前所拥有的。然而,它只是重复任务出错的事实,而不是再次触发任务的操作。
public static async Task<T> Retry<T>(this Task<T> task, int retryCount, int delay, TaskCompletionSource<T> tcs = null)
{
if (tcs == null)
{
tcs = new TaskCompletionSource<T>();
}
await task.ContinueWith(async _original =>
{
if (_original.IsFaulted)
{
if (retryCount == 0)
{
tcs.SetException(_original.Exception.InnerExceptions);
}
else
{
Console.WriteLine("Unhandled exception. Retrying...");
await Task.Delay(delay).ContinueWith(async t =>
{
await Retry(task, retryCount - 1, delay, tcs);
});
}
}
else
tcs.SetResult(_original.Result);
});
return await tcs.Task;
}
我试图通过一点思考来获得动作。然而,一旦任务完成,操作似乎设置为空。
var action = task
.GetType()
.GetField("m_action", BindingFlags.NonPublic | BindingFlags.Instance)
.GetValue(task) as Action;
理想情况下,我希望我的实现看起来像这样:
try
{
await MakeFailure().Retry(5, 1000);
}
catch (Exception ex)
{
Console.WriteLine("I had an exception");
}
这可能不可能,但我想在将代码重构为 Retry(Func<T> task)
那里有一个非常棒的库,您可以在不编写自己的代码的情况下使用它。它被称为瞬态故障应用程序块。但我会首先评估块中名为 TransientFaultHandling.Core.
的单个库它的使用方式与您上面的代码非常相似。这是一个简单的例子:
using System;
using Microsoft.Practices.TransientFaultHandling;
namespace Whosebug
{
class Program
{
internal class MyTransientErrorDetectionStrategy : ITransientErrorDetectionStrategy
{
public bool IsTransient(Exception ex)
{
return true;
}
}
private static void Main(string[] args)
{
const int retryCount = 5;
const int retryIntervalInSeconds = 1;
// define the strategy for retrying
var retryStrategy = new FixedInterval(
retryCount,
TimeSpan.FromSeconds(retryIntervalInSeconds));
// define the policy
var retryPolicy =
new RetryPolicy<MyTransientErrorDetectionStrategy>(retryStrategy);
retryPolicy.Retrying += retryPolicy_Retrying;
for (int i = 0; i < 50; i++)
{
// try this a few times just to illustrate
try
{
retryPolicy.ExecuteAction(SomeMethodThatCanSometimesFail);
// (the retry policy has async support as well)
}
catch (Exception)
{
// if it got to this point, your retries were exhausted
// the original exception is rethrown
throw;
}
}
Console.WriteLine("Press Enter to Exit");
Console.ReadLine();
}
private static void SomeMethodThatCanSometimesFail()
{
var random = new Random().Next(1, 4);
if (random == 2)
{
const string msg = "randomFailure";
Console.WriteLine(msg);
throw new Exception(msg);
}
}
private static void retryPolicy_Retrying(object sender, RetryingEventArgs e)
{
Console.WriteLine("retrying");
}
}
}
您遇到的问题是,一旦您的 Task<T>
处于飞行状态,就无法撤消或重试。您必须以 Func<Task<T>>
开头才能重试。
现在您可以直接使用 TPL,但我建议您使用 Microsoft 的 Reactive Framework 来构建您需要的功能。比TPL强多了。
鉴于 Func<Task<T>>
这就是您需要的:
Func<Task<T>> taskFactory = ...
int retryCount = 5;
Task<T> retryingTask = Observable.FromAsync(taskFactory).Retry(retryCount).ToTask();
我用这段代码测试了这个:
var i = 0;
Func<Task<int>> taskFactory = () => Task.Run(() =>
{
if (i++ == 0)
throw new Exception("Foo");
return i;
});
int retryCount = 5;
Task<int> retryingTask = Observable.FromAsync(taskFactory).Retry(retryCount).ToTask();
Console.WriteLine(retryingTask.Result);
Reactive Framework 可以让您做更多的事情——它是一个非常强大的库——但它确实使这项任务变得非常简单。您可以通过 NuGet "Rx-Main" 获取位。
Not completely against it. But it changes the flow of the code to a fault first layout which I don't like
考虑你的类型。 Task
代表一个异步操作。在异步世界中,Task
代表一个异步操作已经开始。 Task
不是你能做到的 "retry".
另一方面,Func<Task>
表示可以启动的异步操作。或者重新启动。这就是您需要处理的问题。
一旦您使用了合适的类型,代码就很简单了:
public static async Task<T> Retry<T>(Func<Task<T>> action, int retryCount, int delay)
{
while (retryCount > 0)
{
try
{
return await action().ConfigureAwait(false);
}
catch (Exception ex)
{
await Task.Delay(delay).ConfigureAwait(false);
--retryCount;
}
}
return await action().ConfigureAwait(false);
}
与其他回答者一样,我建议您使用专门为此设计的库。 Transient Fault Handling Application Block and Polly 是两个很好的例子。