// Copyright (c) 2025 GeWuYou
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
namespace GFramework.Core.Functional.Async;
///
/// 异步函数式编程扩展方法
///
public static class AsyncFunctionalExtensions
{
///
/// 为任务工厂添加重试机制
///
/// 任务结果类型
/// 任务工厂函数
/// 最大重试次数
/// 重试间隔
/// 判断是否应该重试的函数,默认对所有异常重试
/// 当为 true 时直接抛出原始异常,否则包装为 AggregateException
/// 任务结果
/// 当 taskFactory 为 null 时抛出
/// 当 maxRetries 小于 0 时抛出
///
///
/// var result = await (() => UnreliableOperation())
/// .WithRetryAsync(maxRetries: 3, delay: TimeSpan.FromSeconds(1));
///
///
public static async Task WithRetryAsync(
this Func> taskFactory,
int maxRetries,
TimeSpan delay,
Func? shouldRetry = null,
bool throwOriginal = false)
{
ArgumentNullException.ThrowIfNull(taskFactory);
if (maxRetries < 0)
throw new ArgumentOutOfRangeException(nameof(maxRetries), "最大重试次数不能为负数");
shouldRetry ??= _ => true;
for (var attempt = 0; attempt <= maxRetries; attempt++)
{
try
{
return await taskFactory();
}
catch (Exception ex)
{
// 若还有重试机会且允许重试,则等待后继续;否则统一包装为 AggregateException 抛出
if (attempt < maxRetries && shouldRetry(ex))
{
await Task.Delay(delay);
}
else
{
if (throwOriginal)
throw;
throw new AggregateException($"操作在 {attempt} 次重试后仍然失败", ex);
}
}
}
// 理论上不可达,仅满足编译器要求
throw new AggregateException($"操作在 {maxRetries} 次重试后仍然失败");
}
///
/// 安全执行异步操作,将异常包装为 Result 类型
///
/// 任务结果类型
/// 要执行的异步函数
/// 包含结果或异常的 Result 对象
/// 当 func 为 null 时抛出
///
///
/// var result = await (() => RiskyOperation()).TryAsync();
/// result.Match(
/// value => Console.WriteLine($"成功: {value}"),
/// error => Console.WriteLine($"失败: {error.Message}")
/// );
///
///
public static async Task> TryAsync(this Func> func)
{
ArgumentNullException.ThrowIfNull(func);
try
{
var result = await func();
return new Result(result);
}
catch (Exception ex)
{
return new Result(ex);
}
}
}