该任务的本质是使用带有动态更新的 ProgressBar 和 Label 的表单从 Internet 服务器下载应用程序安装程序文件,该 Label 指示已下载的 MB 数以及HttpClient类,以获得对 Internet 服务器的直接访问。另外,如果互联网连接中断,请等待 60 秒才能重新连接。如果互联网丢失 >= 60 秒 - 关闭表单并中断下载。
在 Windows 10 上,该功能工作正常,但在 Windows 11 上,当 Internet 连接中断时,即断开连接时,就会出现问题 - 表单停止处理交互事件(单击按钮等)(当 GUI 正常工作时)。在某些版本的 Windows 上(Windows 11 版本 23h2) - 整个应用程序关闭(从应用程序调用下载表单)。
.NET框架4.8
StartDownload - 下载开始时调用的方法。
private async void StartDownload(string path = "")
{
if (_cts != null) return;
_lastSelectedPath = string.IsNullOrEmpty(path) ? SelectFolder() : path;
if (_lastSelectedPath == null || !CanWriteToFolder(_lastSelectedPath))
{
MessageBox.Show(Properties.Resources.AccessViolation);
Close();
return;
}
FilePath = Path.Combine(_lastSelectedPath, _fileName);
string url = _plugin.IsTestMode
? $"Сервер №1"
: $"Сервер №2";
bool isDownloadSuccessful = false;
using (_cts = new CancellationTokenSource())
{
try
{
var progress = new Progress<DownloadProgress>(UpdateProgress);
do
{
try
{
await DownloadAndSaveFileAsync(url, FilePath, progress, _cts.Token); // Метод будет описан ниже
isDownloadSuccessful = true;
break; // Успешная загрузка, выходим из цикла
}
catch (IOException ex) // потеря доступа к интернету
{
lbProgress.Text = Properties.Resources.ConnectWithServerLost; // lbProgress - Label на форме отображающий состояние скачивания
await WaitForReconnectAsync(_cts.Token); // Запуск 60 секунд для восстановления интернета
}
catch (HttpRequestException ex)
{
if (ex.Message.Contains("416")) // 416 (Запрошенный диапазон невыполним = Файл уже полностью загружен)
{
MessageBox.Show(Properties.Resources.FileAlreadyUploaded);
DialogResult = DialogResult.OK;
break;
}
else break; // выходим из цикла, если были другие ошибки типа HttpRequestException
}
catch (OperationCanceledException) // Отмена пользователем с помощью кнопки "Cancel" или закрытия формы
{
DialogResult = DialogResult.Cancel;
break;
}
catch (Exception msg)
{
break;
}
}
while (!_connectionEstablishmentFailed); // Повторяем, пока не исчерпан лимит времени
if (!_connectionEstablishmentFailed && DialogResult != DialogResult.Cancel) isDownloadSuccessful = true;
else // Соединение не восстановилось
{
DialogResult = DialogResult.Cancel;
}
}
finally
{
if (isDownloadSuccessful) DialogResult = DialogResult.OK;
progress.Value = 0;
Close();
}
}
_cts = null; // Обязательно сбрасываем, чтобы избежать ObjectDisposedException
}
DownloadAndSaveFileAsync - 在 StartDownload 执行期间调用以开始下载文件
private async Task DownloadAndSaveFileAsync(string url, string filePath, IProgress<DownloadProgress> progress, CancellationToken token)
{
const int bufferLength = 8192;
long downloadedBytes = File.Exists(filePath) ? new FileInfo(filePath).Length : 0;
using (HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, url))
{
if (downloadedBytes > 0) request.Headers.Range = new RangeHeaderValue(downloadedBytes, null);
using (HttpResponseMessage response = await _httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, token).ConfigureAwait(false))
{
response.EnsureSuccessStatusCode();
using (Stream contentStream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false))
using (FileStream fs = new FileStream(filePath, FileMode.Append, FileAccess.Write, FileShare.None))
{
long totalBytes = downloadedBytes + response.Content.Headers.ContentLength ?? 0;
byte[] buffer = new byte[bufferLength];
int bytesReceived;
while ((bytesReceived = await contentStream.ReadAsync(buffer, 0, buffer.Length, token).ConfigureAwait(false)) > 0)
{
await fs.WriteAsync(buffer, 0, bytesReceived, token).ConfigureAwait(false);
downloadedBytes += bytesReceived;
progress?.Report(new DownloadProgress
{
BytesDownloaded = downloadedBytes,
TotalBytes = totalBytes
});
}
}
}
}
}
WaitForReconnectAsync - 启动等待 60 秒以恢复 Internet 连接的过程的方法
private async Task WaitForReconnectAsync(CancellationToken token)
{
int elapsedTime = 0;
while (elapsedTime < maxWaitTime)
{
token.ThrowIfCancellationRequested();
if (PingServer("4.2.2.4"))
{
_connectionEstablishmentFailed = false; // Сбрасываем флаг при успешном восстановлении сети Интернет
return;
}
await Task.Delay(checkInterval, token);
lbProgress.Text = Properties.Resources.ConnectWithServerLost + $" {elapsedTime / 1000} sec.";
elapsedTime += checkInterval;
}
_connectionEstablishmentFailed = true; // Соединение не восстановлено в течении maxWaitTime
}
PingServer - 向指定地址发送请求的方法
private bool PingServer(string host)
{
try
{
using (var ping = new Ping())
{
var reply = ping.Send(host, 1000);
return reply.Status == IPStatus.Success;
}
}
catch { return false; }
}