233 lines
9.3 KiB
C#
233 lines
9.3 KiB
C#
|
|
using System;
|
|||
|
|
using System.Threading;
|
|||
|
|
using System.Threading.Tasks;
|
|||
|
|
|
|||
|
|
namespace MainShell.Common
|
|||
|
|
{
|
|||
|
|
public class RetryMechanism
|
|||
|
|
{
|
|||
|
|
// 自定义异常:操作失败
|
|||
|
|
public class RetryException : Exception
|
|||
|
|
{
|
|||
|
|
public RetryException(string message) : base(message) { }
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public class RetryResult
|
|||
|
|
{
|
|||
|
|
public bool Succeeded { get; set; }
|
|||
|
|
|
|||
|
|
public bool IsAborted { get; set; }
|
|||
|
|
|
|||
|
|
public int AttemptCount { get; set; }
|
|||
|
|
|
|||
|
|
public int MaxAttempts { get; set; }
|
|||
|
|
|
|||
|
|
public string Message { get; set; }
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 向后兼容旧接口:默认直接继续重试直到达到最大次数,失败时抛异常。
|
|||
|
|
/// 新代码建议优先使用 <see cref="RetryOrThrow(Func{bool}, Func{int, bool}, string, int)"/> 或返回结果对象的重载。
|
|||
|
|
/// </summary>
|
|||
|
|
public static void RetryIfNeeded(Func<bool> checkCondition, string message, int maxAttempts = 3)
|
|||
|
|
{
|
|||
|
|
RetryOrThrow(checkCondition, attempt => true, message, maxAttempts);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 执行同步重试并返回结果对象。
|
|||
|
|
/// </summary>
|
|||
|
|
/// <param name="checkCondition">
|
|||
|
|
/// 每次尝试时执行的检查逻辑。
|
|||
|
|
/// 返回 <c>true</c> 表示成功,返回 <c>false</c> 表示本次失败并根据 <paramref name="shouldRetry"/> 决定是否继续。
|
|||
|
|
/// 如果该委托抛出异常,则异常会直接向外传播,不会被当前重试机制捕获或吞掉,也不会自动进入下一次重试。
|
|||
|
|
/// </param>
|
|||
|
|
/// <param name="shouldRetry">当一次尝试失败后,决定是否继续重试。参数为当前已完成的尝试次数,从 1 开始计数。</param>
|
|||
|
|
/// <param name="message">操作描述信息。</param>
|
|||
|
|
/// <param name="maxAttempts">最大尝试次数,必须大于 0。</param>
|
|||
|
|
public static RetryResult RetryIfNeeded(Func<bool> checkCondition, Func<int, bool> shouldRetry, string message, int maxAttempts = 3)
|
|||
|
|
{
|
|||
|
|
if (checkCondition == null)
|
|||
|
|
{
|
|||
|
|
throw new ArgumentNullException(nameof(checkCondition));
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (shouldRetry == null)
|
|||
|
|
{
|
|||
|
|
throw new ArgumentNullException(nameof(shouldRetry));
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (maxAttempts <= 0)
|
|||
|
|
{
|
|||
|
|
throw new ArgumentOutOfRangeException(nameof(maxAttempts), "maxAttempts must be greater than 0.");
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
for (int attempt = 1; attempt <= maxAttempts; attempt++)
|
|||
|
|
{
|
|||
|
|
bool conditionMet = checkCondition();
|
|||
|
|
if (conditionMet)
|
|||
|
|
{
|
|||
|
|
return new RetryResult
|
|||
|
|
{
|
|||
|
|
Succeeded = true,
|
|||
|
|
AttemptCount = attempt,
|
|||
|
|
MaxAttempts = maxAttempts,
|
|||
|
|
Message = message
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (attempt == maxAttempts)
|
|||
|
|
{
|
|||
|
|
return new RetryResult
|
|||
|
|
{
|
|||
|
|
Succeeded = false,
|
|||
|
|
AttemptCount = attempt,
|
|||
|
|
MaxAttempts = maxAttempts,
|
|||
|
|
Message = message
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
bool continueRetry = shouldRetry(attempt);
|
|||
|
|
if (!continueRetry)
|
|||
|
|
{
|
|||
|
|
return new RetryResult
|
|||
|
|
{
|
|||
|
|
Succeeded = false,
|
|||
|
|
IsAborted = true,
|
|||
|
|
AttemptCount = attempt,
|
|||
|
|
MaxAttempts = maxAttempts,
|
|||
|
|
Message = message
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return new RetryResult
|
|||
|
|
{
|
|||
|
|
Succeeded = false,
|
|||
|
|
AttemptCount = maxAttempts,
|
|||
|
|
MaxAttempts = maxAttempts,
|
|||
|
|
Message = message
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 保留抛异常风格的兼容方法
|
|||
|
|
public static void RetryOrThrow(Func<bool> checkCondition, Func<int, bool> shouldRetry, string message, int maxAttempts = 3)
|
|||
|
|
{
|
|||
|
|
var result = RetryIfNeeded(checkCondition, shouldRetry, message, maxAttempts);
|
|||
|
|
EnsureSuccess(result);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 向后兼容旧接口的异步版本:默认直接继续重试直到达到最大次数,失败时抛异常。
|
|||
|
|
/// 新代码建议优先使用 <see cref="RetryOrThrowAsync(Func{Task{bool}}, Func{int, Task{bool}}, string, int, CancellationToken)"/> 或返回结果对象的重载。
|
|||
|
|
/// </summary>
|
|||
|
|
public static async Task RetryIfNeededAsync(Func<Task<bool>> checkConditionAsync, string message, int maxAttempts = 3)
|
|||
|
|
{
|
|||
|
|
await RetryOrThrowAsync(checkConditionAsync, attempt => Task.FromResult(true), message, maxAttempts, CancellationToken.None).ConfigureAwait(false);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 执行异步重试并返回结果对象。
|
|||
|
|
/// </summary>
|
|||
|
|
/// <param name="checkConditionAsync">
|
|||
|
|
/// 每次尝试时执行的异步检查逻辑。
|
|||
|
|
/// 返回 <c>true</c> 表示成功,返回 <c>false</c> 表示本次失败并根据 <paramref name="shouldRetryAsync"/> 决定是否继续。
|
|||
|
|
/// 如果该委托抛出异常,则异常会直接向外传播,不会被当前重试机制捕获或吞掉,也不会自动进入下一次重试。
|
|||
|
|
/// </param>
|
|||
|
|
/// <param name="shouldRetryAsync">当一次尝试失败后,决定是否继续重试。参数为当前已完成的尝试次数,从 1 开始计数。</param>
|
|||
|
|
/// <param name="message">操作描述信息。</param>
|
|||
|
|
/// <param name="maxAttempts">最大尝试次数,必须大于 0。</param>
|
|||
|
|
/// <param name="cancellationToken">用于取消异步重试流程。</param>
|
|||
|
|
public static async Task<RetryResult> RetryIfNeededAsync(Func<Task<bool>> checkConditionAsync, Func<int, Task<bool>> shouldRetryAsync, string message, int maxAttempts = 3, CancellationToken cancellationToken = default(CancellationToken))
|
|||
|
|
{
|
|||
|
|
if (checkConditionAsync == null)
|
|||
|
|
{
|
|||
|
|
throw new ArgumentNullException(nameof(checkConditionAsync));
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (shouldRetryAsync == null)
|
|||
|
|
{
|
|||
|
|
throw new ArgumentNullException(nameof(shouldRetryAsync));
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (maxAttempts <= 0)
|
|||
|
|
{
|
|||
|
|
throw new ArgumentOutOfRangeException(nameof(maxAttempts), "maxAttempts must be greater than 0.");
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
for (int attempt = 1; attempt <= maxAttempts; attempt++)
|
|||
|
|
{
|
|||
|
|
cancellationToken.ThrowIfCancellationRequested();
|
|||
|
|
|
|||
|
|
bool conditionMet = await checkConditionAsync().ConfigureAwait(false);
|
|||
|
|
if (conditionMet)
|
|||
|
|
{
|
|||
|
|
return new RetryResult
|
|||
|
|
{
|
|||
|
|
Succeeded = true,
|
|||
|
|
AttemptCount = attempt,
|
|||
|
|
MaxAttempts = maxAttempts,
|
|||
|
|
Message = message
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (attempt == maxAttempts)
|
|||
|
|
{
|
|||
|
|
return new RetryResult
|
|||
|
|
{
|
|||
|
|
Succeeded = false,
|
|||
|
|
AttemptCount = attempt,
|
|||
|
|
MaxAttempts = maxAttempts,
|
|||
|
|
Message = message
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
cancellationToken.ThrowIfCancellationRequested();
|
|||
|
|
|
|||
|
|
bool continueRetry = await shouldRetryAsync(attempt).ConfigureAwait(false);
|
|||
|
|
if (!continueRetry)
|
|||
|
|
{
|
|||
|
|
return new RetryResult
|
|||
|
|
{
|
|||
|
|
Succeeded = false,
|
|||
|
|
IsAborted = true,
|
|||
|
|
AttemptCount = attempt,
|
|||
|
|
MaxAttempts = maxAttempts,
|
|||
|
|
Message = message
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return new RetryResult
|
|||
|
|
{
|
|||
|
|
Succeeded = false,
|
|||
|
|
AttemptCount = maxAttempts,
|
|||
|
|
MaxAttempts = maxAttempts,
|
|||
|
|
Message = message
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 保留抛异常风格的异步兼容方法
|
|||
|
|
public static async Task RetryOrThrowAsync(Func<Task<bool>> checkConditionAsync, Func<int, Task<bool>> shouldRetryAsync, string message, int maxAttempts = 3, CancellationToken cancellationToken = default(CancellationToken))
|
|||
|
|
{
|
|||
|
|
var result = await RetryIfNeededAsync(checkConditionAsync, shouldRetryAsync, message, maxAttempts, cancellationToken).ConfigureAwait(false);
|
|||
|
|
EnsureSuccess(result);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private static void EnsureSuccess(RetryResult result)
|
|||
|
|
{
|
|||
|
|
if (result.Succeeded)
|
|||
|
|
{
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (result.IsAborted)
|
|||
|
|
{
|
|||
|
|
throw new RetryException($"{result.Message} 已中止重试,操作失败。尝试次数: {result.AttemptCount}/{result.MaxAttempts}");
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
throw new RetryException($"{result.Message} 已达到最大重试次数,操作失败。尝试次数: {result.AttemptCount}/{result.MaxAttempts}");
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|