动态配置并发数量

This commit is contained in:
2025-06-26 19:05:42 +08:00
parent 3bdc95b2ce
commit 00ccc313e0
3 changed files with 66 additions and 46 deletions

View File

@@ -15,4 +15,8 @@
</dependentAssembly>
</assemblyBinding>
</runtime>
<appSettings>
<add key="MaxConcurrentDownloads" value="2" />
<add key="MaxDownloadRetries" value="2" />
</appSettings>
</configuration>

View File

@@ -125,6 +125,7 @@
<Reference Include="System.Net.Http" />
<Reference Include="System.Windows.Forms" />
<Reference Include="System.Xml" />
<Reference Include="System.Configuration" />
</ItemGroup>
<ItemGroup>
<Compile Include="Form1.cs">
@@ -176,7 +177,7 @@
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>
<ErrorText>这台计算机上缺少此项目引用的 NuGet 程序包。使用NuGet 程序包还原可下载这些程序包。有关更多信息,请参见 http://go.microsoft.com/fwlink/?LinkID=322105。缺少的文件是 {0}。</ErrorText>
<ErrorText>这台计算机上缺少此项目引用的 NuGet 程序包。使用"NuGet 程序包还原"可下载这些程序包。有关更多信息,请参见 http://go.microsoft.com/fwlink/?LinkID=322105。缺少的文件是 {0}。</ErrorText>
</PropertyGroup>
<Error Condition="!Exists('packages\Costura.Fody.6.0.0\build\Costura.Fody.props')" Text="$([System.String]::Format('$(ErrorText)', 'packages\Costura.Fody.6.0.0\build\Costura.Fody.props'))" />
<Error Condition="!Exists('packages\Costura.Fody.6.0.0\build\Costura.Fody.targets')" Text="$([System.String]::Format('$(ErrorText)', 'packages\Costura.Fody.6.0.0\build\Costura.Fody.targets'))" />

105
Form1.cs
View File

