diff --git a/Net/DownloadConfigurationManager.cs b/Net/DownloadConfigurationManager.cs
new file mode 100644
index 00000000..c3cf1dd0
--- /dev/null
+++ b/Net/DownloadConfigurationManager.cs
@@ -0,0 +1,670 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text.Json;
+using System.Text.Json.Serialization;
+using PCL.Core.IO;
+using PCL.Core.Logging;
+
+namespace PCL.Core.Net;
+
+///
+/// 下载器配置文件结构
+///
+public class DownloadConfigurationProfile
+{
+ ///
+ /// 配置文件名称
+ ///
+ public string Name { get; set; } = "Default";
+
+ ///
+ /// 配置描述
+ ///
+ public string? Description { get; set; }
+
+ ///
+ /// 是否为默认配置
+ ///
+ public bool IsDefault { get; set; } = false;
+
+ ///
+ /// 线程数
+ ///
+ public int ThreadCount { get; set; } = 4;
+
+ ///
+ /// 分块大小 (字节)
+ ///
+ public int ChunkSize { get; set; } = 1024 * 1024; // 1MB
+
+ ///
+ /// 最大重试次数
+ ///
+ public int MaxRetries { get; set; } = 3;
+
+ ///
+ /// 超时时间 (毫秒)
+ ///
+ public int TimeoutMs { get; set; } = 30000;
+
+ ///
+ /// 缓冲区大小 (字节)
+ ///
+ public int BufferSize { get; set; } = 8192;
+
+ ///
+ /// 速度限制 (字节/秒,0表示无限制)
+ ///
+ public long SpeedLimit { get; set; } = 0;
+
+ ///
+ /// 是否启用详细日志
+ ///
+ public bool VerboseLogging { get; set; } = false;
+
+ ///
+ /// 连接池大小
+ ///
+ public int ConnectionPoolSize { get; set; } = 8;
+
+ ///
+ /// 连接保持时间 (秒)
+ ///
+ public int KeepAliveTimeoutSeconds { get; set; } = 30;
+
+ ///
+ /// 是否启用压缩
+ ///
+ public bool EnableCompression { get; set; } = true;
+
+ ///
+ /// 用户代理
+ ///
+ public string UserAgent { get; set; } = "PCL.Core/1.0 (Enhanced Multi-Thread Downloader)";
+
+ ///
+ /// 自定义请求头
+ ///
+ public Dictionary CustomHeaders { get; set; } = new();
+
+ ///
+ /// 是否验证SSL证书
+ ///
+ public bool ValidateSSLCertificate { get; set; } = true;
+
+ ///
+ /// 重试延迟策略
+ ///
+ public RetryDelayStrategy RetryDelayStrategy { get; set; } = RetryDelayStrategy.ExponentialBackoff;
+
+ ///
+ /// 基础重试延迟 (毫秒)
+ ///
+ public int BaseRetryDelayMs { get; set; } = 1000;
+
+ ///
+ /// 最大重试延迟 (毫秒)
+ ///
+ public int MaxRetryDelayMs { get; set; } = 30000;
+
+ ///
+ /// 适用场景标签
+ ///
+ public List ScenarioTags { get; set; } = new();
+
+ ///
+ /// 创建时间
+ ///
+ public DateTime CreatedAt { get; set; } = DateTime.Now;
+
+ ///
+ /// 最后修改时间
+ ///
+ public DateTime LastModified { get; set; } = DateTime.Now;
+
+ ///
+ /// 创建配置的副本
+ ///
+ public DownloadConfigurationProfile Clone()
+ {
+ var json = JsonSerializer.Serialize(this);
+ return JsonSerializer.Deserialize(json)!;
+ }
+
+ ///
+ /// 验证配置的有效性
+ ///
+ public void Validate()
+ {
+ if (ThreadCount <= 0 || ThreadCount > 64)
+ throw new ArgumentOutOfRangeException(nameof(ThreadCount), "线程数必须在1-64之间");
+
+ if (ChunkSize < 1024 || ChunkSize > 100 * 1024 * 1024)
+ throw new ArgumentOutOfRangeException(nameof(ChunkSize), "分块大小必须在1KB-100MB之间");
+
+ if (TimeoutMs < 1000 || TimeoutMs > 300000)
+ throw new ArgumentOutOfRangeException(nameof(TimeoutMs), "超时时间必须在1-300秒之间");
+
+ if (BufferSize < 1024 || BufferSize > 1024 * 1024)
+ throw new ArgumentOutOfRangeException(nameof(BufferSize), "缓冲区大小必须在1KB-1MB之间");
+
+ if (SpeedLimit < 0)
+ throw new ArgumentOutOfRangeException(nameof(SpeedLimit), "速度限制不能为负数");
+
+ if (MaxRetries < 0 || MaxRetries > 10)
+ throw new ArgumentOutOfRangeException(nameof(MaxRetries), "重试次数必须在0-10之间");
+ }
+}
+
+///
+/// 下载配置
+///
+public class DownloadConfiguration
+{
+ ///
+ /// 线程数
+ ///
+ public int ThreadCount { get; set; } = 4;
+
+ ///
+ /// 分块大小 (字节)
+ ///
+ public int ChunkSize { get; set; } = 1024 * 1024; // 1MB
+
+ ///
+ /// 最大重试次数
+ ///
+ public int MaxRetries { get; set; } = 3;
+
+ ///
+ /// 超时时间 (毫秒)
+ ///
+ public int TimeoutMs { get; set; } = 30000;
+
+ ///
+ /// 缓冲区大小 (字节)
+ ///
+ public int BufferSize { get; set; } = 8192;
+
+ ///
+ /// 速度限制 (字节/秒,0表示无限制)
+ ///
+ public long SpeedLimit { get; set; } = 0;
+
+ ///
+ /// 是否启用详细日志
+ ///
+ public bool VerboseLogging { get; set; } = false;
+
+ ///
+ /// 启用断点续传
+ ///
+ public bool EnableResumeSupport { get; set; } = true;
+
+ ///
+ /// 启用连接池复用
+ ///
+ public bool EnableConnectionPooling { get; set; } = true;
+
+ ///
+ /// 文件预分配
+ ///
+ public bool PreAllocateFile { get; set; } = true;
+
+ ///
+ /// 从配置文件创建下载配置
+ ///
+ public static DownloadConfiguration FromProfile(DownloadConfigurationProfile profile)
+ {
+ return new DownloadConfiguration
+ {
+ ThreadCount = profile.ThreadCount,
+ ChunkSize = profile.ChunkSize,
+ MaxRetries = profile.MaxRetries,
+ TimeoutMs = profile.TimeoutMs,
+ BufferSize = profile.BufferSize,
+ SpeedLimit = profile.SpeedLimit,
+ VerboseLogging = profile.VerboseLogging,
+ EnableResumeSupport = true, // 默认启用断点续传
+ EnableConnectionPooling = true, // 默认启用连接池
+ PreAllocateFile = true // 默认启用文件预分配
+ };
+ }
+}
+
+///
+/// 重试延迟策略
+///
+public enum RetryDelayStrategy
+{
+ ///
+ /// 固定延迟
+ ///
+ Fixed,
+
+ ///
+ /// 指数退避
+ ///
+ ExponentialBackoff,
+
+ ///
+ /// 线性增长
+ ///
+ Linear,
+
+ ///
+ /// 随机延迟
+ ///
+ Random
+}
+
+///
+/// 下载配置提供器
+/// 提供预定义的下载配置,遵循底层服务原则,不维护配置文件
+///
+public static class DownloadConfigurationProvider
+{
+ private const string LogModule = "DownloadConfigProvider";
+
+ ///
+ /// 当前活动配置
+ ///
+ public static DownloadConfigurationProfile ActiveProfile { get; private set; } = GetPresetProfile("Default");
+
+ ///
+ /// 所有可用的预设配置
+ ///
+ public static IReadOnlyCollection AvailableProfiles => GetAllPresetProfiles();
+
+ ///
+ /// 配置变更事件
+ ///
+ public static event Action? ConfigurationChanged;
+
+ ///
+ /// 切换活动配置
+ ///
+ /// 配置名称
+ public static bool SetActiveProfile(string profileName)
+ {
+ try
+ {
+ var profile = GetPresetProfile(profileName);
+ ActiveProfile = profile;
+ ConfigurationChanged?.Invoke(profile);
+ LogWrapper.Info(LogModule, $"已切换到配置: {profileName}");
+ return true;
+ }
+ catch
+ {
+ LogWrapper.Warn(LogModule, $"配置不存在: {profileName}");
+ return false;
+ }
+ }
+
+ ///
+ /// 获取预定义配置
+ ///
+ /// 配置名称
+ /// 配置实例
+ public static DownloadConfigurationProfile GetPresetProfile(string name)
+ {
+ return name.ToLower() switch
+ {
+ "high-speed" => CreateHighSpeedProfile(),
+ "low-bandwidth" => CreateLowBandwidthProfile(),
+ "stable" => CreateStableProfile(),
+ "mobile" => CreateMobileProfile(),
+ "server" => CreateServerProfile(),
+ _ => CreateDefaultProfile()
+ };
+ }
+
+ ///
+ /// 获取所有预设配置
+ ///
+ public static List GetAllPresetProfiles()
+ {
+ return new List
+ {
+ GetPresetProfile("Default"),
+ GetPresetProfile("High-Speed"),
+ GetPresetProfile("Low-Bandwidth"),
+ GetPresetProfile("Stable"),
+ GetPresetProfile("Mobile"),
+ GetPresetProfile("Server")
+ };
+ }
+
+ ///
+ /// 根据网络条件自动选择最佳配置
+ ///
+ /// 网络速度 (字节/秒)
+ /// 是否为计量网络
+ /// 推荐的配置
+ public static DownloadConfigurationProfile GetRecommendedProfile(long networkSpeed = 0, bool isMetered = false)
+ {
+ try
+ {
+ if (isMetered)
+ {
+ LogWrapper.Info(LogModule, "检测到计量网络,使用低带宽配置");
+ return GetPresetProfile("Low-Bandwidth");
+ }
+
+ if (networkSpeed > 0)
+ {
+ // 根据网速推荐配置
+ if (networkSpeed > 50 * 1024 * 1024) // 50MB/s
+ {
+ LogWrapper.Info(LogModule, "高速网络,使用高速配置");
+ return GetPresetProfile("High-Speed");
+ }
+ else if (networkSpeed < 1 * 1024 * 1024) // 1MB/s
+ {
+ LogWrapper.Info(LogModule, "低速网络,使用移动网络配置");
+ return GetPresetProfile("Mobile");
+ }
+ }
+
+ LogWrapper.Info(LogModule, "使用稳定配置");
+ return GetPresetProfile("Stable");
+ }
+ catch (Exception ex)
+ {
+ LogWrapper.Warn(LogModule, $"获取推荐配置失败: {ex.Message},使用默认配置");
+ return GetPresetProfile("Default");
+ }
+ }
+
+ ///
+ /// 根据文件大小推荐配置
+ ///
+ /// 文件大小
+ /// 推荐的配置
+ public static DownloadConfigurationProfile GetProfileForFileSize(long fileSize)
+ {
+ try
+ {
+ if (fileSize < 10 * 1024 * 1024) // 小于10MB
+ {
+ LogWrapper.Info(LogModule, "小文件,使用移动网络配置");
+ return GetPresetProfile("Mobile");
+ }
+ else if (fileSize > 1024 * 1024 * 1024) // 大于1GB
+ {
+ LogWrapper.Info(LogModule, "大文件,使用高速配置");
+ return GetPresetProfile("High-Speed");
+ }
+ else
+ {
+ LogWrapper.Info(LogModule, "中等文件,使用稳定配置");
+ return GetPresetProfile("Stable");
+ }
+ }
+ catch (Exception ex)
+ {
+ LogWrapper.Warn(LogModule, $"获取文件大小配置失败: {ex.Message},使用默认配置");
+ return GetPresetProfile("Default");
+ }
+ }
+
+ ///
+ /// 创建自定义配置(基于预设配置)
+ ///
+ /// 基础配置名称
+ /// 自定义配置名称
+ /// 自定义参数
+ public static DownloadConfigurationProfile CreateCustomProfile(string baseName, string customName, Action customizations)
+ {
+ try
+ {
+ var baseProfile = GetPresetProfile(baseName);
+ var customProfile = baseProfile.Clone();
+
+ // 应用自定义设置
+ customProfile.Name = customName;
+ customProfile.IsDefault = false;
+ customProfile.LastModified = DateTime.Now;
+ customizations(customProfile);
+
+ // 验证配置
+ customProfile.Validate();
+
+ LogWrapper.Info(LogModule, $"创建自定义配置: {customName} (基于 {baseName})");
+ return customProfile;
+ }
+ catch (Exception ex)
+ {
+ LogWrapper.Error(ex, LogModule, $"创建自定义配置失败: {customName}");
+ throw;
+ }
+ }
+
+ ///
+ /// 创建默认配置
+ ///
+ private static DownloadConfigurationProfile CreateDefaultProfile()
+ {
+ return new DownloadConfigurationProfile
+ {
+ Name = "Default",
+ Description = "默认下载配置,适用于大多数场景",
+ IsDefault = true,
+ ThreadCount = 4,
+ ChunkSize = 1024 * 1024, // 1MB
+ MaxRetries = 3,
+ TimeoutMs = 30000,
+ BufferSize = 8192,
+ SpeedLimit = 0,
+ VerboseLogging = false,
+ ConnectionPoolSize = 8,
+ KeepAliveTimeoutSeconds = 30,
+ EnableCompression = true,
+ ValidateSSLCertificate = true,
+ RetryDelayStrategy = RetryDelayStrategy.ExponentialBackoff,
+ BaseRetryDelayMs = 1000,
+ MaxRetryDelayMs = 30000,
+ ScenarioTags = new List { "general", "default" }
+ };
+ }
+
+ ///
+ /// 创建高速下载配置
+ ///
+ private static DownloadConfigurationProfile CreateHighSpeedProfile()
+ {
+ return new DownloadConfigurationProfile
+ {
+ Name = "High-Speed",
+ Description = "高速下载配置,适用于高带宽网络环境",
+ ThreadCount = 8,
+ ChunkSize = 4 * 1024 * 1024, // 4MB
+ MaxRetries = 5,
+ TimeoutMs = 60000,
+ BufferSize = 32768,
+ SpeedLimit = 0,
+ VerboseLogging = false,
+ ConnectionPoolSize = 16,
+ KeepAliveTimeoutSeconds = 60,
+ EnableCompression = true,
+ ValidateSSLCertificate = true,
+ RetryDelayStrategy = RetryDelayStrategy.Fixed,
+ BaseRetryDelayMs = 500,
+ MaxRetryDelayMs = 5000,
+ ScenarioTags = new List { "high-speed", "high-bandwidth", "server" }
+ };
+ }
+
+ ///
+ /// 创建低带宽配置
+ ///
+ private static DownloadConfigurationProfile CreateLowBandwidthProfile()
+ {
+ return new DownloadConfigurationProfile
+ {
+ Name = "Low-Bandwidth",
+ Description = "低带宽配置,适用于网络条件较差的环境",
+ ThreadCount = 2,
+ ChunkSize = 256 * 1024, // 256KB
+ MaxRetries = 5,
+ TimeoutMs = 120000,
+ BufferSize = 4096,
+ SpeedLimit = 512 * 1024, // 512KB/s
+ VerboseLogging = true,
+ ConnectionPoolSize = 4,
+ KeepAliveTimeoutSeconds = 15,
+ EnableCompression = true,
+ ValidateSSLCertificate = true,
+ RetryDelayStrategy = RetryDelayStrategy.ExponentialBackoff,
+ BaseRetryDelayMs = 2000,
+ MaxRetryDelayMs = 60000,
+ ScenarioTags = new List { "low-bandwidth", "slow-network", "conservative" }
+ };
+ }
+
+ ///
+ /// 创建稳定配置
+ ///
+ private static DownloadConfigurationProfile CreateStableProfile()
+ {
+ return new DownloadConfigurationProfile
+ {
+ Name = "Stable",
+ Description = "稳定配置,平衡速度和稳定性",
+ ThreadCount = 6,
+ ChunkSize = 2 * 1024 * 1024, // 2MB
+ MaxRetries = 4,
+ TimeoutMs = 45000,
+ BufferSize = 16384,
+ SpeedLimit = 0,
+ VerboseLogging = false,
+ ConnectionPoolSize = 12,
+ KeepAliveTimeoutSeconds = 45,
+ EnableCompression = true,
+ ValidateSSLCertificate = true,
+ RetryDelayStrategy = RetryDelayStrategy.ExponentialBackoff,
+ BaseRetryDelayMs = 1500,
+ MaxRetryDelayMs = 20000,
+ ScenarioTags = new List { "stable", "balanced", "reliable" }
+ };
+ }
+
+ ///
+ /// 创建移动网络配置
+ ///
+ private static DownloadConfigurationProfile CreateMobileProfile()
+ {
+ return new DownloadConfigurationProfile
+ {
+ Name = "Mobile",
+ Description = "移动网络配置,适用于移动设备和不稳定网络",
+ ThreadCount = 2,
+ ChunkSize = 512 * 1024, // 512KB
+ MaxRetries = 6,
+ TimeoutMs = 90000,
+ BufferSize = 4096,
+ SpeedLimit = 1024 * 1024, // 1MB/s
+ VerboseLogging = true,
+ ConnectionPoolSize = 4,
+ KeepAliveTimeoutSeconds = 20,
+ EnableCompression = true,
+ ValidateSSLCertificate = true,
+ RetryDelayStrategy = RetryDelayStrategy.ExponentialBackoff,
+ BaseRetryDelayMs = 3000,
+ MaxRetryDelayMs = 60000,
+ ScenarioTags = new List { "mobile", "unstable-network", "metered" }
+ };
+ }
+
+ ///
+ /// 创建服务器配置
+ ///
+ private static DownloadConfigurationProfile CreateServerProfile()
+ {
+ return new DownloadConfigurationProfile
+ {
+ Name = "Server",
+ Description = "服务器配置,适用于服务器环境的批量下载",
+ ThreadCount = 16,
+ ChunkSize = 8 * 1024 * 1024, // 8MB
+ MaxRetries = 3,
+ TimeoutMs = 120000,
+ BufferSize = 65536,
+ SpeedLimit = 0,
+ VerboseLogging = false,
+ ConnectionPoolSize = 32,
+ KeepAliveTimeoutSeconds = 300,
+ EnableCompression = true,
+ ValidateSSLCertificate = true,
+ RetryDelayStrategy = RetryDelayStrategy.Fixed,
+ BaseRetryDelayMs = 1000,
+ MaxRetryDelayMs = 10000,
+ ScenarioTags = new List { "server", "batch", "high-performance" }
+ };
+ }
+
+ ///
+ /// 获取配置的性能评估
+ ///
+ /// 配置文件
+ /// 性能评估信息
+ public static string GetPerformanceAssessment(DownloadConfigurationProfile profile)
+ {
+ var assessment = new List();
+
+ // 评估并发能力
+ if (profile.ThreadCount >= 8)
+ assessment.Add("高并发");
+ else if (profile.ThreadCount >= 4)
+ assessment.Add("中等并发");
+ else
+ assessment.Add("低并发");
+
+ // 评估内存使用
+ var memoryUsage = profile.ThreadCount * profile.ChunkSize / 1024 / 1024;
+ if (memoryUsage > 50)
+ assessment.Add("高内存使用");
+ else if (memoryUsage > 20)
+ assessment.Add("中等内存使用");
+ else
+ assessment.Add("低内存使用");
+
+ // 评估网络友好度
+ if (profile.SpeedLimit > 0)
+ assessment.Add("带宽限制");
+ if (profile.MaxRetries >= 5)
+ assessment.Add("高容错");
+
+ return string.Join(", ", assessment);
+ }
+
+ ///
+ /// 获取适用场景建议
+ ///
+ /// 配置文件
+ /// 场景建议
+ public static List GetScenarioRecommendations(DownloadConfigurationProfile profile)
+ {
+ var recommendations = new List();
+
+ if (profile.ThreadCount >= 8 && profile.ChunkSize >= 4 * 1024 * 1024)
+ recommendations.Add("大文件下载");
+
+ if (profile.SpeedLimit > 0)
+ recommendations.Add("带宽受限环境");
+
+ if (profile.MaxRetries >= 5)
+ recommendations.Add("不稳定网络");
+
+ if (profile.ConnectionPoolSize >= 16)
+ recommendations.Add("服务器批量下载");
+
+ if (profile.ThreadCount <= 2 && profile.ChunkSize <= 512 * 1024)
+ recommendations.Add("移动设备");
+
+ return recommendations;
+ }
+}
diff --git a/Net/DownloadErrorHandler.cs b/Net/DownloadErrorHandler.cs
new file mode 100644
index 00000000..ef9a4ef9
--- /dev/null
+++ b/Net/DownloadErrorHandler.cs
@@ -0,0 +1,804 @@
+using System;
+using System.Collections.Generic;
+using System.Net;
+using System.Net.Http;
+using System.Threading.Tasks;
+using PCL.Core.Logging;
+
+namespace PCL.Core.Net;
+
+///
+/// 下载错误类型
+///
+public enum DownloadErrorType
+{
+ ///
+ /// 网络连接错误
+ ///
+ NetworkConnection,
+
+ ///
+ /// HTTP错误
+ ///
+ HttpError,
+
+ ///
+ /// 文件IO错误
+ ///
+ FileIo,
+
+ ///
+ /// 服务器错误
+ ///
+ ServerError,
+
+ ///
+ /// 认证错误
+ ///
+ Authentication,
+
+ ///
+ /// 超时错误
+ ///
+ Timeout,
+
+ ///
+ /// 取消操作
+ ///
+ Cancelled,
+
+ ///
+ /// 配置错误
+ ///
+ Configuration,
+
+ ///
+ /// 未知错误
+ ///
+ Unknown
+}
+
+///
+/// 错误严重程度
+///
+public enum ErrorSeverity
+{
+ ///
+ /// 信息级别
+ ///
+ Info,
+
+ ///
+ /// 警告级别
+ ///
+ Warning,
+
+ ///
+ /// 错误级别
+ ///
+ Error,
+
+ ///
+ /// 严重错误级别
+ ///
+ Critical
+}
+
+///
+/// 下载错误信息
+///
+public class DownloadError
+{
+ ///
+ /// 错误ID
+ ///
+ public string Id { get; set; } = Guid.NewGuid().ToString();
+
+ ///
+ /// 发生时间
+ ///
+ public DateTime Timestamp { get; set; } = DateTime.Now;
+
+ ///
+ /// 错误类型
+ ///
+ public DownloadErrorType Type { get; set; }
+
+ ///
+ /// 错误严重程度
+ ///
+ public ErrorSeverity Severity { get; set; }
+
+ ///
+ /// 错误代码
+ ///
+ public string ErrorCode { get; set; } = "";
+
+ ///
+ /// 错误消息
+ ///
+ public string Message { get; set; } = "";
+
+ ///
+ /// 详细描述
+ ///
+ public string Details { get; set; } = "";
+
+ ///
+ /// 原始异常
+ ///
+ public Exception? OriginalException { get; set; }
+
+ ///
+ /// 上下文信息
+ ///
+ public Dictionary Context { get; set; } = new();
+
+ ///
+ /// 建议的解决方案
+ ///
+ public List SuggestedSolutions { get; set; } = new();
+
+ ///
+ /// 是否可重试
+ ///
+ public bool IsRetryable { get; set; } = true;
+
+ ///
+ /// 重试延迟(毫秒)
+ ///
+ public int RetryDelayMs { get; set; } = 1000;
+
+ ///
+ /// 已重试次数
+ ///
+ public int RetryCount { get; set; } = 0;
+
+ ///
+ /// 最大重试次数
+ ///
+ public int MaxRetries { get; set; } = 3;
+
+ public override string ToString()
+ {
+ return $"[{Type}] {ErrorCode}: {Message}";
+ }
+}
+
+///
+/// 错误恢复策略
+///
+public enum ErrorRecoveryStrategy
+{
+ ///
+ /// 立即重试
+ ///
+ ImmediateRetry,
+
+ ///
+ /// 延迟重试
+ ///
+ DelayedRetry,
+
+ ///
+ /// 指数退避重试
+ ///
+ ExponentialBackoff,
+
+ ///
+ /// 降级处理(如切换到单线程)
+ ///
+ Fallback,
+
+ ///
+ /// 终止操作
+ ///
+ Abort,
+
+ ///
+ /// 跳过当前任务
+ ///
+ Skip
+}
+
+///
+/// 错误恢复结果
+///
+public class ErrorRecoveryResult
+{
+ ///
+ /// 采用的策略
+ ///
+ public ErrorRecoveryStrategy Strategy { get; set; }
+
+ ///
+ /// 是否成功恢复
+ ///
+ public bool IsRecovered { get; set; }
+
+ ///
+ /// 恢复消息
+ ///
+ public string Message { get; set; } = "";
+
+ ///
+ /// 额外参数
+ ///
+ public Dictionary Parameters { get; set; } = new();
+}
+
+///
+/// 下载错误处理器
+/// 提供错误分析、诊断和自动恢复功能
+///
+public class DownloadErrorHandler
+{
+ private const string LogModule = "DownloadErrorHandler";
+
+ private readonly Dictionary> _recoveryHandlers = new();
+ private readonly List _errorHistory = new();
+ private readonly object _lockObject = new();
+
+ ///
+ /// 最大错误历史记录数
+ ///
+ public int MaxErrorHistoryCount { get; set; } = 100;
+
+ ///
+ /// 启用自动恢复
+ ///
+ public bool EnableAutoRecovery { get; set; } = true;
+
+ ///
+ /// 错误历史记录
+ ///
+ public IReadOnlyList ErrorHistory
+ {
+ get
+ {
+ lock (_lockObject)
+ {
+ return _errorHistory.ToArray();
+ }
+ }
+ }
+
+ ///
+ /// 错误发生事件
+ ///
+ public event Action? ErrorOccurred;
+
+ ///
+ /// 错误恢复事件
+ ///
+ public event Action? ErrorRecovered;
+
+ public DownloadErrorHandler()
+ {
+ InitializeDefaultRecoveryHandlers();
+ LogWrapper.Info(LogModule, "下载错误处理器已初始化");
+ }
+
+ ///
+ /// 处理异常并转换为下载错误
+ ///
+ /// 异常实例
+ /// 上下文信息
+ /// 下载错误实例
+ public DownloadError HandleException(Exception exception, Dictionary? context = null)
+ {
+ var error = AnalyzeException(exception);
+
+ // 添加上下文信息
+ if (context != null)
+ {
+ foreach (var kvp in context)
+ {
+ error.Context[kvp.Key] = kvp.Value;
+ }
+ }
+
+ // 记录错误
+ RecordError(error);
+
+ // 触发事件
+ ErrorOccurred?.Invoke(error);
+
+ LogWrapper.Warn(LogModule, $"处理下载错误: {error}");
+
+ return error;
+ }
+
+ ///
+ /// 尝试恢复错误
+ ///
+ /// 错误实例
+ /// 恢复结果
+ public async Task TryRecoverAsync(DownloadError error)
+ {
+ if (!EnableAutoRecovery || !error.IsRetryable)
+ {
+ return new ErrorRecoveryResult
+ {
+ Strategy = ErrorRecoveryStrategy.Abort,
+ IsRecovered = false,
+ Message = "自动恢复已禁用或错误不可重试"
+ };
+ }
+
+ ErrorRecoveryResult result;
+
+ if (_recoveryHandlers.TryGetValue(error.Type, out var handler))
+ {
+ result = handler(error);
+ }
+ else
+ {
+ result = HandleGenericError(error);
+ }
+
+ // 应用恢复策略
+ if (result.IsRecovered && result.Strategy == ErrorRecoveryStrategy.DelayedRetry)
+ {
+ var delay = result.Parameters.ContainsKey("DelayMs")
+ ? (int)result.Parameters["DelayMs"]
+ : error.RetryDelayMs;
+ await Task.Delay(delay);
+ }
+
+ // 更新错误状态
+ error.RetryCount++;
+
+ // 触发恢复事件
+ ErrorRecovered?.Invoke(error, result);
+
+ LogWrapper.Info(LogModule, $"错误恢复尝试: {error.Id}, 策略: {result.Strategy}, 成功: {result.IsRecovered}");
+
+ return result;
+ }
+
+ ///
+ /// 分析异常并创建下载错误
+ ///
+ private DownloadError AnalyzeException(Exception exception)
+ {
+ var error = new DownloadError
+ {
+ OriginalException = exception,
+ Message = exception.Message,
+ Details = exception.ToString()
+ };
+
+ // 分析异常类型
+ switch (exception)
+ {
+ case HttpRequestException httpEx:
+ error.Type = DownloadErrorType.HttpError;
+ error.Severity = ErrorSeverity.Error;
+ error.ErrorCode = "HTTP_REQUEST_FAILED";
+ AnalyzeHttpException(error, httpEx);
+ break;
+
+ case TaskCanceledException cancelEx when cancelEx.InnerException is TimeoutException:
+ error.Type = DownloadErrorType.Timeout;
+ error.Severity = ErrorSeverity.Warning;
+ error.ErrorCode = "TIMEOUT";
+ error.SuggestedSolutions.AddRange(new[]
+ {
+ "增加超时时间",
+ "检查网络连接",
+ "减少并发线程数"
+ });
+ break;
+
+ case OperationCanceledException:
+ error.Type = DownloadErrorType.Cancelled;
+ error.Severity = ErrorSeverity.Info;
+ error.ErrorCode = "OPERATION_CANCELLED";
+ error.IsRetryable = false;
+ break;
+
+ case UnauthorizedAccessException:
+ case System.Security.Authentication.AuthenticationException:
+ error.Type = DownloadErrorType.Authentication;
+ error.Severity = ErrorSeverity.Error;
+ error.ErrorCode = "AUTHENTICATION_FAILED";
+ error.SuggestedSolutions.AddRange(new[]
+ {
+ "检查访问凭据",
+ "验证用户权限",
+ "检查认证配置"
+ });
+ break;
+
+ case System.IO.IOException ioEx:
+ error.Type = DownloadErrorType.FileIo;
+ error.Severity = ErrorSeverity.Error;
+ error.ErrorCode = "FILE_IO_ERROR";
+ AnalyzeFileIOException(error, ioEx);
+ break;
+
+ case System.Net.NetworkInformation.NetworkInformationException:
+ case System.Net.Sockets.SocketException:
+ error.Type = DownloadErrorType.NetworkConnection;
+ error.Severity = ErrorSeverity.Error;
+ error.ErrorCode = "NETWORK_CONNECTION_FAILED";
+ error.SuggestedSolutions.AddRange(new[]
+ {
+ "检查网络连接",
+ "检查防火墙设置",
+ "尝试使用代理"
+ });
+ break;
+
+ case ArgumentException argEx:
+ error.Type = DownloadErrorType.Configuration;
+ error.Severity = ErrorSeverity.Error;
+ error.ErrorCode = "INVALID_CONFIGURATION";
+ error.Message = $"配置错误: {argEx.Message}";
+ error.IsRetryable = false;
+ break;
+
+ default:
+ error.Type = DownloadErrorType.Unknown;
+ error.Severity = ErrorSeverity.Error;
+ error.ErrorCode = "UNKNOWN_ERROR";
+ error.SuggestedSolutions.Add("查看详细错误信息以获取更多帮助");
+ break;
+ }
+
+ return error;
+ }
+
+ ///
+ /// 分析HTTP异常
+ ///
+ private void AnalyzeHttpException(DownloadError error, HttpRequestException httpEx)
+ {
+ if (httpEx.Message.Contains("404"))
+ {
+ error.ErrorCode = "HTTP_404_NOT_FOUND";
+ error.Message = "文件未找到";
+ error.IsRetryable = false;
+ error.SuggestedSolutions.Add("检查下载链接是否正确");
+ }
+ else if (httpEx.Message.Contains("403"))
+ {
+ error.ErrorCode = "HTTP_403_FORBIDDEN";
+ error.Message = "访问被禁止";
+ error.IsRetryable = false;
+ error.SuggestedSolutions.AddRange(new[]
+ {
+ "检查访问权限",
+ "检查User-Agent设置",
+ "检查访问频率限制"
+ });
+ }
+ else if (httpEx.Message.Contains("500"))
+ {
+ error.Type = DownloadErrorType.ServerError;
+ error.ErrorCode = "HTTP_500_SERVER_ERROR";
+ error.Message = "服务器内部错误";
+ error.SuggestedSolutions.AddRange(new[]
+ {
+ "稍后重试",
+ "联系服务器管理员",
+ "尝试其他下载源"
+ });
+ }
+ else if (httpEx.Message.Contains("502") || httpEx.Message.Contains("503"))
+ {
+ error.Type = DownloadErrorType.ServerError;
+ error.ErrorCode = "HTTP_SERVER_UNAVAILABLE";
+ error.Message = "服务器不可用";
+ error.RetryDelayMs = 5000; // 5秒后重试
+ }
+ }
+
+ ///
+ /// 分析文件IO异常
+ ///
+ private void AnalyzeFileIOException(DownloadError error, System.IO.IOException ioEx)
+ {
+ if (ioEx.Message.Contains("space") || ioEx.Message.Contains("disk"))
+ {
+ error.ErrorCode = "INSUFFICIENT_DISK_SPACE";
+ error.Message = "磁盘空间不足";
+ error.IsRetryable = false;
+ error.Severity = ErrorSeverity.Critical;
+ error.SuggestedSolutions.AddRange(new[]
+ {
+ "清理磁盘空间",
+ "选择其他存储位置",
+ "删除不必要的文件"
+ });
+ }
+ else if (ioEx.Message.Contains("access") || ioEx.Message.Contains("permission"))
+ {
+ error.ErrorCode = "FILE_ACCESS_DENIED";
+ error.Message = "文件访问被拒绝";
+ error.IsRetryable = false;
+ error.SuggestedSolutions.AddRange(new[]
+ {
+ "检查文件权限",
+ "以管理员身份运行",
+ "选择其他存储位置"
+ });
+ }
+ else if (ioEx.Message.Contains("use") || ioEx.Message.Contains("locked"))
+ {
+ error.ErrorCode = "FILE_IN_USE";
+ error.Message = "文件正在被使用";
+ error.RetryDelayMs = 2000; // 2秒后重试
+ error.SuggestedSolutions.AddRange(new[]
+ {
+ "关闭正在使用该文件的程序",
+ "稍后重试",
+ "选择不同的文件名"
+ });
+ }
+ }
+
+ ///
+ /// 记录错误到历史记录
+ ///
+ private void RecordError(DownloadError error)
+ {
+ lock (_lockObject)
+ {
+ _errorHistory.Add(error);
+
+ // 限制历史记录数量
+ while (_errorHistory.Count > MaxErrorHistoryCount)
+ {
+ _errorHistory.RemoveAt(0);
+ }
+ }
+ }
+
+ ///
+ /// 初始化默认恢复处理器
+ ///
+ private void InitializeDefaultRecoveryHandlers()
+ {
+ _recoveryHandlers[DownloadErrorType.NetworkConnection] = HandleNetworkError;
+ _recoveryHandlers[DownloadErrorType.HttpError] = HandleHttpError;
+ _recoveryHandlers[DownloadErrorType.ServerError] = HandleServerError;
+ _recoveryHandlers[DownloadErrorType.Timeout] = HandleTimeoutError;
+ _recoveryHandlers[DownloadErrorType.FileIo] = HandleFileIOError;
+ }
+
+ ///
+ /// 处理网络错误
+ ///
+ private ErrorRecoveryResult HandleNetworkError(DownloadError error)
+ {
+ if (error.RetryCount < error.MaxRetries)
+ {
+ return new ErrorRecoveryResult
+ {
+ Strategy = ErrorRecoveryStrategy.ExponentialBackoff,
+ IsRecovered = true,
+ Message = "网络错误,使用指数退避重试",
+ Parameters = new Dictionary
+ {
+ ["DelayMs"] = Math.Min(error.RetryDelayMs * (int)Math.Pow(2, error.RetryCount), 30000)
+ }
+ };
+ }
+
+ return new ErrorRecoveryResult
+ {
+ Strategy = ErrorRecoveryStrategy.Abort,
+ IsRecovered = false,
+ Message = "网络错误重试次数已达上限"
+ };
+ }
+
+ ///
+ /// 处理HTTP错误
+ ///
+ private ErrorRecoveryResult HandleHttpError(DownloadError error)
+ {
+ // 4xx错误通常不需要重试
+ if (error.ErrorCode.Contains("404") || error.ErrorCode.Contains("403"))
+ {
+ return new ErrorRecoveryResult
+ {
+ Strategy = ErrorRecoveryStrategy.Abort,
+ IsRecovered = false,
+ Message = "HTTP客户端错误,不需要重试"
+ };
+ }
+
+ // 其他HTTP错误可以重试
+ if (error.RetryCount < error.MaxRetries)
+ {
+ return new ErrorRecoveryResult
+ {
+ Strategy = ErrorRecoveryStrategy.DelayedRetry,
+ IsRecovered = true,
+ Message = "HTTP错误,延迟重试",
+ Parameters = new Dictionary
+ {
+ ["DelayMs"] = error.RetryDelayMs
+ }
+ };
+ }
+
+ return new ErrorRecoveryResult
+ {
+ Strategy = ErrorRecoveryStrategy.Abort,
+ IsRecovered = false,
+ Message = "HTTP错误重试次数已达上限"
+ };
+ }
+
+ ///
+ /// 处理服务器错误
+ ///
+ private ErrorRecoveryResult HandleServerError(DownloadError error)
+ {
+ if (error.RetryCount < error.MaxRetries)
+ {
+ return new ErrorRecoveryResult
+ {
+ Strategy = ErrorRecoveryStrategy.ExponentialBackoff,
+ IsRecovered = true,
+ Message = "服务器错误,使用指数退避重试",
+ Parameters = new Dictionary
+ {
+ ["DelayMs"] = Math.Min(5000 * (int)Math.Pow(2, error.RetryCount), 60000)
+ }
+ };
+ }
+
+ return new ErrorRecoveryResult
+ {
+ Strategy = ErrorRecoveryStrategy.Abort,
+ IsRecovered = false,
+ Message = "服务器错误重试次数已达上限"
+ };
+ }
+
+ ///
+ /// 处理超时错误
+ ///
+ private ErrorRecoveryResult HandleTimeoutError(DownloadError error)
+ {
+ if (error.RetryCount < error.MaxRetries)
+ {
+ // 超时后切换到单线程模式
+ return new ErrorRecoveryResult
+ {
+ Strategy = ErrorRecoveryStrategy.Fallback,
+ IsRecovered = true,
+ Message = "超时错误,切换到单线程模式",
+ Parameters = new Dictionary
+ {
+ ["ThreadCount"] = 1,
+ ["TimeoutMs"] = (int)(error.Context.ContainsKey("TimeoutMs") ?
+ (int)error.Context["TimeoutMs"] * 1.5 : 45000)
+ }
+ };
+ }
+
+ return new ErrorRecoveryResult
+ {
+ Strategy = ErrorRecoveryStrategy.Abort,
+ IsRecovered = false,
+ Message = "超时错误重试次数已达上限"
+ };
+ }
+
+ ///
+ /// 处理文件IO错误
+ ///
+ private ErrorRecoveryResult HandleFileIOError(DownloadError error)
+ {
+ // 磁盘空间不足或权限错误不需要重试
+ if (error.ErrorCode.Contains("DISK_SPACE") || error.ErrorCode.Contains("ACCESS_DENIED"))
+ {
+ return new ErrorRecoveryResult
+ {
+ Strategy = ErrorRecoveryStrategy.Abort,
+ IsRecovered = false,
+ Message = "文件系统错误,无法自动恢复"
+ };
+ }
+
+ // 文件被占用可以延迟重试
+ if (error.ErrorCode.Contains("FILE_IN_USE") && error.RetryCount < error.MaxRetries)
+ {
+ return new ErrorRecoveryResult
+ {
+ Strategy = ErrorRecoveryStrategy.DelayedRetry,
+ IsRecovered = true,
+ Message = "文件被占用,延迟重试",
+ Parameters = new Dictionary
+ {
+ ["DelayMs"] = 3000 // 3秒
+ }
+ };
+ }
+
+ return new ErrorRecoveryResult
+ {
+ Strategy = ErrorRecoveryStrategy.Abort,
+ IsRecovered = false,
+ Message = "文件IO错误无法恢复"
+ };
+ }
+
+ ///
+ /// 处理一般性错误
+ ///
+ private ErrorRecoveryResult HandleGenericError(DownloadError error)
+ {
+ if (error.RetryCount < error.MaxRetries)
+ {
+ return new ErrorRecoveryResult
+ {
+ Strategy = ErrorRecoveryStrategy.DelayedRetry,
+ IsRecovered = true,
+ Message = "一般错误,延迟重试",
+ Parameters = new Dictionary
+ {
+ ["DelayMs"] = error.RetryDelayMs
+ }
+ };
+ }
+
+ return new ErrorRecoveryResult
+ {
+ Strategy = ErrorRecoveryStrategy.Abort,
+ IsRecovered = false,
+ Message = "错误重试次数已达上限"
+ };
+ }
+
+ ///
+ /// 获取错误统计信息
+ ///
+ /// 错误统计
+ public Dictionary GetErrorStatistics()
+ {
+ lock (_lockObject)
+ {
+ var stats = new Dictionary();
+
+ foreach (DownloadErrorType errorType in Enum.GetValues())
+ {
+ stats[errorType] = 0;
+ }
+
+ foreach (var error in _errorHistory)
+ {
+ stats[error.Type]++;
+ }
+
+ return stats;
+ }
+ }
+
+ ///
+ /// 清理错误历史记录
+ ///
+ /// 清理指定时间之前的记录
+ public void ClearErrorHistory(DateTime? olderThan = null)
+ {
+ lock (_lockObject)
+ {
+ if (olderThan.HasValue)
+ {
+ _errorHistory.RemoveAll(e => e.Timestamp < olderThan.Value);
+ }
+ else
+ {
+ _errorHistory.Clear();
+ }
+ }
+
+ LogWrapper.Info(LogModule, $"已清理错误历史记录");
+ }
+}
diff --git a/Net/DownloadMonitor.cs b/Net/DownloadMonitor.cs
new file mode 100644
index 00000000..8849f0f6
--- /dev/null
+++ b/Net/DownloadMonitor.cs
@@ -0,0 +1,642 @@
+using System;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using PCL.Core.App.Tasks;
+using PCL.Core.Logging;
+
+namespace PCL.Core.Net;
+
+///
+/// 下载性能指标
+///
+public class DownloadMetrics
+{
+ public DateTime Timestamp { get; set; } = DateTime.Now;
+ public long BytesPerSecond { get; set; }
+ public int ActiveConnections { get; set; }
+ public double CpuUsagePercent { get; set; }
+ public long MemoryUsageBytes { get; set; }
+ public int RetryCount { get; set; }
+ public TimeSpan ResponseTime { get; set; }
+}
+
+///
+/// 下载健康状态
+///
+public enum DownloadHealth
+{
+ Excellent, // > 80% 预期速度
+ Good, // 50-80% 预期速度
+ Fair, // 20-50% 预期速度
+ Poor, // < 20% 预期速度
+ Critical // 连接失败或严重错误
+}
+
+///
+/// 下载诊断信息
+///
+public class DownloadDiagnosis
+{
+ public DownloadHealth Health { get; set; }
+ public string HealthDescription { get; set; } = string.Empty;
+ public List Issues { get; set; } = new();
+ public List Recommendations { get; set; } = new();
+ public double EfficiencyScore { get; set; } // 0-100
+}
+
+///
+/// 下载监控器
+/// 提供实时监控、性能分析和诊断功能
+///
+public class DownloadMonitor : IDisposable
+{
+ private const string LogModule = "DownloadMonitor";
+
+ private readonly ConcurrentQueue _metricsHistory = new();
+ private readonly ConcurrentDictionary _activeDownloads = new();
+ private readonly Timer _monitoringTimer;
+ private readonly object _lockObject = new();
+ private bool _disposed = false;
+
+ ///
+ /// 监控间隔(毫秒)
+ ///
+ public int MonitoringInterval { get; set; } = 1000;
+
+ ///
+ /// 性能指标历史记录保留数量
+ ///
+ public int MaxHistoryCount { get; set; } = 300; // 5分钟历史
+
+ ///
+ /// 启用详细监控
+ ///
+ public bool EnableDetailedMonitoring { get; set; } = true;
+
+ ///
+ /// 性能警告阈值(字节/秒)
+ ///
+ public long PerformanceWarningThreshold { get; set; } = 100 * 1024; // 100KB/s
+
+ ///
+ /// 性能指标历史
+ ///
+ public IReadOnlyCollection MetricsHistory => _metricsHistory.ToArray();
+
+ ///
+ /// 当前活动下载数量
+ ///
+ public int ActiveDownloadCount => _activeDownloads.Count;
+
+ ///
+ /// 性能警告事件
+ ///
+ public event Action? PerformanceWarning;
+
+ ///
+ /// 下载完成事件
+ ///
+ public event Action? DownloadCompleted;
+
+ public DownloadMonitor()
+ {
+ _monitoringTimer = new Timer(MonitorPerformance, null,
+ TimeSpan.FromMilliseconds(MonitoringInterval),
+ TimeSpan.FromMilliseconds(MonitoringInterval));
+
+ LogWrapper.Info(LogModule, "下载监控器已启动");
+ }
+
+ ///
+ /// 注册下载任务进行监控
+ ///
+ /// 任务ID
+ /// 下载任务
+ public void RegisterDownload(string taskId, EnhancedMultiThreadDownloadTask task)
+ {
+ if (_disposed) return;
+
+ var stats = task.GetDetailedStatus();
+ _activeDownloads[taskId] = stats;
+
+ // 订阅任务状态变化
+ task.StateChanged += (sender, oldState, newState) =>
+ {
+ if (newState is TaskState.Completed or TaskState.Failed or TaskState.Canceled)
+ {
+ OnDownloadCompleted(taskId, task);
+ }
+ };
+
+ LogWrapper.Info(LogModule, $"注册下载任务: {taskId}");
+ }
+
+ ///
+ /// 注销下载任务
+ ///
+ /// 任务ID
+ public void UnregisterDownload(string taskId)
+ {
+ if (_activeDownloads.TryRemove(taskId, out var stats))
+ {
+ LogWrapper.Info(LogModule, $"注销下载任务: {taskId}");
+ }
+ }
+
+ ///
+ /// 获取总体统计信息
+ ///
+ /// 总体统计
+ public (long TotalBytes, long DownloadedBytes, double AverageSpeed, int ActiveTasks) GetOverallStatistics()
+ {
+ lock (_lockObject)
+ {
+ var stats = _activeDownloads.Values.ToArray();
+ var totalBytes = stats.Sum(s => s.TotalBytes);
+ var downloadedBytes = stats.Sum(s => s.DownloadedBytes);
+ var averageSpeed = stats.Where(s => s.CurrentSpeed > 0).DefaultIfEmpty().Average(s => s.CurrentSpeed);
+
+ return (totalBytes, downloadedBytes, averageSpeed, stats.Length);
+ }
+ }
+
+ ///
+ /// 获取性能分析报告
+ ///
+ /// 性能报告
+ public PerformanceReport GetPerformanceReport()
+ {
+ lock (_lockObject)
+ {
+ var metrics = _metricsHistory.ToArray();
+ var stats = _activeDownloads.Values.ToArray();
+
+ return new PerformanceReport
+ {
+ GeneratedAt = DateTime.Now,
+ ActiveDownloads = stats.Length,
+ TotalBytesTransferred = stats.Sum(s => s.DownloadedBytes),
+ AverageSpeed = metrics.Where(m => m.BytesPerSecond > 0).DefaultIfEmpty().Average(m => m.BytesPerSecond),
+ PeakSpeed = metrics.DefaultIfEmpty().Max(m => m?.BytesPerSecond ?? 0),
+ TotalRetries = stats.Sum(s => s.RetryCount),
+ AverageConnections = metrics.Where(m => m.ActiveConnections > 0).DefaultIfEmpty().Average(m => m.ActiveConnections),
+ OverallHealth = CalculateOverallHealth(stats),
+ Recommendations = GenerateRecommendations(stats, metrics)
+ };
+ }
+ }
+
+ ///
+ /// 诊断下载性能
+ ///
+ /// 任务ID
+ /// 诊断结果
+ public DownloadDiagnosis DiagnoseDownload(string taskId)
+ {
+ if (!_activeDownloads.TryGetValue(taskId, out var stats))
+ {
+ return new DownloadDiagnosis
+ {
+ Health = DownloadHealth.Critical,
+ HealthDescription = "下载任务未找到",
+ EfficiencyScore = 0
+ };
+ }
+
+ var diagnosis = new DownloadDiagnosis();
+ var issues = new List();
+ var recommendations = new List();
+
+ // 速度分析
+ var expectedSpeed = EstimateExpectedSpeed(stats.TotalBytes);
+ var speedRatio = stats.CurrentSpeed / expectedSpeed;
+
+ if (speedRatio >= 0.8)
+ {
+ diagnosis.Health = DownloadHealth.Excellent;
+ diagnosis.HealthDescription = "下载速度优秀";
+ }
+ else if (speedRatio >= 0.5)
+ {
+ diagnosis.Health = DownloadHealth.Good;
+ diagnosis.HealthDescription = "下载速度良好";
+ }
+ else if (speedRatio >= 0.2)
+ {
+ diagnosis.Health = DownloadHealth.Fair;
+ diagnosis.HealthDescription = "下载速度一般";
+ issues.Add("下载速度低于预期");
+ recommendations.Add("考虑增加线程数或检查网络连接");
+ }
+ else
+ {
+ diagnosis.Health = DownloadHealth.Poor;
+ diagnosis.HealthDescription = "下载速度较差";
+ issues.Add("下载速度严重低于预期");
+ recommendations.Add("检查网络连接、增加重试次数或更换下载源");
+ }
+
+ // 重试分析
+ if (stats.RetryCount > 10)
+ {
+ issues.Add($"重试次数过多: {stats.RetryCount}");
+ recommendations.Add("检查网络稳定性或服务器可靠性");
+ }
+
+ // 效率分析
+ var progressRate = stats.ProgressPercentage / stats.ElapsedTime.TotalMinutes;
+ diagnosis.EfficiencyScore = Math.Min(100, speedRatio * 100);
+
+ // 连接分析
+ if (stats.ActiveThreads == 1 && stats.TotalBytes > 10 * 1024 * 1024) // 大于10MB
+ {
+ issues.Add("大文件使用单线程下载");
+ recommendations.Add("检查服务器是否支持分块下载");
+ }
+
+ diagnosis.Issues = issues;
+ diagnosis.Recommendations = recommendations;
+
+ return diagnosis;
+ }
+
+ ///
+ /// 获取实时性能指标
+ ///
+ /// 当前性能指标
+ public DownloadMetrics GetCurrentMetrics()
+ {
+ lock (_lockObject)
+ {
+ var stats = _activeDownloads.Values.ToArray();
+
+ return new DownloadMetrics
+ {
+ Timestamp = DateTime.Now,
+ BytesPerSecond = (long)stats.Sum(s => s.CurrentSpeed),
+ ActiveConnections = stats.Sum(s => s.ActiveThreads),
+ RetryCount = stats.Sum(s => s.RetryCount),
+ CpuUsagePercent = GetCpuUsage(),
+ MemoryUsageBytes = GetMemoryUsage()
+ };
+ }
+ }
+
+ ///
+ /// 监控性能(定时器回调)
+ ///
+ private void MonitorPerformance(object? state)
+ {
+ if (_disposed) return;
+
+ try
+ {
+ var currentMetrics = GetCurrentMetrics();
+
+ // 添加到历史记录
+ _metricsHistory.Enqueue(currentMetrics);
+
+ // 限制历史记录数量
+ while (_metricsHistory.Count > MaxHistoryCount)
+ {
+ _metricsHistory.TryDequeue(out _);
+ }
+
+ // 检查性能警告
+ CheckPerformanceWarnings(currentMetrics);
+
+ // 更新活动下载统计
+ UpdateActiveDownloadStats();
+ }
+ catch (Exception ex)
+ {
+ LogWrapper.Error(ex, LogModule, "性能监控出错");
+ }
+ }
+
+ ///
+ /// 更新活动下载统计信息
+ ///
+ private void UpdateActiveDownloadStats()
+ {
+ // 这里可以从实际的下载任务中更新统计信息
+ // TODO:暂时保持空实现,实际使用时需要与具体的下载任务集成
+ }
+
+ ///
+ /// 检查性能警告
+ ///
+ private void CheckPerformanceWarnings(DownloadMetrics metrics)
+ {
+ if (!EnableDetailedMonitoring) return;
+
+ var issues = new List();
+ var recommendations = new List();
+ var health = DownloadHealth.Good;
+
+ // 检查下载速度
+ if (metrics.BytesPerSecond > 0 && metrics.BytesPerSecond < PerformanceWarningThreshold)
+ {
+ health = DownloadHealth.Poor;
+ issues.Add($"下载速度过低: {metrics.BytesPerSecond / 1024:F1} KB/s");
+ recommendations.Add("检查网络连接或增加并发数");
+ }
+
+ // 检查CPU使用率
+ if (metrics.CpuUsagePercent > 80)
+ {
+ issues.Add($"CPU使用率过高: {metrics.CpuUsagePercent:F1}%");
+ recommendations.Add("考虑减少并发线程数");
+ }
+
+ // 检查内存使用
+ if (metrics.MemoryUsageBytes > 500 * 1024 * 1024) // 500MB
+ {
+ issues.Add($"内存使用过高: {metrics.MemoryUsageBytes / 1024 / 1024:F1} MB");
+ recommendations.Add("检查是否有内存泄漏或减少缓冲区大小");
+ }
+
+ if (issues.Any())
+ {
+ var diagnosis = new DownloadDiagnosis
+ {
+ Health = health,
+ HealthDescription = "检测到性能问题",
+ Issues = issues,
+ Recommendations = recommendations,
+ EfficiencyScore = CalculateEfficiencyScore(metrics)
+ };
+
+ PerformanceWarning?.Invoke(diagnosis);
+ }
+ }
+
+ ///
+ /// 计算效率分数
+ ///
+ private double CalculateEfficiencyScore(DownloadMetrics metrics)
+ {
+ var score = 100.0;
+
+ // 速度评分
+ if (metrics.BytesPerSecond < PerformanceWarningThreshold)
+ score -= 30;
+
+ // CPU评分
+ if (metrics.CpuUsagePercent > 80)
+ score -= 20;
+ else if (metrics.CpuUsagePercent > 60)
+ score -= 10;
+
+ // 内存评分
+ if (metrics.MemoryUsageBytes > 500 * 1024 * 1024)
+ score -= 20;
+ else if (metrics.MemoryUsageBytes > 200 * 1024 * 1024)
+ score -= 10;
+
+ return Math.Max(0, score);
+ }
+
+ ///
+ /// 下载完成处理
+ ///
+ private void OnDownloadCompleted(string taskId, EnhancedMultiThreadDownloadTask task)
+ {
+ if (_activeDownloads.TryRemove(taskId, out var stats))
+ {
+ var finalStats = task.GetDetailedStatus();
+ DownloadCompleted?.Invoke(taskId, finalStats);
+
+ LogWrapper.Info(LogModule, $"下载完成: {taskId}, " +
+ $"平均速度: {finalStats.AverageSpeed / 1024 / 1024:F2} MB/s, " +
+ $"重试次数: {finalStats.RetryCount}");
+ }
+ }
+
+ ///
+ /// 估算预期下载速度
+ ///
+ private double EstimateExpectedSpeed(long fileSize)
+ {
+ // 基于文件大小的简单估算,实际应用中可以基于历史数据
+ if (fileSize < 1024 * 1024) // < 1MB
+ return 500 * 1024; // 500KB/s
+ else if (fileSize < 100 * 1024 * 1024) // < 100MB
+ return 2 * 1024 * 1024; // 2MB/s
+ else
+ return 5 * 1024 * 1024; // 5MB/s
+ }
+
+ ///
+ /// 计算整体健康状态
+ ///
+ private DownloadHealth CalculateOverallHealth(DownloadStatistics[] stats)
+ {
+ if (!stats.Any()) return DownloadHealth.Good;
+
+ var avgSpeed = stats.Average(s => s.CurrentSpeed);
+ var totalRetries = stats.Sum(s => s.RetryCount);
+ var avgProgress = stats.Average(s => s.ProgressPercentage);
+
+ if (avgSpeed > 2 * 1024 * 1024 && totalRetries < 10) // > 2MB/s, < 10 retries
+ return DownloadHealth.Excellent;
+ else if (avgSpeed > 1024 * 1024 && totalRetries < 20) // > 1MB/s, < 20 retries
+ return DownloadHealth.Good;
+ else if (avgSpeed > 512 * 1024) // > 512KB/s
+ return DownloadHealth.Fair;
+ else
+ return DownloadHealth.Poor;
+ }
+
+ ///
+ /// 生成性能建议
+ ///
+ private List GenerateRecommendations(DownloadStatistics[] stats, DownloadMetrics[] metrics)
+ {
+ var recommendations = new List();
+
+ if (!stats.Any()) return recommendations;
+
+ var avgSpeed = stats.Average(s => s.CurrentSpeed);
+ var totalRetries = stats.Sum(s => s.RetryCount);
+ var avgThreads = stats.Average(s => s.ActiveThreads);
+
+ if (avgSpeed < 1024 * 1024) // < 1MB/s
+ {
+ recommendations.Add("考虑增加下载线程数以提高速度");
+ }
+
+ if (totalRetries > 50)
+ {
+ recommendations.Add("重试次数较多,建议检查网络稳定性");
+ }
+
+ if (avgThreads < 2 && stats.Any(s => s.TotalBytes > 10 * 1024 * 1024))
+ {
+ recommendations.Add("大文件建议使用多线程下载");
+ }
+
+ if (metrics.Any(m => m.CpuUsagePercent > 70))
+ {
+ recommendations.Add("CPU使用率较高,可适当减少并发数");
+ }
+
+ return recommendations;
+ }
+
+ private static readonly PerformanceCounter? _cpuCounter;
+ private static readonly PerformanceCounter? _memoryCounter;
+ private static readonly Process _currentProcess = Process.GetCurrentProcess();
+ private DateTime _lastCpuTime = DateTime.UtcNow;
+ private TimeSpan _lastProcessorTime = _currentProcess.TotalProcessorTime;
+
+ static DownloadMonitor()
+ {
+ try
+ {
+ _cpuCounter = new PerformanceCounter("Processor", "% Processor Time", "_Total");
+ _memoryCounter = new PerformanceCounter("Memory", "Available MBytes");
+ // Initialize CPU counter (first call returns 0)
+ _cpuCounter.NextValue();
+ }
+ catch (Exception ex)
+ {
+ LogWrapper.Warn("DownloadMonitor", $"无法初始化性能计数器: {ex.Message}");
+ }
+ }
+
+ ///
+ /// 获取CPU使用率
+ ///
+ private double GetCpuUsage()
+ {
+ try
+ {
+ // 先使用方法1:使用PerformanceCounter(Windows系统)
+ if (_cpuCounter != null)
+ {
+ return _cpuCounter.NextValue();
+ }
+
+ // 再使用方法2:手动计算当前进程CPU使用率
+ var currentTime = DateTime.UtcNow;
+ var currentProcessorTime = _currentProcess.TotalProcessorTime;
+
+ var cpuUsedMs = (currentProcessorTime - _lastProcessorTime).TotalMilliseconds;
+ var totalMsPassed = (currentTime - _lastCpuTime).TotalMilliseconds;
+ var cpuUsageTotal = cpuUsedMs / (Environment.ProcessorCount * totalMsPassed);
+
+ _lastCpuTime = currentTime;
+ _lastProcessorTime = currentProcessorTime;
+
+ return Math.Max(0, Math.Min(100, cpuUsageTotal * 100));
+ }
+ catch (Exception ex)
+ {
+ LogWrapper.Debug("DownloadMonitor", $"获取CPU使用率失败: {ex.Message}");
+ return 0.0;
+ }
+ }
+
+ ///
+ /// 获取内存使用量
+ ///
+ private long GetMemoryUsage()
+ {
+ try
+ {
+ // 获取当前进程的工作集内存
+ _currentProcess.Refresh();
+ var workingSet = _currentProcess.WorkingSet64;
+
+ // 获取.NET管理的内存
+ var managedMemory = GC.GetTotalMemory(false);
+
+ // 获取进程私有内存
+ var privateMemory = _currentProcess.PrivateMemorySize64;
+
+ // 返回工作集内存(实际物理内存使用量)
+ return workingSet;
+ }
+ catch (Exception ex)
+ {
+ LogWrapper.Debug("DownloadMonitor", $"获取内存使用量失败: {ex.Message}");
+ // 降级到GC内存统计
+ return GC.GetTotalMemory(false);
+ }
+ }
+
+ ///
+ /// 获取详细内存信息
+ ///
+ public (long WorkingSet, long PrivateMemory, long ManagedMemory, long AvailableMemory) GetDetailedMemoryInfo()
+ {
+ try
+ {
+ _currentProcess.Refresh();
+ var workingSet = _currentProcess.WorkingSet64;
+ var privateMemory = _currentProcess.PrivateMemorySize64;
+ var managedMemory = GC.GetTotalMemory(false);
+
+ long availableMemory = 0;
+ if (_memoryCounter != null)
+ {
+ availableMemory = (long)_memoryCounter.NextValue() * 1024 * 1024; // Convert MB to bytes
+ }
+
+ return (workingSet, privateMemory, managedMemory, availableMemory);
+ }
+ catch (Exception ex)
+ {
+ LogWrapper.Debug("DownloadMonitor", $"获取详细内存信息失败: {ex.Message}");
+ var managedMemory = GC.GetTotalMemory(false);
+ return (managedMemory, managedMemory, managedMemory, 0);
+ }
+ }
+
+ ///
+ /// 释放资源
+ ///
+ public void Dispose()
+ {
+ if (_disposed) return;
+
+ _monitoringTimer?.Dispose();
+ _activeDownloads.Clear();
+ _disposed = true;
+
+ LogWrapper.Info(LogModule, "下载监控器已释放");
+ GC.SuppressFinalize(this);
+ }
+}
+
+///
+/// 性能报告
+///
+public class PerformanceReport
+{
+ public DateTime GeneratedAt { get; set; }
+ public int ActiveDownloads { get; set; }
+ public long TotalBytesTransferred { get; set; }
+ public double AverageSpeed { get; set; }
+ public long PeakSpeed { get; set; }
+ public int TotalRetries { get; set; }
+ public double AverageConnections { get; set; }
+ public DownloadHealth OverallHealth { get; set; }
+ public List Recommendations { get; set; } = new();
+
+ public override string ToString()
+ {
+ return $"性能报告 ({GeneratedAt:yyyy-MM-dd HH:mm:ss})\n" +
+ $"活动下载: {ActiveDownloads}\n" +
+ $"总传输: {TotalBytesTransferred / 1024 / 1024:F1} MB\n" +
+ $"平均速度: {AverageSpeed / 1024 / 1024:F2} MB/s\n" +
+ $"峰值速度: {PeakSpeed / 1024 / 1024:F2} MB/s\n" +
+ $"总重试: {TotalRetries}\n" +
+ $"整体健康: {OverallHealth}";
+ }
+}
diff --git a/Net/EnhancedMultiThreadDownloadTask.cs b/Net/EnhancedMultiThreadDownloadTask.cs
new file mode 100644
index 00000000..ab4c3a1c
--- /dev/null
+++ b/Net/EnhancedMultiThreadDownloadTask.cs
@@ -0,0 +1,799 @@
+using System;
+using System.Buffers;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Net.Http;
+using System.Net.Http.Headers;
+using System.Threading;
+using System.Threading.Tasks;
+using PCL.Core.App.Tasks;
+using PCL.Core.Logging;
+
+namespace PCL.Core.Net;
+
+
+///
+/// 下载统计信息
+///
+public class DownloadStatistics
+{
+ public DateTime StartTime { get; set; }
+ public DateTime? EndTime { get; set; }
+ public long TotalBytes { get; set; }
+ public long DownloadedBytes { get; set; }
+ public double CurrentSpeed { get; set; } // bytes/second
+ public double AverageSpeed { get; set; } // bytes/second
+ public double PeakSpeed { get; set; } // bytes/second
+ public int ActiveThreads { get; set; }
+ public int RetryCount { get; set; }
+ public TimeSpan ElapsedTime => (EndTime ?? DateTime.Now) - StartTime;
+ public double ProgressPercentage => TotalBytes > 0 ? (double)DownloadedBytes / TotalBytes * 100 : 0;
+ public TimeSpan EstimatedTimeRemaining => CurrentSpeed > 0 ?
+ TimeSpan.FromSeconds((TotalBytes - DownloadedBytes) / CurrentSpeed) : TimeSpan.Zero;
+}
+
+///
+/// 速度控制器
+///
+public class SpeedController
+{
+ private readonly long _speedLimit;
+ private readonly SemaphoreSlim _semaphore;
+ private DateTime _lastCheck = DateTime.Now;
+ private long _bytesInCurrentSecond = 0;
+
+ public SpeedController(long speedLimit)
+ {
+ _speedLimit = speedLimit;
+ _semaphore = new SemaphoreSlim(1, 1);
+ }
+
+ public async Task CanTransfer(int bytes, CancellationToken cancellationToken)
+ {
+ if (_speedLimit <= 0) return true;
+
+ await _semaphore.WaitAsync(cancellationToken);
+ try
+ {
+ var now = DateTime.Now;
+ var elapsed = (now - _lastCheck).TotalMilliseconds;
+
+ if (elapsed >= 1000) // Reset every second
+ {
+ _lastCheck = now;
+ _bytesInCurrentSecond = 0;
+ }
+
+ if (_bytesInCurrentSecond + bytes <= _speedLimit)
+ {
+ _bytesInCurrentSecond += bytes;
+ return true;
+ }
+
+ // Need to wait
+ var remainingTime = 1000 - elapsed;
+ if (remainingTime > 0)
+ {
+ await Task.Delay((int)remainingTime, cancellationToken);
+ _lastCheck = DateTime.Now;
+ _bytesInCurrentSecond = bytes;
+ }
+
+ return true;
+ }
+ finally
+ {
+ _semaphore.Release();
+ }
+ }
+}
+
+///
+/// 多线程下载任务
+/// 支持断点续传、速度限制、连接池复用等高级功能
+///
+public class EnhancedMultiThreadDownloadTask : IObservableTaskStateSource, IObservableProgressSource
+{
+ private const string LogModule = "EnhancedMultiThreadDownload";
+
+ private readonly Uri _sourceUri;
+ private readonly string _targetPath;
+ private readonly string _tempPath;
+ private readonly DownloadConfiguration _config;
+ private readonly DownloadStatistics _stats = new();
+ private readonly SpeedController _speedController;
+ private readonly ArrayPool _bufferPool = ArrayPool.Shared;
+
+ private long _totalSize = 0;
+ private long _totalDownloaded = 0;
+ private readonly ConcurrentQueue _pendingChunks = new();
+ private readonly ConcurrentDictionary _activeChunks = new();
+ private readonly object _statsLock = new();
+ private CancellationTokenSource? _internalCts;
+ private Timer? _speedCalculationTimer;
+
+ // 性能计数器
+ private long _lastSpeedCheck = 0;
+ private DateTime _lastSpeedTime = DateTime.Now;
+
+ public DownloadStatistics Statistics => _stats;
+
+ // 接口实现所需的字段和属性
+ private TaskState _state = TaskState.Waiting;
+ private double _progress = 0.0;
+ private readonly CancellationToken? _cancellationToken;
+
+ // 接口实现 - IObservableTaskStateSource
+ public string Name { get; }
+ public string? Description { get; }
+
+ public TaskState State
+ {
+ get => _state;
+ private set
+ {
+ var oldState = _state;
+ _state = value;
+ StateChanged?.Invoke(this, oldState, value);
+ }
+ }
+
+ // 接口实现 - IObservableProgressSource
+ public double Progress
+ {
+ get => _progress;
+ private set
+ {
+ var oldProgress = _progress;
+ _progress = Math.Max(0, Math.Min(1, value));
+ ProgressChanged?.Invoke(this, oldProgress, _progress);
+ }
+ }
+
+ // 接口实现 - 事件
+ public event StateChangedHandler? StateChanged;
+ public event StateChangedHandler? ProgressChanged;
+
+ // 显式接口实现
+ event StateChangedHandler? IStateChangedSource.StateChanged
+ {
+ add => StateChanged += value;
+ remove => StateChanged -= value;
+ }
+
+ event StateChangedHandler? IStateChangedSource.StateChanged
+ {
+ add => ProgressChanged += value;
+ remove => ProgressChanged -= value;
+ }
+
+ ///
+ /// 创建多线程下载任务
+ ///
+ public EnhancedMultiThreadDownloadTask(
+ Uri sourceUri,
+ string targetPath,
+ DownloadConfiguration? config = null,
+ CancellationToken? cancellationToken = null)
+ {
+ _sourceUri = sourceUri ?? throw new ArgumentNullException(nameof(sourceUri));
+ _targetPath = Path.GetFullPath(targetPath);
+ _tempPath = _targetPath + ".download";
+ _config = config ?? new DownloadConfiguration();
+ _speedController = new SpeedController(_config.SpeedLimit);
+ _cancellationToken = cancellationToken;
+
+ // 设置任务信息
+ Name = $"下载 {Path.GetFileName(targetPath)}";
+ Description = $"从 {sourceUri} 下载到 {targetPath}";
+
+ // 验证配置
+ ValidateConfiguration();
+
+ LogWrapper.Info(LogModule, $"创建下载任务: {Name}, 配置: {_config.ThreadCount}线程, {_config.ChunkSize / 1024 / 1024}MB分块");
+ }
+
+ private void ValidateConfiguration()
+ {
+ _config.ThreadCount = Math.Max(1, Math.Min(_config.ThreadCount, 32));
+ _config.ChunkSize = Math.Max(64 * 1024, _config.ChunkSize); // 最小64KB
+ _config.BufferSize = Math.Max(4096, _config.BufferSize); // 最小4KB
+ _config.MaxRetries = Math.Max(0, _config.MaxRetries);
+ _config.TimeoutMs = Math.Max(5000, _config.TimeoutMs); // 最小5秒
+ }
+
+ ///
+ /// 执行下载任务
+ ///
+ public async Task RunAsync()
+ {
+ if (State != TaskState.Waiting)
+ throw new InvalidOperationException($"任务已执行,当前状态: {State}");
+
+ State = TaskState.Running;
+ Progress = 0;
+ _stats.StartTime = DateTime.Now;
+
+ try
+ {
+ _internalCts = CancellationTokenSource.CreateLinkedTokenSource(_cancellationToken ?? System.Threading.CancellationToken.None);
+ var token = _internalCts.Token;
+
+ LogWrapper.Info(LogModule, $"开始下载: {_sourceUri} -> {_targetPath}");
+
+ // 启动速度计算定时器
+ StartSpeedCalculationTimer();
+
+ // 1. 检查断点续传
+ Progress = 0.02;
+ var resumeInfo = await CheckResumeCapabilityAsync(token);
+
+ // 2. 获取文件信息
+ Progress = 0.05;
+ var fileInfo = await GetFileInfoAsync(token);
+
+ if (!fileInfo.SupportsRange || fileInfo.ContentLength <= _config.ChunkSize)
+ {
+ LogWrapper.Info(LogModule, "使用单线程下载模式");
+ return await DownloadSingleThreadAsync(token);
+ }
+
+ _totalSize = fileInfo.ContentLength;
+ _stats.TotalBytes = _totalSize;
+
+ // 3. 处理断点续传或创建新下载
+ Progress = 0.08;
+ if (resumeInfo.CanResume && resumeInfo.ExistingSize > 0)
+ {
+ LogWrapper.Info(LogModule, $"断点续传: 已下载 {resumeInfo.ExistingSize:N0} / {_totalSize:N0} bytes");
+ Interlocked.Exchange(ref _totalDownloaded, resumeInfo.ExistingSize);
+ CreateResumeChunks(resumeInfo.ExistingSize, _totalSize);
+ }
+ else
+ {
+ CreateDownloadChunks(_totalSize);
+ PrepareTargetFile();
+ }
+
+ // 4. 执行多线程下载
+ Progress = 0.1;
+ await DownloadMultiThreadAsync(token);
+
+ // 5. 验证和完成
+ Progress = 0.95;
+ await FinalizeDownloadAsync(token);
+
+ Progress = 1.0;
+ State = TaskState.Completed;
+ _stats.EndTime = DateTime.Now;
+
+ var result = CreateSuccessResult();
+ LogWrapper.Info(LogModule, $"下载完成: 大小={_totalSize:N0} bytes, 耗时={_stats.ElapsedTime.TotalSeconds:F1}s, 平均速度={result.AverageSpeed / 1024 / 1024:F2}MB/s");
+ return result;
+ }
+ catch (OperationCanceledException)
+ {
+ State = TaskState.Canceled;
+ LogWrapper.Info(LogModule, $"下载已取消: {_targetPath}");
+ return CreateCancelledResult();
+ }
+ catch (Exception ex)
+ {
+ State = TaskState.Failed;
+ LogWrapper.Error(ex, LogModule, $"下载失败: {_targetPath}");
+ return CreateFailedResult(ex);
+ }
+ finally
+ {
+ _speedCalculationTimer?.Dispose();
+ _internalCts?.Dispose();
+ }
+ }
+
+ ///
+ /// 检查断点续传能力
+ ///
+ private async Task<(bool CanResume, long ExistingSize)> CheckResumeCapabilityAsync(CancellationToken token)
+ {
+ if (!_config.EnableResumeSupport)
+ return (false, 0);
+
+ try
+ {
+ if (File.Exists(_tempPath))
+ {
+ var existingSize = new FileInfo(_tempPath).Length;
+ if (existingSize > 0)
+ {
+ // 验证服务器是否支持Range请求
+ using var client = GetHttpClient();
+ using var request = new HttpRequestMessage(HttpMethod.Head, _sourceUri);
+ using var response = await client.SendAsync(request, token);
+
+ var supportsRange = response.Headers.AcceptRanges?.Contains("bytes") == true;
+ if (supportsRange)
+ {
+ LogWrapper.Info(LogModule, $"检测到断点续传文件: {existingSize:N0} bytes");
+ return (true, existingSize);
+ }
+ }
+ }
+ else if (File.Exists(_targetPath))
+ {
+ // 目标文件已存在,检查是否完整
+ var existingSize = new FileInfo(_targetPath).Length;
+ using var client = GetHttpClient();
+ using var request = new HttpRequestMessage(HttpMethod.Head, _sourceUri);
+ using var response = await client.SendAsync(request, token);
+
+ var serverSize = response.Content.Headers.ContentLength ?? 0;
+ if (existingSize == serverSize)
+ {
+ LogWrapper.Info(LogModule, "文件已完整存在,跳过下载");
+ return (true, existingSize);
+ }
+ }
+ }
+ catch (Exception ex)
+ {
+ LogWrapper.Warn(LogModule, $"检查断点续传失败: {ex.Message}");
+ }
+
+ return (false, 0);
+ }
+
+ ///
+ /// 创建断点续传的下载分块
+ ///
+ private void CreateResumeChunks(long existingSize, long totalSize)
+ {
+ if (existingSize >= totalSize)
+ {
+ // 文件已完整
+ Interlocked.Exchange(ref _totalDownloaded, totalSize);
+ return;
+ }
+
+ var remainingSize = totalSize - existingSize;
+ var chunkCount = Math.Min(_config.ThreadCount, (int)Math.Ceiling((double)remainingSize / _config.ChunkSize));
+ var chunkSize = remainingSize / chunkCount;
+
+ for (int i = 0; i < chunkCount; i++)
+ {
+ var start = existingSize + (i * chunkSize);
+ var end = i == chunkCount - 1 ? totalSize - 1 : start + chunkSize - 1;
+
+ var chunk = new DownloadChunk
+ {
+ Id = i,
+ StartPosition = start,
+ EndPosition = end,
+ Status = ChunkStatus.Waiting
+ };
+
+ _pendingChunks.Enqueue(chunk);
+ }
+
+ LogWrapper.Info(LogModule, $"断点续传: 创建 {chunkCount} 个分块下载剩余 {remainingSize:N0} bytes");
+ }
+
+ ///
+ /// 获取HttpClient
+ ///
+ private HttpClient GetHttpClient()
+ {
+ if (_config.EnableConnectionPooling)
+ {
+ return NetworkService.GetClient();
+ }
+ else
+ {
+ var client = new HttpClient();
+ client.Timeout = TimeSpan.FromMilliseconds(_config.TimeoutMs);
+ return client;
+ }
+ }
+
+ ///
+ /// 多线程下载
+ ///
+ private async Task DownloadMultiThreadAsync(CancellationToken token)
+ {
+ var downloadTasks = new List();
+
+ // 启动工作线程
+ for (int i = 0; i < _config.ThreadCount; i++)
+ {
+ var threadId = i;
+ var task = Task.Run(async () => await OptimizedDownloadWorkerAsync(threadId, token), token);
+ downloadTasks.Add(task);
+ }
+
+ // 等待所有下载任务完成
+ await Task.WhenAll(downloadTasks);
+ }
+
+ ///
+ /// 下载工作线程
+ ///
+ private async Task OptimizedDownloadWorkerAsync(int workerId, CancellationToken token)
+ {
+ using var client = GetHttpClient();
+ var buffer = _bufferPool.Rent(_config.BufferSize);
+
+ try
+ {
+ if (_config.VerboseLogging)
+ LogWrapper.Debug(LogModule, $"工作线程 {workerId} 启动 (缓冲区: {_config.BufferSize:N0} bytes)");
+
+ while (!token.IsCancellationRequested && _pendingChunks.TryDequeue(out var chunk))
+ {
+ _activeChunks[workerId] = chunk;
+ await DownloadChunkOptimizedAsync(client, chunk, buffer, workerId, token);
+ _activeChunks.TryRemove(workerId, out _);
+ }
+
+ if (_config.VerboseLogging)
+ LogWrapper.Debug(LogModule, $"工作线程 {workerId} 完成");
+ }
+ finally
+ {
+ _bufferPool.Return(buffer);
+ if (!_config.EnableConnectionPooling)
+ {
+ client.Dispose();
+ }
+ }
+ }
+
+ ///
+ /// 分块下载
+ ///
+ private async Task DownloadChunkOptimizedAsync(HttpClient client, DownloadChunk chunk, byte[] buffer, int workerId, CancellationToken token)
+ {
+ for (int attempt = 0; attempt <= _config.MaxRetries; attempt++)
+ {
+ if (token.IsCancellationRequested) return;
+
+ try
+ {
+ chunk.Status = ChunkStatus.Downloading;
+ chunk.LastError = null;
+
+ using var request = new HttpRequestMessage(HttpMethod.Get, _sourceUri);
+ request.Headers.Range = new RangeHeaderValue(
+ chunk.StartPosition + chunk.DownloadedBytes,
+ chunk.EndPosition);
+
+ using var response = await client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, token);
+ response.EnsureSuccessStatusCode();
+
+ using var contentStream = await response.Content.ReadAsStreamAsync(token);
+
+ // 使用异步文件IO
+ using var fileStream = new FileStream(
+ _tempPath,
+ FileMode.OpenOrCreate,
+ FileAccess.Write,
+ FileShare.Write,
+ bufferSize: 4096,
+ useAsync: true);
+
+ fileStream.Position = chunk.StartPosition + chunk.DownloadedBytes;
+
+ int bytesRead;
+ while ((bytesRead = await contentStream.ReadAsync(buffer, 0, buffer.Length, token)) > 0)
+ {
+ // 速度限制检查
+ await _speedController.CanTransfer(bytesRead, token);
+
+ await fileStream.WriteAsync(buffer, 0, bytesRead, token);
+ await fileStream.FlushAsync(token);
+
+ // 更新统计信息
+ UpdateDownloadProgress(chunk, bytesRead);
+ }
+
+ chunk.Status = ChunkStatus.Completed;
+
+ if (_config.VerboseLogging)
+ LogWrapper.Debug(LogModule, $"线程 {workerId} 完成分块 {chunk.Id}");
+
+ return;
+ }
+ catch (Exception ex) when (attempt < _config.MaxRetries)
+ {
+ chunk.LastError = ex;
+ chunk.RetryCount = attempt + 1;
+ _stats.RetryCount++;
+
+ LogWrapper.Warn(LogModule, $"线程 {workerId} 分块 {chunk.Id} 重试 {attempt + 1}/{_config.MaxRetries}: {ex.Message}");
+
+ // 指数退避
+ var delay = Math.Min(1000 * (int)Math.Pow(2, attempt), 10000);
+ await Task.Delay(delay, token);
+ }
+ catch (Exception ex)
+ {
+ chunk.Status = ChunkStatus.Failed;
+ chunk.LastError = ex;
+ LogWrapper.Error(ex, LogModule, $"线程 {workerId} 分块 {chunk.Id} 最终失败");
+ throw;
+ }
+ }
+ }
+
+ ///
+ /// 更新下载进度
+ ///
+ private void UpdateDownloadProgress(DownloadChunk chunk, int bytesRead)
+ {
+ lock (_statsLock)
+ {
+ chunk.DownloadedBytes += bytesRead;
+ Interlocked.Add(ref _totalDownloaded, bytesRead);
+
+ // 更新统计
+ _stats.DownloadedBytes = Interlocked.Read(ref _totalDownloaded);
+ _stats.ActiveThreads = _activeChunks.Count;
+
+ // 实时更新进度 - Task机制会自动读取Progress属性
+ if (_totalSize > 0)
+ {
+ var totalDownloaded = Interlocked.Read(ref _totalDownloaded);
+ var currentProgress = 0.1 + (0.85 * totalDownloaded / _totalSize);
+ Progress = Math.Min(currentProgress, 0.95);
+ }
+ }
+ }
+
+ ///
+ /// 启动速度计算定时器
+ ///
+ private void StartSpeedCalculationTimer()
+ {
+ _speedCalculationTimer = new Timer(CalculateSpeed, null, TimeSpan.Zero, TimeSpan.FromSeconds(1));
+ }
+
+ ///
+ /// 计算下载速度
+ ///
+ private void CalculateSpeed(object? state)
+ {
+ var now = DateTime.Now;
+ var currentDownloaded = Interlocked.Read(ref _totalDownloaded);
+
+ lock (_statsLock)
+ {
+ var timeDiff = (now - _lastSpeedTime).TotalSeconds;
+ if (timeDiff > 0)
+ {
+ var bytesDiff = currentDownloaded - _lastSpeedCheck;
+ _stats.CurrentSpeed = bytesDiff / timeDiff;
+
+ // 更新峰值速度
+ if (_stats.CurrentSpeed > _stats.PeakSpeed)
+ _stats.PeakSpeed = _stats.CurrentSpeed;
+
+ // 计算平均速度
+ var totalTime = _stats.ElapsedTime.TotalSeconds;
+ if (totalTime > 0)
+ _stats.AverageSpeed = currentDownloaded / totalTime;
+ }
+
+ _lastSpeedCheck = currentDownloaded;
+ _lastSpeedTime = now;
+ }
+ }
+
+
+ ///
+ /// 获取文件信息
+ ///
+ private async Task<(bool SupportsRange, long ContentLength)> GetFileInfoAsync(CancellationToken token)
+ {
+ using var client = GetHttpClient();
+ using var request = new HttpRequestMessage(HttpMethod.Head, _sourceUri);
+ using var response = await client.SendAsync(request, token);
+ response.EnsureSuccessStatusCode();
+
+ var supportsRange = response.Headers.AcceptRanges?.Contains("bytes") == true;
+ var contentLength = response.Content.Headers.ContentLength ?? 0;
+
+ LogWrapper.Info(LogModule, $"文件信息: 大小={contentLength:N0} bytes, 支持分块={supportsRange}");
+ return (supportsRange, contentLength);
+ }
+
+ ///
+ /// 创建下载分块
+ ///
+ private void CreateDownloadChunks(long totalSize)
+ {
+ var chunkCount = Math.Min(_config.ThreadCount, (int)Math.Ceiling((double)totalSize / _config.ChunkSize));
+ if (chunkCount <= 0)
+ {
+ LogWrapper.Error(LogModule, $"无法创建下载分块: chunkCount={chunkCount} (ThreadCount={_config.ThreadCount}, totalSize={totalSize}, ChunkSize={_config.ChunkSize})");
+ return;
+ }
+ var chunkSize = totalSize / chunkCount;
+ var remainder = totalSize % chunkCount;
+
+ for (int i = 0; i < chunkCount; i++)
+ {
+ var start = i * chunkSize;
+ var end = start + chunkSize - 1;
+ if (i == chunkCount - 1)
+ end += remainder;
+
+ var chunk = new DownloadChunk
+ {
+ Id = i,
+ StartPosition = start,
+ EndPosition = end,
+ Status = ChunkStatus.Waiting
+ };
+
+ _pendingChunks.Enqueue(chunk);
+ }
+
+ LogWrapper.Info(LogModule, $"创建 {chunkCount} 个下载分块");
+ }
+
+ ///
+ /// 准备目标文件
+ ///
+ private void PrepareTargetFile()
+ {
+ var directory = Path.GetDirectoryName(_tempPath);
+ if (!string.IsNullOrEmpty(directory) && !Directory.Exists(directory))
+ {
+ Directory.CreateDirectory(directory);
+ }
+
+ if (_config.PreAllocateFile && _totalSize > 0)
+ {
+ using var fs = new FileStream(_tempPath, FileMode.Create, FileAccess.Write);
+ fs.SetLength(_totalSize);
+ }
+ }
+
+ ///
+ /// 完成下载
+ ///
+ private async Task FinalizeDownloadAsync(CancellationToken token)
+ {
+ await Task.Run(() =>
+ {
+ // 验证文件完整性
+ if (!File.Exists(_tempPath))
+ throw new FileNotFoundException("临时下载文件不存在");
+
+ var actualSize = new FileInfo(_tempPath).Length;
+ if (actualSize != _totalSize)
+ throw new InvalidDataException($"文件大小不匹配: 期望 {_totalSize:N0}, 实际 {actualSize:N0}");
+
+ // 移动到最终位置
+ if (File.Exists(_targetPath))
+ File.Delete(_targetPath);
+
+ File.Move(_tempPath, _targetPath);
+
+ LogWrapper.Info(LogModule, "文件验证和移动完成");
+ }, token);
+ }
+
+ ///
+ /// 单线程下载
+ ///
+ private async Task DownloadSingleThreadAsync(CancellationToken token)
+ {
+ using var client = GetHttpClient();
+ using var response = await client.GetAsync(_sourceUri, HttpCompletionOption.ResponseHeadersRead, token);
+ response.EnsureSuccessStatusCode();
+
+ _totalSize = response.Content.Headers.ContentLength ?? 0;
+ _stats.TotalBytes = _totalSize;
+
+ using var contentStream = await response.Content.ReadAsStreamAsync(token);
+ using var fileStream = new FileStream(_targetPath, FileMode.Create, FileAccess.Write, FileShare.None, 4096, true);
+
+ var buffer = _bufferPool.Rent(_config.BufferSize);
+ try
+ {
+ int bytesRead;
+ while ((bytesRead = await contentStream.ReadAsync(buffer, 0, buffer.Length, token)) > 0)
+ {
+ await _speedController.CanTransfer(bytesRead, token);
+ await fileStream.WriteAsync(buffer, 0, bytesRead, token);
+
+ Interlocked.Add(ref _totalDownloaded, bytesRead);
+ _stats.DownloadedBytes = Interlocked.Read(ref _totalDownloaded);
+
+ if (_totalSize > 0)
+ {
+ var totalDownloaded = Interlocked.Read(ref _totalDownloaded);
+ Progress = 0.1 + (0.85 * totalDownloaded / _totalSize);
+ }
+ }
+ }
+ finally
+ {
+ _bufferPool.Return(buffer);
+ }
+
+ // 设置完成状态
+ Progress = 1.0;
+ State = TaskState.Completed;
+ _stats.EndTime = DateTime.Now;
+
+ LogWrapper.Info(LogModule, $"单线程下载完成: 大小={_totalSize:N0} bytes, 耗时={_stats.ElapsedTime.TotalSeconds:F1}s");
+ return CreateSuccessResult();
+ }
+
+ private MultiThreadDownloadResult CreateSuccessResult()
+ {
+ return new MultiThreadDownloadResult
+ {
+ FilePath = _targetPath,
+ TotalSize = _totalSize,
+ Duration = _stats.ElapsedTime,
+ AverageSpeed = _stats.AverageSpeed,
+ IsSuccess = true
+ };
+ }
+
+ private MultiThreadDownloadResult CreateCancelledResult()
+ {
+ return new MultiThreadDownloadResult
+ {
+ FilePath = _targetPath,
+ Duration = _stats.ElapsedTime,
+ IsSuccess = false,
+ ErrorMessage = "下载已取消"
+ };
+ }
+
+ private MultiThreadDownloadResult CreateFailedResult(Exception ex)
+ {
+ return new MultiThreadDownloadResult
+ {
+ FilePath = _targetPath,
+ Duration = _stats.ElapsedTime,
+ IsSuccess = false,
+ ErrorMessage = ex.Message
+ };
+ }
+
+ ///
+ /// 取消下载
+ ///
+ public void CancelDownload()
+ {
+ _internalCts?.Cancel();
+ LogWrapper.Info(LogModule, $"取消下载: {_targetPath}");
+ }
+
+ ///
+ /// 获取详细状态信息
+ ///
+ public DownloadStatistics GetDetailedStatus()
+ {
+ lock (_statsLock)
+ {
+ return new DownloadStatistics
+ {
+ StartTime = _stats.StartTime,
+ EndTime = _stats.EndTime,
+ TotalBytes = _stats.TotalBytes,
+ DownloadedBytes = _stats.DownloadedBytes,
+ CurrentSpeed = _stats.CurrentSpeed,
+ AverageSpeed = _stats.AverageSpeed,
+ PeakSpeed = _stats.PeakSpeed,
+ ActiveThreads = _stats.ActiveThreads,
+ RetryCount = _stats.RetryCount
+ };
+ }
+ }
+}
diff --git a/Net/MultiThreadDownloadManager.cs b/Net/MultiThreadDownloadManager.cs
new file mode 100644
index 00000000..a23acb90
--- /dev/null
+++ b/Net/MultiThreadDownloadManager.cs
@@ -0,0 +1,318 @@
+using System;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using PCL.Core.App.Tasks;
+using PCL.Core.Logging;
+
+namespace PCL.Core.Net;
+
+///
+/// 多线程下载管理器
+/// 提供简化的API用于管理多个下载任务
+///
+public class MultiThreadDownloadManager : IDisposable
+{
+ private const string LogModule = "MultiThreadDownloadManager";
+
+ private readonly ConcurrentDictionary _activeTasks = new();
+ private readonly CancellationTokenSource _globalCts = new();
+ private bool _disposed = false;
+
+ ///
+ /// 默认线程数
+ ///
+ public int DefaultThreadCount { get; set; } = 4;
+
+ ///
+ /// 默认分块大小 (字节)
+ ///
+ public int DefaultChunkSize { get; set; } = 1024 * 1024; // 1MB
+
+ ///
+ /// 默认最大重试次数
+ ///
+ public int DefaultMaxRetries { get; set; } = 3;
+
+ ///
+ /// 默认超时时间 (毫秒)
+ ///
+ public int DefaultTimeoutMs { get; set; } = 30000; // 30秒
+
+ ///
+ /// 当前活动任务数量
+ ///
+ public int ActiveTaskCount => _activeTasks.Count;
+
+ ///
+ /// 获取所有活动任务
+ ///
+ public IEnumerable ActiveTasks => _activeTasks.Values.ToArray();
+
+ ///
+ /// 创建并启动下载任务
+ ///
+ /// 下载地址
+ /// 保存路径
+ /// 线程数(可选,使用默认值)
+ /// 分块大小(可选,使用默认值)
+ /// 最大重试次数(可选,使用默认值)
+ /// 超时时间(可选,使用默认值)
+ /// 进度回调(可选)
+ /// 状态变化回调(可选)
+ /// 下载任务
+ public async Task DownloadAsync(
+ string url,
+ string filePath,
+ int? threadCount = null,
+ int? chunkSize = null,
+ int? maxRetries = null,
+ int? timeoutMs = null,
+ Action? progressCallback = null,
+ Action? stateCallback = null)
+ {
+ if (_disposed)
+ throw new ObjectDisposedException(nameof(MultiThreadDownloadManager));
+
+ if (!Uri.TryCreate(url, UriKind.Absolute, out var uri))
+ throw new ArgumentException($"无效的URL: {url}", nameof(url));
+
+ var taskId = Path.GetFileName(filePath) ?? Guid.NewGuid().ToString();
+ if (_activeTasks.ContainsKey(taskId))
+ {
+ taskId += "_" + DateTime.Now.Ticks;
+ }
+
+ var downloadTask = new MultiThreadDownloadTask(
+ uri,
+ filePath,
+ threadCount ?? DefaultThreadCount,
+ chunkSize ?? DefaultChunkSize,
+ maxRetries ?? DefaultMaxRetries,
+ timeoutMs ?? DefaultTimeoutMs,
+ 0, // speedLimitBytesPerSecond - 默认无限制
+ 1000, // baseRetryDelayMs - 默认1秒
+ true, // useExponentialBackoff - 默认启用指数退避
+ _globalCts.Token
+ );
+
+ // 订阅进度和状态变化事件
+ if (progressCallback != null)
+ {
+ downloadTask.ProgressChanged += (sender, oldValue, newValue) => progressCallback(newValue);
+ }
+
+ if (stateCallback != null)
+ {
+ downloadTask.StateChanged += (sender, oldValue, newValue) => stateCallback(newValue);
+ }
+
+ // 任务完成后自动从活动列表中移除
+ downloadTask.StateChanged += (sender, oldState, newState) =>
+ {
+ if (newState is TaskState.Completed or TaskState.Failed or TaskState.Canceled)
+ {
+ _activeTasks.TryRemove(taskId, out _);
+ LogWrapper.Info(LogModule, $"任务已完成并从活动列表中移除: {taskId}");
+ }
+ };
+
+ _activeTasks[taskId] = downloadTask;
+ LogWrapper.Info(LogModule, $"开始下载任务: {taskId} [{url} -> {filePath}]");
+
+ // 在后台运行任务
+ _ = Task.Run(async () =>
+ {
+ try
+ {
+ await downloadTask.RunAsync();
+ }
+ catch (Exception ex)
+ {
+ LogWrapper.Error(ex, LogModule, $"下载任务执行异常: {taskId}");
+ }
+ }, _globalCts.Token);
+
+ return downloadTask;
+ }
+
+ ///
+ /// 同步下载文件 (阻塞直到完成)
+ ///
+ /// 下载地址
+ /// 保存路径
+ /// 线程数(可选)
+ /// 分块大小(可选)
+ /// 最大重试次数(可选)
+ /// 超时时间(可选)
+ /// 进度回调(可选)
+ /// 下载结果
+ public async Task DownloadFileAsync(
+ string url,
+ string filePath,
+ int? threadCount = null,
+ int? chunkSize = null,
+ int? maxRetries = null,
+ int? timeoutMs = null,
+ Action? progressCallback = null)
+ {
+ var task = await DownloadAsync(url, filePath, threadCount, chunkSize, maxRetries, timeoutMs, progressCallback);
+
+ // 等待任务完成
+ while (task.State is TaskState.Waiting or TaskState.Running)
+ {
+ await Task.Delay(100);
+ }
+
+ return task.Result ?? new MultiThreadDownloadResult
+ {
+ FilePath = filePath,
+ IsSuccess = false,
+ ErrorMessage = "任务未正确完成"
+ };
+ }
+
+ ///
+ /// 批量下载文件
+ ///
+ /// 下载项列表(URL, 文件路径)
+ /// 最大并发数
+ /// 每个任务的线程数
+ /// 整体进度回调
+ /// 所有下载结果
+ public async Task> DownloadBatchAsync(
+ IEnumerable<(string Url, string FilePath)> downloads,
+ int maxConcurrency = 3,
+ int threadCountPerTask = 2,
+ Action? progressCallback = null)
+ {
+ var downloadList = downloads.ToList();
+ if (!downloadList.Any())
+ return new List();
+
+ var results = new ConcurrentBag();
+ var completed = 0;
+
+ var semaphore = new SemaphoreSlim(maxConcurrency, maxConcurrency);
+ var tasks = downloadList.Select(async download =>
+ {
+ await semaphore.WaitAsync(_globalCts.Token);
+ try
+ {
+ var result = await DownloadFileAsync(
+ download.Url,
+ download.FilePath,
+ threadCountPerTask);
+
+ results.Add(result);
+
+ var currentCompleted = Interlocked.Increment(ref completed);
+ var overallProgress = (double)currentCompleted / downloadList.Count;
+ progressCallback?.Invoke(overallProgress);
+
+ return result;
+ }
+ finally
+ {
+ semaphore.Release();
+ }
+ });
+
+ await Task.WhenAll(tasks);
+ return results.ToList();
+ }
+
+ ///
+ /// 取消指定任务
+ ///
+ /// 任务ID
+ /// 是否成功取消
+ public bool CancelTask(string taskId)
+ {
+ if (_activeTasks.TryGetValue(taskId, out var task))
+ {
+ task.CancelDownload();
+ return true;
+ }
+ return false;
+ }
+
+ ///
+ /// 取消所有活动任务
+ ///
+ public void CancelAllTasks()
+ {
+ LogWrapper.Info(LogModule, $"取消所有活动任务,共 {_activeTasks.Count} 个");
+
+ foreach (var task in _activeTasks.Values)
+ {
+ task.CancelDownload();
+ }
+
+ _globalCts.Cancel();
+ }
+
+ ///
+ /// 获取总体下载统计信息
+ ///
+ /// 统计信息
+ public (int ActiveTasks, long TotalDownloaded, long TotalSize, double OverallSpeed) GetOverallStatistics()
+ {
+ var activeTasks = _activeTasks.Values.ToArray();
+ var totalDownloaded = 0L;
+ var totalSize = 0L;
+ var totalSpeed = 0.0;
+
+ foreach (var task in activeTasks)
+ {
+ var (downloaded, total, speed, _) = task.GetDownloadStatus();
+ totalDownloaded += downloaded;
+ totalSize += total;
+ totalSpeed += speed;
+ }
+
+ return (activeTasks.Length, totalDownloaded, totalSize, totalSpeed);
+ }
+
+ ///
+ /// 等待所有任务完成
+ ///
+ /// 超时时间(可选)
+ /// 是否所有任务都成功完成
+ public async Task WaitForAllTasksAsync(TimeSpan? timeout = null)
+ {
+ var startTime = DateTime.Now;
+
+ while (_activeTasks.Any())
+ {
+ if (timeout.HasValue && DateTime.Now - startTime > timeout.Value)
+ {
+ LogWrapper.Warn(LogModule, "等待所有任务完成超时");
+ return false;
+ }
+
+ await Task.Delay(100);
+ }
+
+ LogWrapper.Info(LogModule, "所有任务已完成");
+ return true;
+ }
+
+ ///
+ /// 释放资源
+ ///
+ public void Dispose()
+ {
+ if (_disposed) return;
+
+ CancelAllTasks();
+ _globalCts.Dispose();
+ _disposed = true;
+
+ LogWrapper.Info(LogModule, "多线程下载管理器已释放");
+ GC.SuppressFinalize(this);
+ }
+}
diff --git a/Net/MultiThreadDownloadTask.cs b/Net/MultiThreadDownloadTask.cs
new file mode 100644
index 00000000..16d1cc69
--- /dev/null
+++ b/Net/MultiThreadDownloadTask.cs
@@ -0,0 +1,602 @@
+using System;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Net.Http;
+using System.Net.Http.Headers;
+using System.Threading;
+using System.Threading.Tasks;
+using PCL.Core.App.Tasks;
+using PCL.Core.Logging;
+
+namespace PCL.Core.Net;
+
+///
+/// 多线程下载任务结果
+///
+public class MultiThreadDownloadResult
+{
+ public string FilePath { get; set; } = string.Empty;
+ public long TotalSize { get; set; }
+ public TimeSpan Duration { get; set; }
+ public double AverageSpeed { get; set; } // bytes per second
+ public bool IsSuccess { get; set; }
+ public string? ErrorMessage { get; set; }
+}
+
+///
+/// 下载分片信息
+///
+public class DownloadChunk
+{
+ public int Id { get; set; }
+ public long StartPosition { get; set; }
+ public long EndPosition { get; set; }
+ public long DownloadedBytes { get; set; }
+ public ChunkStatus Status { get; set; } = ChunkStatus.Waiting;
+ public Exception? LastError { get; set; }
+ public int RetryCount { get; set; } = 0;
+}
+
+///
+/// 分片状态
+///
+public enum ChunkStatus
+{
+ Waiting,
+ Downloading,
+ Completed,
+ Failed,
+ Cancelled
+}
+
+///
+/// 速度限制控制器
+///
+public class SpeedLimiter
+{
+ private readonly long _speedLimitBytesPerSecond;
+ private readonly object _lockObject = new();
+ private DateTime _lastResetTime = DateTime.Now;
+ private long _bytesInCurrentSecond = 0;
+
+ public SpeedLimiter(long speedLimitBytesPerSecond)
+ {
+ _speedLimitBytesPerSecond = speedLimitBytesPerSecond;
+ }
+
+ public async Task WaitIfNeeded(int bytesToTransfer, CancellationToken cancellationToken)
+ {
+ if (_speedLimitBytesPerSecond <= 0) return;
+
+ await Task.Run(() =>
+ {
+ lock (_lockObject)
+ {
+ var now = DateTime.Now;
+ var timeSinceReset = now - _lastResetTime;
+
+ // Reset every second
+ if (timeSinceReset.TotalMilliseconds >= 1000)
+ {
+ _lastResetTime = now;
+ _bytesInCurrentSecond = 0;
+ }
+
+ _bytesInCurrentSecond += bytesToTransfer;
+
+ // Calculate delay if needed
+ if (_bytesInCurrentSecond > _speedLimitBytesPerSecond)
+ {
+ var remainingTime = 1000 - timeSinceReset.TotalMilliseconds;
+ if (remainingTime > 0)
+ {
+ Thread.Sleep((int)remainingTime);
+ _lastResetTime = DateTime.Now;
+ _bytesInCurrentSecond = bytesToTransfer;
+ }
+ }
+ }
+ }, cancellationToken);
+ }
+}
+
+///
+/// 高性能多线程下载任务
+/// 继承TaskBase以完全适配PCL.Core架构,支持IObservableProgressSource接口
+/// 支持速度限制和可配置重试间隔
+///
+public class MultiThreadDownloadTask : TaskBase
+{
+ private const string LogModule = "MultiThreadDownload";
+
+ private readonly Uri _sourceUri;
+ private readonly string _targetPath;
+ private readonly int _threadCount;
+ private readonly int _chunkSize;
+ private readonly int _maxRetries;
+ private readonly int _timeoutMs;
+ private readonly long _speedLimitBytesPerSecond;
+ private readonly int _baseRetryDelayMs;
+ private readonly bool _useExponentialBackoff;
+
+ private long _totalSize = 0;
+ private long _totalDownloaded = 0;
+ private readonly ConcurrentQueue _chunks = new();
+ private readonly ConcurrentDictionary _activeChunks = new();
+ private readonly object _progressLock = new();
+ private DateTime _startTime;
+ private CancellationTokenSource? _internalCts;
+ private readonly SpeedLimiter _speedLimiter;
+
+ ///
+ /// 创建多线程下载任务
+ ///
+ /// 下载源地址
+ /// 目标文件路径
+ /// 线程数量(默认4)
+ /// 分块大小(默认1MB)
+ /// 最大重试次数(默认3)
+ /// 超时时间毫秒(默认30秒)
+ /// 速度限制(字节/秒,0表示无限制,默认无限制)
+ /// 基础重试延迟毫秒(默认1秒)
+ /// 使用指数退避重试(默认true)
+ /// 取消令牌
+ public MultiThreadDownloadTask(
+ Uri sourceUri,
+ string targetPath,
+ int threadCount = 4,
+ int chunkSize = 1024 * 1024, // 1MB
+ int maxRetries = 3,
+ int timeoutMs = 30000,
+ long speedLimitBytesPerSecond = 0, // 0 = 无限制
+ int baseRetryDelayMs = 1000, // 1秒
+ bool useExponentialBackoff = true,
+ CancellationToken? cancellationToken = null)
+ : base($"下载 {Path.GetFileName(targetPath)}", cancellationToken, $"从 {sourceUri} 下载到 {targetPath}")
+ {
+ _sourceUri = sourceUri ?? throw new ArgumentNullException(nameof(sourceUri));
+ _targetPath = Path.GetFullPath(targetPath);
+ _threadCount = Math.Max(1, Math.Min(threadCount, 16)); // 限制在1-16个线程
+ _chunkSize = Math.Max(1024, chunkSize); // 至少1KB
+ _maxRetries = Math.Max(0, maxRetries);
+ _timeoutMs = Math.Max(5000, timeoutMs); // 至少5秒
+ _speedLimitBytesPerSecond = Math.Max(0, speedLimitBytesPerSecond);
+ _baseRetryDelayMs = Math.Max(100, baseRetryDelayMs); // 至少100ms
+ _useExponentialBackoff = useExponentialBackoff;
+
+ // 初始化速度限制器
+ _speedLimiter = new SpeedLimiter(_speedLimitBytesPerSecond);
+
+ LogWrapper.Info(LogModule, $"创建多线程下载任务: {Name}, 线程数: {_threadCount}, 速度限制: {(_speedLimitBytesPerSecond > 0 ? $"{_speedLimitBytesPerSecond / 1024 / 1024:F1}MB/s" : "无限制")}");
+ }
+
+ ///
+ /// 执行下载
+ ///
+ public override async Task RunAsync(params object[] objects)
+ {
+ if (State != TaskState.Waiting)
+ throw new InvalidOperationException($"任务已执行,当前状态: {State}");
+
+ State = TaskState.Running;
+ Progress = 0;
+ _startTime = DateTime.Now;
+
+ try
+ {
+ _internalCts = CancellationTokenSource.CreateLinkedTokenSource(CancellationToken ?? System.Threading.CancellationToken.None);
+ var token = _internalCts.Token;
+
+ LogWrapper.Info(LogModule, $"开始下载: {_sourceUri} -> {_targetPath}");
+
+ // 1. 获取文件信息
+ Progress = 0.05;
+ var fileInfo = await GetFileInfoAsync(token);
+ if (!fileInfo.SupportsRange || fileInfo.ContentLength <= 0)
+ {
+ // 不支持分块下载,使用单线程
+ LogWrapper.Info(LogModule, "服务器不支持分块下载,使用单线程模式");
+ return await DownloadSingleThreadAsync(token);
+ }
+
+ _totalSize = fileInfo.ContentLength;
+
+ // 2. 创建下载分块
+ Progress = 0.1;
+ CreateDownloadChunks(fileInfo.ContentLength);
+
+ // 3. 准备目标文件
+ PrepareTargetFile();
+
+ // 4. 启动多线程下载
+ Progress = 0.15;
+ await DownloadMultiThreadAsync(token);
+
+ // 5. 验证下载完整性
+ Progress = 0.95;
+ await ValidateDownloadAsync(token);
+
+ Progress = 1.0;
+ State = TaskState.Completed;
+
+ var duration = DateTime.Now - _startTime;
+ var result = new MultiThreadDownloadResult
+ {
+ FilePath = _targetPath,
+ TotalSize = _totalSize,
+ Duration = duration,
+ AverageSpeed = _totalSize / duration.TotalSeconds,
+ IsSuccess = true
+ };
+
+ LogWrapper.Info(LogModule, $"下载完成: {_targetPath}, 大小: {_totalSize:N0} bytes, 耗时: {duration.TotalSeconds:F1}s, 平均速度: {result.AverageSpeed / 1024 / 1024:F2} MB/s");
+ return result;
+ }
+ catch (OperationCanceledException)
+ {
+ State = TaskState.Canceled;
+ LogWrapper.Info(LogModule, $"下载已取消: {_targetPath}");
+ return new MultiThreadDownloadResult
+ {
+ FilePath = _targetPath,
+ Duration = DateTime.Now - _startTime,
+ IsSuccess = false,
+ ErrorMessage = "下载已取消"
+ };
+ }
+ catch (Exception ex)
+ {
+ State = TaskState.Failed;
+ LogWrapper.Error(ex, LogModule, $"下载失败: {_targetPath}");
+ return new MultiThreadDownloadResult
+ {
+ FilePath = _targetPath,
+ Duration = DateTime.Now - _startTime,
+ IsSuccess = false,
+ ErrorMessage = ex.Message
+ };
+ }
+ finally
+ {
+ _internalCts?.Dispose();
+ }
+ }
+
+ ///
+ /// 获取文件信息
+ ///
+ private async Task<(bool SupportsRange, long ContentLength)> GetFileInfoAsync(CancellationToken token)
+ {
+ using var client = NetworkService.GetClient();
+ using var request = new HttpRequestMessage(HttpMethod.Head, _sourceUri);
+
+ using var response = await client.SendAsync(request, token);
+ response.EnsureSuccessStatusCode();
+
+ var supportsRange = response.Headers.AcceptRanges?.Contains("bytes") == true;
+ var contentLength = response.Content.Headers.ContentLength ?? 0;
+
+ LogWrapper.Info(LogModule, $"文件信息: 大小={contentLength:N0} bytes, 支持分块={supportsRange}");
+ return (supportsRange, contentLength);
+ }
+
+ ///
+ /// 创建下载分块
+ ///
+ private void CreateDownloadChunks(long totalSize)
+ {
+ var chunkCount = Math.Min(_threadCount, (int)Math.Ceiling((double)totalSize / _chunkSize));
+ var chunkSize = totalSize / chunkCount;
+ var remainder = totalSize % chunkCount;
+
+ for (int i = 0; i < chunkCount; i++)
+ {
+ var start = i * chunkSize;
+ var end = start + chunkSize - 1;
+ if (i == chunkCount - 1) // 最后一块包含余数
+ end += remainder;
+
+ var chunk = new DownloadChunk
+ {
+ Id = i,
+ StartPosition = start,
+ EndPosition = end,
+ Status = ChunkStatus.Waiting
+ };
+
+ _chunks.Enqueue(chunk);
+ }
+
+ LogWrapper.Info(LogModule, $"创建了 {chunkCount} 个下载分块,每块大小约 {chunkSize:N0} bytes");
+ }
+
+ ///
+ /// 准备目标文件
+ ///
+ private void PrepareTargetFile()
+ {
+ var directory = Path.GetDirectoryName(_targetPath);
+ if (!string.IsNullOrEmpty(directory) && !Directory.Exists(directory))
+ {
+ Directory.CreateDirectory(directory);
+ }
+
+ // 预分配文件空间
+ using var fs = new FileStream(_targetPath, FileMode.Create, FileAccess.Write, FileShare.Read);
+ fs.SetLength(_totalSize);
+ fs.Close();
+ }
+
+ ///
+ /// 多线程下载
+ ///
+ private async Task DownloadMultiThreadAsync(CancellationToken token)
+ {
+ var downloadTasks = new List();
+
+ // 启动下载线程
+ for (int i = 0; i < _threadCount; i++)
+ {
+ var threadId = i;
+ var task = Task.Run(async () => await DownloadWorkerAsync(threadId, token), token);
+ downloadTasks.Add(task);
+ }
+
+ // 启动进度更新任务
+ var progressTask = Task.Run(async () => await UpdateProgressAsync(token), token);
+ downloadTasks.Add(progressTask);
+
+ // 等待所有任务完成
+ await Task.WhenAll(downloadTasks);
+ }
+
+ ///
+ /// 下载工作线程
+ ///
+ private async Task DownloadWorkerAsync(int workerId, CancellationToken token)
+ {
+ using var client = NetworkService.GetClient();
+ client.Timeout = TimeSpan.FromMilliseconds(_timeoutMs);
+
+ LogWrapper.Debug(LogModule, $"工作线程 {workerId} 已启动");
+
+ while (!token.IsCancellationRequested && _chunks.TryDequeue(out var chunk))
+ {
+ _activeChunks[workerId] = chunk;
+ await DownloadChunkAsync(client, chunk, workerId, token);
+ _activeChunks.TryRemove(workerId, out _);
+ }
+
+ LogWrapper.Debug(LogModule, $"工作线程 {workerId} 已完成");
+ }
+
+ ///
+ /// 下载单个分块
+ ///
+ private async Task DownloadChunkAsync(HttpClient client, DownloadChunk chunk, int workerId, CancellationToken token)
+ {
+ var buffer = new byte[8192]; // 8KB读取缓冲区
+
+ for (int attempt = 0; attempt <= _maxRetries; attempt++)
+ {
+ if (token.IsCancellationRequested)
+ return;
+
+ try
+ {
+ chunk.Status = ChunkStatus.Downloading;
+ chunk.LastError = null;
+
+ using var request = new HttpRequestMessage(HttpMethod.Get, _sourceUri);
+ request.Headers.Range = new RangeHeaderValue(
+ chunk.StartPosition + chunk.DownloadedBytes,
+ chunk.EndPosition);
+
+ using var response = await client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, token);
+ response.EnsureSuccessStatusCode();
+
+ using var contentStream = await response.Content.ReadAsStreamAsync(token);
+ using var fileStream = new FileStream(_targetPath, FileMode.Open, FileAccess.Write, FileShare.Write);
+
+ fileStream.Position = chunk.StartPosition + chunk.DownloadedBytes;
+
+ int bytesRead;
+ while ((bytesRead = await contentStream.ReadAsync(buffer, 0, buffer.Length, token)) > 0)
+ {
+ // 速度限制检查
+ await _speedLimiter.WaitIfNeeded(bytesRead, token);
+
+ await fileStream.WriteAsync(buffer, 0, bytesRead, token);
+
+ lock (_progressLock)
+ {
+ chunk.DownloadedBytes += bytesRead;
+ _totalDownloaded += bytesRead;
+ }
+ }
+
+ chunk.Status = ChunkStatus.Completed;
+ LogWrapper.Debug(LogModule, $"工作线程 {workerId} 完成分块 {chunk.Id} ({chunk.StartPosition}-{chunk.EndPosition})");
+ return;
+ }
+ catch (Exception ex) when (attempt < _maxRetries)
+ {
+ chunk.LastError = ex;
+ chunk.RetryCount = attempt + 1;
+ LogWrapper.Warn(LogModule, $"工作线程 {workerId} 分块 {chunk.Id} 下载失败 (重试 {attempt + 1}/{_maxRetries}): {ex.Message}");
+
+ // 计算重试延迟时间
+ int retryDelay;
+ if (_useExponentialBackoff)
+ {
+ // 指数退避: baseDelay * 2^attempt,最大30秒
+ retryDelay = Math.Min(_baseRetryDelayMs * (int)Math.Pow(2, attempt), 30000);
+ }
+ else
+ {
+ // 固定延迟
+ retryDelay = _baseRetryDelayMs;
+ }
+
+ LogWrapper.Debug(LogModule, $"工作线程 {workerId} 分块 {chunk.Id} 将在 {retryDelay}ms 后重试");
+ await Task.Delay(retryDelay, token);
+ }
+ catch (Exception ex)
+ {
+ chunk.Status = ChunkStatus.Failed;
+ chunk.LastError = ex;
+ LogWrapper.Error(ex, LogModule, $"工作线程 {workerId} 分块 {chunk.Id} 下载失败,已达最大重试次数");
+ throw;
+ }
+ }
+ }
+
+ ///
+ /// 更新进度
+ ///
+ private async Task UpdateProgressAsync(CancellationToken token)
+ {
+ while (!token.IsCancellationRequested)
+ {
+ try
+ {
+ lock (_progressLock)
+ {
+ if (_totalSize > 0)
+ {
+ var currentProgress = 0.15 + (0.8 * _totalDownloaded / _totalSize); // 15%-95%的进度区间
+ Progress = Math.Min(currentProgress, 0.95);
+ }
+ }
+
+ await Task.Delay(100, token); // 每100ms更新一次进度
+ }
+ catch (OperationCanceledException)
+ {
+ break;
+ }
+ }
+ }
+
+ ///
+ /// 单线程下载(用于不支持分块的服务器)
+ ///
+ private async Task DownloadSingleThreadAsync(CancellationToken token)
+ {
+ using var client = NetworkService.GetClient();
+ using var response = await client.GetAsync(_sourceUri, HttpCompletionOption.ResponseHeadersRead, token);
+ response.EnsureSuccessStatusCode();
+
+ _totalSize = response.Content.Headers.ContentLength ?? 0;
+
+ using var contentStream = await response.Content.ReadAsStreamAsync(token);
+ using var fileStream = new FileStream(_targetPath, FileMode.Create, FileAccess.Write);
+
+ var buffer = new byte[8192];
+ int bytesRead;
+ long totalDownloaded = 0;
+
+ while ((bytesRead = await contentStream.ReadAsync(buffer, 0, buffer.Length, token)) > 0)
+ {
+ // 速度限制检查
+ await _speedLimiter.WaitIfNeeded(bytesRead, token);
+
+ await fileStream.WriteAsync(buffer, 0, bytesRead, token);
+ totalDownloaded += bytesRead;
+
+ if (_totalSize > 0)
+ {
+ Progress = 0.15 + (0.8 * totalDownloaded / _totalSize);
+ }
+ }
+
+ Progress = 1.0;
+ State = TaskState.Completed;
+
+ var duration = DateTime.Now - _startTime;
+ return new MultiThreadDownloadResult
+ {
+ FilePath = _targetPath,
+ TotalSize = totalDownloaded,
+ Duration = duration,
+ AverageSpeed = totalDownloaded / duration.TotalSeconds,
+ IsSuccess = true
+ };
+ }
+
+ ///
+ /// 验证下载完整性
+ ///
+ private async Task ValidateDownloadAsync(CancellationToken token)
+ {
+ await Task.Run(() =>
+ {
+ var fileInfo = new FileInfo(_targetPath);
+ if (!fileInfo.Exists)
+ throw new FileNotFoundException("下载的文件不存在");
+
+ if (fileInfo.Length != _totalSize)
+ throw new InvalidDataException($"文件大小不匹配: 期望 {_totalSize:N0} bytes, 实际 {fileInfo.Length:N0} bytes");
+
+ LogWrapper.Debug(LogModule, "文件完整性验证通过");
+ }, token);
+ }
+
+ ///
+ /// 取消下载
+ ///
+ public void CancelDownload()
+ {
+ _internalCts?.Cancel();
+ LogWrapper.Info(LogModule, $"取消下载: {_targetPath}");
+ }
+
+ ///
+ /// 获取当前下载状态信息
+ ///
+ public (long Downloaded, long Total, double Speed, int ActiveThreads) GetDownloadStatus()
+ {
+ lock (_progressLock)
+ {
+ var elapsed = DateTime.Now - _startTime;
+ var speed = elapsed.TotalSeconds > 0 ? _totalDownloaded / elapsed.TotalSeconds : 0;
+ return (_totalDownloaded, _totalSize, speed, _activeChunks.Count);
+ }
+ }
+
+ ///
+ /// 获取当前速度限制设置(字节/秒)
+ ///
+ public long GetSpeedLimit() => _speedLimitBytesPerSecond;
+
+ ///
+ /// 获取重试配置信息
+ ///
+ public (int BaseRetryDelayMs, bool UseExponentialBackoff, int MaxRetries) GetRetryConfig()
+ {
+ return (_baseRetryDelayMs, _useExponentialBackoff, _maxRetries);
+ }
+
+ ///
+ /// 获取详细下载配置信息
+ ///
+ public string GetConfigurationInfo()
+ {
+ var speedLimitText = _speedLimitBytesPerSecond > 0
+ ? $"{_speedLimitBytesPerSecond / 1024.0 / 1024.0:F2} MB/s"
+ : "无限制";
+
+ var retryStrategyText = _useExponentialBackoff ? "指数退避" : "固定间隔";
+
+ return $"配置信息:\n" +
+ $" 线程数: {_threadCount}\n" +
+ $" 分块大小: {_chunkSize / 1024.0 / 1024.0:F2} MB\n" +
+ $" 速度限制: {speedLimitText}\n" +
+ $" 最大重试: {_maxRetries}\n" +
+ $" 重试策略: {retryStrategyText}\n" +
+ $" 基础延迟: {_baseRetryDelayMs} ms\n" +
+ $" 超时时间: {_timeoutMs} ms";
+ }
+}