现代应用程序中,文件下载是一个非常常见的需求。为了提高下载效率和用户体验,我们通常会采用多线程并发下载的方式来实现。这篇博客将介绍如何使用C#中的多线程来实现文件的并发下载。
目录
一、项目介绍及前期准备
本项目旨在通过多线程并发下载文件,提高下载速度和效率。首先在代码实现前我们需要做以下准备:
下载管理对象的初始化:
DownLoadFile dlf = new DownLoadFile();
表单加载事件:
private void Form1_Load(object sender, EventArgs e)
{
dlf.ThreadNum = 3; // 线程数,不设置默认为3
dlf.doSendMsg += SendMsgHander; // 下载过程处理事件
}
- 在表单加载时,设置
dlf.ThreadNum
为 3,表示最多使用 3 个并发线程下载文件。 - 将
SendMsgHander
事件处理程序绑定到dlf.doSendMsg
事件,以处理下载过程中的各种消息。
二、代码实现
在创建好类和构造完函数后,我们下面来实现并发下载的功能。
1.添加下载任务并启动下载,按钮点击事件:
private void btnTest_Click(object sender, EventArgs e)
{
string[] lines = File.ReadAllLines("软件下载1.txt"); //当用户点击按钮时,读取名为 "软件下载1.txt" 的文件。
for (int i = 0; i < lines.Length; i++)
{
string[] line = lines[i].Split(new string[] { "|" }, StringSplitOptions.RemoveEmptyEntries);
if (line.Length == 2)
{
string path = Uri.EscapeUriString(line[1]);
string filename = Path.GetFileName(path);
string dir = @"F:\test";
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] })); //在 listView1 中添加一项,显示下载任务的基本信息,并获取其索引 id
int id = item.Index;
dlf.AddDown(path, dir, id, id.ToString());
}
}
dlf.StartDown();
}
2.处理下载过程中的消息:
使用
AddDown
方法添加新的下载任务并创建线程。
private void SendMsgHander(DownMsg msg)
{
switch (msg.Tag)
{
case DownStatus.Start:
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:
this.Invoke((MethodInvoker)delegate ()
{
listView1.Items[msg.Id].SubItems[3].Text = msg.LengthInfo;
listView1.Items[msg.Id].SubItems[8].Text = "连接成功";
});
break;
根据 msg.Tag
不同的值处理下载过程中的不同状态:
DownStatus.Start
:表示下载任务开始,更新listView1
中对应项的状态和开始时间。DownStatus.GetLength
:表示成功获取文件长度,更新listView1
中对应项的文件大小和状态。DownStatus.End
和DownStatus.DownLoad
:表示下载完成或下载中,更新listView1
中对应项的下载进度、速度、剩余时间和状态。
case DownStatus.End:
case DownStatus.DownLoad:
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;
使用 this.Invoke
方法确保在主线程中执行更新操作:
- 更新
ListView
中对应项的第 3 列(索引为 2)为已下载的文件大小msg.SizeInfo
。 - 更新第 5 列(索引为 4)为下载进度百分比
msg.Progress
。 - 更新第 6 列(索引为 5)为下载速度
msg.SpeedInfo
。 - 更新第 7 列(索引为 6)为剩余时间
msg.SurplusInfo
。 - 根据
msg.Tag
的值,更新第 9 列(索引为 8)为 "下载中" 或 "下载完成"。 - 调用
Application.DoEvents
确保界面及时刷新。
DownStatus.Error
:表示下载出错,更新listView1
中对应项的错误信息和状态。
case DownStatus.Error:
this.Invoke((MethodInvoker)delegate ()
{
listView1.Items[msg.Id].SubItems[6].Text = "失败";
listView1.Items[msg.Id].SubItems[8].Text = msg.ErrMessage;
Application.DoEvents();
});
break;
msg.Tag
为DownStatus.Error
,表示下载任务出错。- 使用
this.Invoke
方法确保在主线程中执行更新操作:- 更新
ListView
中对应项的第 7 列(索引为 6)为 "失败"。 - 更新第 9 列(索引为 8)为错误信息
msg.ErrMessage
。 - 调用
Application.DoEvents
确保界面及时刷新。
- 更新
三、对于线程安全的 UI 操作
使用 this.Invoke
或 this.Invoke((MethodInvoker)delegate () {...})
确保在 UI 线程中进行控件的操作,以避免跨线程调用导致的异常。
四、结果展示
五、总结
这段代码展示了如何使用 DownLoadFile
类实现文件的并发下载,并通过事件机制在下载过程中实时更新 UI 控件(如 ListView
)中的信息。主要流程包括:
- 初始化
DownLoadFile
对象并绑定事件。 - 读取下载任务并将其添加到下载队列中。
- 启动下载任务。
- 处理下载过程中的各种状态和事件,并实时更新 UI 显示。
这种设计方式确保了下载任务的并发执行和下载过程的实时监控。