@@ -22,6 +22,7 @@ using Newtonsoft.Json.Linq;
using System.Collections.Concurrent;
using System.Diagnostics;
using System.Configuration;
namespace CheckDownload
{
@@ -57,7 +58,9 @@ namespace CheckDownload
// 网络优化: 备用DNS服务列表提高解析成功率
private static readonly List<string> _dnsServers = new List<string> { "223.5.5.5", "119.29.29.29" };
// 最大并发下载数量
private static readonly int MaxConcurrentDownloads = 2;
private static readonly int MaxConcurrentDownloads = int.TryParse(ConfigurationManager.AppSettings["MaxConcurrentDownloads"], out var mcd) ? mcd : 4;
// 最大下载重试次数
private static readonly int MaxDownloadRetries = int.TryParse(ConfigurationManager.AppSettings["MaxDownloadRetries"], out var mdr) ? mdr : 2;
// 用于存储下载的文件数据
private Dictionary<string, string> _downloadedFiles = new Dictionary<string, string>();
// 已完成的下载数量
@@ -202,17 +205,33 @@ namespace CheckDownload
}
UpdateStatus("下载并验证文件...");
_totalCount = compareResult.Count;
await DownloadAndVerifyFiles(compareResult);
UpdateProgressValue(100);
// 根据路径长度排序,优先下载小文件/浅层文件,可加其它排序规则
var orderedFileList = compareResult.OrderBy(k => k.Key.Length)
.ToDictionary(k => k.Key, v => v.Value);
// 更新成功后清理临时文件夹
CleanupTempDirectory();
_totalCount = orderedFileList.Count;
_completedCount = 0;
_downloadedFiles.Clear();
var failedFiles = new ConcurrentDictionary<string, string>();
await PerformDownloads(orderedFileList, failedFiles);
if (!failedFiles.IsEmpty)
{
UpdateStatus($"有 {failedFiles.Count} 个文件下载失败,开始重试...");
var stillFailing = await RetryFailedFilesAsync(new Dictionary<string, string>(failedFiles));
if (stillFailing.Any())
{
UpdateStatus($"重试后仍有 {stillFailing.Count} 个文件下载失败。");
}
}
if (_completedCount == 0 && orderedFileList.Count > 0)
{
throw new Exception("所有文件下载失败。");
}
// 显示更新完成并等待3秒
UpdateStatus("更新完成");
await Task.Delay(3000);
this.Close();
await VerifyAndSaveAllFiles();
}
catch (Exception ex)
{
@@ -539,7 +558,7 @@ namespace CheckDownload
UpdateStatus($"使用主域名下载文件...");
var request = new HttpRequestMessage(HttpMethod.Get, authUrl);
var request = new HttpRequestMessage(HttpMethod.Get, authUrl) { Version = HttpVersion.Version11 };
request.Headers.Add("User-Agent", "CheckDownload/1.0");
using (var response = await _httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead))
@@ -574,7 +593,7 @@ namespace CheckDownload
UpdateStatus($"使用备用域名下载文件...");
var request = new HttpRequestMessage(HttpMethod.Get, authUrl);
var request = new HttpRequestMessage(HttpMethod.Get, authUrl) { Version = HttpVersion.Version11 };
request.Headers.Add("User-Agent", "CheckDownload/1.0");
using (var response = await _httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead))
@@ -618,7 +637,7 @@ namespace CheckDownload
{
string authUrl = GenerateAuthUrl($"http://{OneDriveMainDomain}{OneDrivePath}", fileName);
var request = new HttpRequestMessage(HttpMethod.Get, authUrl);
var request = new HttpRequestMessage(HttpMethod.Get, authUrl) { Version = HttpVersion.Version11 };
request.Headers.Add("User-Agent", "CheckDownload/1.0");
using (var response = await _httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead))
@@ -647,7 +666,7 @@ namespace CheckDownload
{
string authUrl = GenerateAuthUrl($"http://{OneDriveBackupDomain}{OneDrivePath}", fileName);
var request = new HttpRequestMessage(HttpMethod.Get, authUrl);
var request = new HttpRequestMessage(HttpMethod.Get, authUrl) { Version = HttpVersion.Version11 };
request.Headers.Add("User-Agent", "CheckDownload/1.0");
using (var response = await _httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead))
@@ -681,10 +700,9 @@ namespace CheckDownload
/// <returns>重试后仍然失败的文件字典</returns>
private async Task<Dictionary<string, string>> RetryFailedFilesAsync(Dictionary<string, string> failedFiles)
{
const int maxRetries = 2;
var filesToRetry = new Dictionary<string, string>(failedFiles);
for (int i = 0; i < maxRetries && filesToRetry.Any(); i++)
for (int i = 0; i < MaxDownloadRetries && filesToRetry.Any(); i++)
{
UpdateStatus($"第 {i + 1} 次重试,剩余 {filesToRetry.Count} 个文件...");
var failedThisRound = new ConcurrentDictionary<string, string>();
@@ -693,7 +711,7 @@ namespace CheckDownload
filesToRetry = new Dictionary<string, string>(failedThisRound);
if (filesToRetry.Any() && i < maxRetries - 1)
if (filesToRetry.Any() && i < MaxDownloadRetries - 1)
{
UpdateStatus($"等待 3 秒后进行下一次重试...");
await Task.Delay(3000);
@@ -715,7 +733,7 @@ namespace CheckDownload
try
{
var requestUri = $"http://{dnsServer}/resolve?name={domain}&type=1&short=1";
var request = new HttpRequestMessage(HttpMethod.Get, requestUri);
var request = new HttpRequestMessage(HttpMethod.Get, requestUri) { Version = HttpVersion.Version11 };
request.Headers.Add("User-Agent", "CheckDownload/1.0");
request.Headers.Host = dnsServer;
@@ -747,31 +765,31 @@ namespace CheckDownload
private async Task VerifyAndSaveAllFiles()
{
UpdateStatus("正在校验文件...");
var failedFiles = new List<string>();
var filesForScripting = new List<(string original, string newFile)>();
int processedCount = 0;
var failedFiles = new ConcurrentBag<string>();
var filesForScripting = new ConcurrentBag<(string original, string newFile)>();
int totalFiles = _downloadedFiles.Count;
int completed = 0;
var semaphore = new SemaphoreSlim(Environment.ProcessorCount);
foreach (var item in _downloadedFiles)
var tasks = _downloadedFiles.Select(async item =>
{
string relativePath = item.Key;
string expectedMd5 = item.Value;
string tempFilePath = Path.Combine(_tempDirectory, relativePath);
await semaphore.WaitAsync();
try
{
processedCount++;
UpdateStatus($"正在校验和保存文件 ({processedCount}/{totalFiles}): {relativePath}");
string relativePath = item.Key;
string expectedMd5 = item.Value;
string tempFilePath = Path.Combine(_tempDirectory, relativePath);
string actualMd5 = CalculateMD5FromFile(tempFilePath);
if (actualMd5 != expectedMd5.ToLower())
{
throw new Exception($"MD5校验失败 (期望: {expectedMd5}, 实际: {actualMd5})");
failedFiles.Add(relativePath);
return;
}
string localPath = Path.Combine(_baseDirectory, relativePath);
string localDir = Path.GetDirectoryName(localPath);
if (!Directory.Exists(localDir))
{
Directory.CreateDirectory(localDir);
@@ -782,31 +800,28 @@ namespace CheckDownload
string backupPath = localPath + ".new";
File.Move(tempFilePath, backupPath);
filesForScripting.Add((localPath, backupPath));
UpdateStatus($"文件 {relativePath} 正在被占用,将在程序重启后更新");
}
else
{
UpdateStatus($"文件 {relativePath} 已更新");
}
int percentage = 95 + (int)(processedCount * 5.0 / totalFiles);
UpdateProgressValue(Math.Min(100, percentage));
}
catch (Exception ex)
finally
{
UpdateStatus($"文件校验失败: {relativePath} - {ex.Message}");
failedFiles.Add(relativePath);
int done = Interlocked.Increment(ref completed);
int percentage = 95 + (int)(done * 5.0 / totalFiles);
UpdateProgressValue(Math.Min(100, percentage));
semaphore.Release();
}
}
});
await Task.WhenAll(tasks);
if (filesForScripting.Any())
{
CreateReplaceScriptForAll(filesForScripting);
CreateReplaceScriptForAll(filesForScripting.ToList());
}
foreach (var failedFile in failedFiles)
foreach (var failed in failedFiles)
{
_downloadedFiles.Remove(failedFile);
_downloadedFiles.Remove(failed);
}
if (failedFiles.Count > 0)
@@ -1009,7 +1024,7 @@ namespace CheckDownload
{
var ipUrl = signedUrl.Replace(signedUri.Host, ip);
var request = new HttpRequestMessage(HttpMethod.Get, ipUrl);
var request = new HttpRequestMessage(HttpMethod.Get, ipUrl) { Version = HttpVersion.Version11 };
request.Headers.Host = signedUri.Host;
using (var response = await _httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead))
{