1.前言
在许多软件和应用程序中,需要从网络上下载文件并更新到本地。如果文件较大,或者网络不太稳定,单线程下载可能会耗费大量时间和带宽。因此,开发一个支持多线程下载的文件下载器,可以提高下载效率,增强用户体验。本文将介绍一个基于 C# 的多线程文件下载器的实现。
2.主要功能
- 支持多线程下载:用户可以设置线程数,默认为 3 个线程同时下载。当某个线程下载失败时,会自动启动新的线程继续下载。
- 提供下载进度显示:下载过程中,会实时显示已下载大小、下载速度、预计剩余时间等信息。
- 支持断点续传:当下载过程中断时,可以从上次中断的位置继续下载,不需要重新下载整个文件。
3.界面设计
其中使用了以下 Windows Forms 控件
-
ListView:
- 用于显示下载任务的详细信息,如文件名、下载进度、下载速度等。
- 在代码中,
listView1
对象被用来操作 ListView 控件,如添加新的 ListViewItem 等。
-
Button:
- 在
Form1
窗体中,有一个名为btnTest
的按钮,用于启动下载任务。 - 按钮的
Click
事件被用来触发下载逻辑。
- 在
-
事件处理:
Form1
类中定义了一个名为SendMsgHander
的方法,用于处理DownLoadFile
类中发送的下载状态更新消息。- 该方法使用
Invoke
方法来确保在 UI 线程中更新 ListView 控件的内容。
-
文件操作:
- 在
btnTest_Click
方法中,代码读取了名为 "软件下载1.txt" 的文件,并解析其中的下载任务信息。 - 然后将这些任务信息添加到 ListView 控件中,并启动下载任务。
- 在
4.代码实现
4.1.界面代码
private void btnTest_Click(object sender, EventArgs e)
{
// 读取 "软件下载1.txt" 文件中的所有行
string[] lines = File.ReadAllLines("软件下载1.txt");
// 遍历每一行数据
for (int i = 0; i < lines.Length; i++)
{
// 将每一行数据按照 '|' 分隔符分割成数组
string[] line = lines[i].Split(new string[] { "|" }, StringSplitOptions.RemoveEmptyEntries);
// 如果分割后的数组长度为 2
if (line.Length == 2)
{
// 获取下载路径,并对其进行 URI 编码
string path = Uri.EscapeUriString(line[1]);
// 从下载路径中获取文件名
string filename = Path.GetFileName(path);
// 设置下载目录为 "E:\test"
string dir = @"E:\test";
// 在 ListView 控件中添加一个新的项目
ListViewItem item = listView1.Items.Add(new ListViewItem(new string[] {
(listView1.Items.Count + 1).ToString(), // 序号
filename, // 文件名
"0", // 下载进度
"0", // 下载速度
"0%", // 下载百分比
"0", // 已下载大小
"0", // 文件大小
DateTime.Now.ToString(), // 开始时间
"等待中", // 状态
line[1] // 下载路径
}));
// 获取新添加项目的索引
int id = item.Index;
// 将下载任务添加到 dlf 对象中
dlf.AddDown(path, dir, id, id.ToString());
}
}
// 启动下载任务
dlf.StartDown();
}
这个代码片段主要负责解析文件中的下载任务信息,并将其添加到 ListView 控件中,同时将下载任务交给 dlf 对象进行处理。这种方式可以方便地在 Windows 窗体中展示和管理多个下载任务。
private void Form1_Load(object sender, EventArgs e)
{
dlf.ThreadNum = 3;//线程数,不设置默认为3
dlf.doSendMsg += SendMsgHander;//下载过程处理事件
}
设置线程数和下载过程处理事件
private void SendMsgHander(DownMsg msg)
{
// 根据下载状态标签 (msg.Tag) 进行不同的处理
switch (msg.Tag)
{
case DownStatus.Start:
// 在 UI 线程中更新 ListView 项的开始下载状态和开始时间
this.Invoke((MethodInvoker)delegate ()
{
listView1.Items[msg.Id].SubItems[8].Text = "开始下载";
listView1.Items[msg.Id].SubItems[7].Text = DateTime.Now.ToString();
});
break;
case DownStatus.GetLength:
// 在 UI 线程中更新 ListView 项的连接成功状态和连接长度信息
this.Invoke((MethodInvoker)delegate ()
{
listView1.Items[msg.Id].SubItems[3].Text = msg.LengthInfo;
listView1.Items[msg.Id].SubItems[8].Text = "连接成功";
});
break;
case DownStatus.End:
case DownStatus.DownLoad:
// 在 UI 线程中更新 ListView 项的下载进度、下载速度、剩余时间和下载状态
this.Invoke(new MethodInvoker(() =>
{
this.Invoke((MethodInvoker)delegate ()
{
listView1.Items[msg.Id].SubItems[2].Text = msg.SizeInfo;
listView1.Items[msg.Id].SubItems[4].Text = msg.Progress.ToString() + "%";
listView1.Items[msg.Id].SubItems[5].Text = msg.SpeedInfo;
listView1.Items[msg.Id].SubItems[6].Text = msg.SurplusInfo;
if (msg.Tag == DownStatus.DownLoad)
{
listView1.Items[msg.Id].SubItems[8].Text = "下载中";
}
else
{
listView1.Items[msg.Id].SubItems[8].Text = "下载完成";
}
Application.DoEvents();
});
}));
break;
case DownStatus.Error:
// 在 UI 线程中更新 ListView 项的下载失败状态和错误信息
this.Invoke((MethodInvoker)delegate ()
{
listView1.Items[msg.Id].SubItems[6].Text = "失败";
listView1.Items[msg.Id].SubItems[8].Text = msg.ErrMessage;
Application.DoEvents();
});
break;
}
}
总的来说,这段代码实现了将下载过程中的各种状态信息更新到 listView1 控件中,为用户提供了一个直观的下载进度展示界面。
4.2功能函数代码
private void Change(DownMsg msg)
{
// 如果下载状态是"错误"或"结束",则启动下一个下载任务
if (msg.Tag == DownStatus.Error || msg.Tag == DownStatus.End)
{
StartDown(1);
}
}
public void AddDown(string DownUrl, string Dir, int Id = 0, string FileName = "")
{
// 创建一个新的下载任务线程,并添加到任务列表中
Thread tsk = new Thread(() =>
{
download(DownUrl, Dir, FileName, Id);
});
list.Add(tsk);
}
public void StartDown(int StartNum = 3)
{
// 启动指定数量的下载任务线程
for (int i2 = 0; i2 < StartNum; i2++)
{
lock (list)
{
// 遍历任务列表,查找尚未启动或已暂停的下载任务线程
for (int i = 0; i < list.Count; i++)
{
if (list[i].ThreadState == System.Threading.ThreadState.Unstarted || list[i].ThreadState == ThreadState.Suspended)
{
// 启动找到的下载任务线程
list[i].Start();
break;
}
}
}
}
}
这段代码实现了一个简单的下载任务管理器,可以根据下载状态自动启动新的下载任务,并且可以同时运行多个下载任务。这种设计可以提高下载效率,但需要注意线程安全问题,以及可能产生的资源竞争和消耗等问题。
public delegate void dlgSendMsg(DownMsg msg);
public event dlgSendMsg doSendMsg;
// 'doSendMsg' 事件用于通知订阅者下载操作的进度情况。
private void download(string path, string dir, string filename, int id = 0)
{
try
{
// 创建一个新的DownMsg对象来保存下载状态
DownMsg msg = new DownMsg();
msg.Id = id;
msg.Tag = 0; // 初始状态
// 通知订阅者下载已经开始
doSendMsg(msg);
// 创建一个FileDownloader实例来处理实际的文件下载
FileDownloader loader = new FileDownloader(path, dir, filename, ThreadNum);
loader.data.Clear();
// 更新DownMsg对象的开始状态和文件大小
msg.Tag = DownStatus.Start;
msg.Length = (int)loader.getFileSize();
doSendMsg(msg);
// 创建一个DownloadProgressListener来监控下载进度
DownloadProgressListener linstenter = new DownloadProgressListener(msg);
linstenter.doSendMsg = new DownloadProgressListener.dlgSendMsg(doSendMsg);
// 开始下载过程
loader.download(linstenter);
}
catch (Exception ex)
{
// 如果在下载过程中发生异常,创建一个新的DownMsg对象,并设置错误状态和错误消息
DownMsg msg = new DownMsg();
msg.Id = id;
msg.Length = 0;
msg.Tag = DownStatus.Error;
msg.ErrMessage = ex.Message;
// 通知订阅者下载出现错误
doSendMsg(msg);
// 将错误消息打印到控制台
Console.WriteLine(ex.Message);
}
}
该代码实现了一个文件下载功能,可以通知订阅者下载的进度和状态。它使用 FileDownloader 类来处理实际的文件下载过程,并使用 DownloadProgressListener 类来监控下载进度。当下载开始、完成或出现错误时,它会触发 doSendMsg 事件,通知订阅者相关信息。订阅者可以通过订阅 doSendMsg 事件来获取下载的实时状态,并进行相应的处理。
5.运行结果
注意:序号4的失败是故意设计的
这是我下载的空白文档,下载位置就是 string dir = @"E:\test";下载地址存放在 string[] lines = File.ReadAllLines("软件下载1.txt");
6.github仓库地址
https://github.com/zhiyinnitaimei6/-C-
7.总结
这个 C# 程序实现了一个简单的文件下载功能。它从一个文本文件中读取下载链接和文件名,并将这些信息添加到一个 ListView 控件中。当用户点击"测试"按钮时,程序会启动多线程下载器,并在 ListView 中实时显示下载进度,包括文件大小、下载速度、剩余时间等信息。如果下载出现错误,程序能够自动重新开始下载。这个程序还可以自定义线程数,以适应不同的网络环境。总的来说,这个程序实现了一个功能完整的文件下载功能,展示了使用 C# 进行多线程编程和 Windows 窗体编程的基本技能。通过这个实验,可以学习到如何利用 C# 的各种类库和控件来开发桌面应用程序,以及如何处理文件下载等常见的应用场景。