在今天的博客中,我们将探讨如何使用C#和Windows Forms来创建一个简单的多线程文件下载管理器。这个文件下载管理器将从文本文件中读取下载链接,并使用多线程技术同时下载多个文件。
准备工作
首先,你需要确保你的开发环境中安装了Visual Studio,并创建了一个Windows Forms项目。此外,你还需要一个包含下载链接的文本文件,每个链接占一行,并可能包含其他相关信息(如文件名),这些信息之间使用分隔符(如“|”)分隔。
设计界面
在Windows Forms设计器中,你可以添加一个ListView
控件来显示下载任务的列表,包括文件名、下载进度等信息。另外,你还需要一个按钮来触发下载过程。
编写代码
定义了DownLoadFile
类的实例dlf
,用于处理下载操作。
DownLoadFile dlf = new DownLoadFile();
btnTest_Click事件处理器
private void btnTest_Click(object sender, EventArgs e)
{
string[] lines = File.ReadAllLines("软件下载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] }));
int id = item.Index;
dlf.AddDown(path, dir, id, id.ToString());
}
}
dlf.StartDown();
}
事件处理器 SendMsgHander
事件处理器 SendMsgHander,用于响应从某个文件下载任务中传递出来的消息。从代码的内容可以看出,这个消息通常是一个自定义类型 DownMsg,其中包含了文件下载的状态信息(如开始、获取长度、下载中、完成、错误等)以及其他相关的数据(如下载的文件大小、已下载大小、速度等)。
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;
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;
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;
}
}
完整代码
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.IO;
using System.Text;
using System.Windows.Forms;
using Gac;
namespace Demo
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
DownLoadFile dlf = new DownLoadFile();
private void btnTest_Click(object sender, EventArgs e)
{
string[] lines = File.ReadAllLines("软件下载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] }));
int id = item.Index;
dlf.AddDown(path, dir, id, id.ToString());
}
}
dlf.StartDown();
}
private void Form1_Load(object sender, EventArgs e)
{
dlf.ThreadNum = 3;//线程数,不设置默认为3
dlf.doSendMsg += SendMsgHander;//下载过程处理事件
}
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;
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;
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;
}
}
多线程下载管理模块
主要实现多线程的管理,根据给定的线程数ThreadNum进行处理。
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
namespace Gac
{
public class DownLoadFile
{
public int ThreadNum = 3;
List<Thread> list = new List<Thread>();
public DownLoadFile()
{
doSendMsg += Change;
}
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;
private void download(string path, string dir,string filename,int id = 0)
{
try
{
DownMsg msg = new DownMsg();
msg.Id = id;
msg.Tag = 0;
doSendMsg(msg);
FileDownloader loader = new FileDownloader(path, dir, filename, ThreadNum);
loader.data.Clear();
msg.Tag = DownStatus.Start;
msg.Length = (int)loader.getFileSize(); ;
doSendMsg(msg);
DownloadProgressListener linstenter = new DownloadProgressListener(msg);
linstenter.doSendMsg = new DownloadProgressListener.dlgSendMsg(doSendMsg);
loader.download(linstenter);
}
catch (Exception ex)
{
DownMsg msg = new DownMsg();
msg.Id = id;
msg.Length = 0;
msg.Tag =DownStatus.Error;
msg.ErrMessage = ex.Message;
doSendMsg(msg);
Console.WriteLine(ex.Message);
}
}
}
}
DownLoadFile
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
namespace Gac
{
public class DownLoadFile
{
public int ThreadNum = 3;
List<Thread> list = new List<Thread>();
public DownLoadFile()
{
doSendMsg += Change;
}
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;
//public event doSendMsg;
//public dlgSendMsg doSendMsg = null;
private void download(string path, string dir,string filename,int id = 0)
{
try
{
DownMsg msg = new DownMsg();
msg.Id = id;
msg.Tag = 0;
doSendMsg(msg);
FileDownloader loader = new FileDownloader(path, dir, filename, ThreadNum);
loader.data.Clear();
msg.Tag = DownStatus.Start;
msg.Length = (int)loader.getFileSize(); ;
doSendMsg(msg);
DownloadProgressListener linstenter = new DownloadProgressListener(msg);
linstenter.doSendMsg = new DownloadProgressListener.dlgSendMsg(doSendMsg);
loader.download(linstenter);
}
catch (Exception ex)
{
DownMsg msg = new DownMsg();
msg.Id = id;
msg.Length = 0;
msg.Tag =DownStatus.Error;
msg.ErrMessage = ex.Message;
doSendMsg(msg);
Console.WriteLine(ex.Message);
}
}
}
}
DownloadProgressListener
using System;
using System.Collections.Generic;
using System.Text;
namespace Gac
{
public class DownloadProgressListener : IDownloadProgressListener
{
private long presize=0;
DownMsg downMsg = null;
public DownloadProgressListener(DownMsg downmsg)
{
this.downMsg = downmsg;
//this.id = id;
//this.Length = Length;
}
public delegate void dlgSendMsg(DownMsg msg);
public dlgSendMsg doSendMsg = null;
public void OnDownloadSize(long size)
{
if (downMsg==null)
{
DownMsg downMsg = new DownMsg();
}
//下载速度
if (downMsg.Size == 0)
{
downMsg.Speed = size;
}
else
{
downMsg.Speed = (float)(size - downMsg.Size);
}
if (downMsg.Speed == 0)
{
downMsg.Surplus = -1;
downMsg.SurplusInfo = "未知";
}
else
{
downMsg.Surplus = ((downMsg.Length - downMsg.Size) / downMsg.Speed);
}
downMsg.Size = size; //下载总量
if (size == downMsg.Length)
{
//下载完成
downMsg.Tag = DownStatus.End;
downMsg.SpeedInfo = "0 K";
downMsg.SurplusInfo = "已完成";
}
else
{
//下载中
downMsg.Tag = DownStatus.DownLoad;
}
if (doSendMsg != null) doSendMsg(downMsg);//通知具体调用者下载进度
}
}
public enum DownStatus
{
Start,
GetLength,
DownLoad,
End,
Error
}
public class DownMsg
{
private int _Length = 0;
private string _LengthInfo = "";
private int _Id = 0;
private DownStatus _Tag = 0;
private long _Size = 0;
private string _SizeInfo = "";
private float _Speed = 0;
private float _Surplus = 0;
private string _SurplusInfo ="";
private string _ErrMessage = "";
private string _SpeedInfo = "";
private double _Progress = 0;
public int Length
{
get
{
return _Length;
}
set
{
_Length = value;
LengthInfo = GetFileSize(value);
}
}
public int Id
{
get
{
return _Id;
}
set
{
_Id = value;
}
}
/// </summary>
public DownStatus Tag
{
get
{
return _Tag;
}
set
{
_Tag = value;
}
}
public long Size
{
get
{
return _Size;
}
set
{
_Size = value;
SizeInfo = GetFileSize(value);
if (Length >= value)
{
Progress = Math.Round((double)value / Length * 100, 2);
}
else
{
Progress = -1;
}
}
}
public float Speed
{
}
//不过多久赘述,省略下方public代码...
}
}
FileDownloader
从以下的FileDownloader
类代码片段中,可以看到这是一个用于多线程文件下载的类的部分实现
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
using System.IO;
using System.Net;
namespace Gac
{
public class FileDownloader
{
private long downloadSize = 0;
private long fileSize = 0;
private DownloadThread[] threads;
private string saveFile;
public Dictionary<int, long> data = new Dictionary<int, long>();
private long block;
private String downloadUrl;
public int getThreadSize()
{
return threads.Length;
}
public long getFileSize()
{
return fileSize;
}
public void append(long size)
{
lock (this) //锁定同步..........
{
downloadSize += size;
}
}
public void update(int threadId, long pos)
{
if (data.ContainsKey(threadId))
{
this.data[threadId] = pos;
}
else
{
this.data.Add(threadId, pos);
}
}
public FileDownloader(string downloadUrl, string fileSaveDir,string filename="", int threadNum=3)
{
try
{
if (string.IsNullOrEmpty(filename))
{
//省略...
}
}
catch (Exception e)
{
Console.WriteLine(e.Message);
throw new Exception("无法连接下载地址");
}
}
public long download(IDownloadProgressListener listener)
{
try
{
using (FileStream fstream = new FileStream(this.saveFile, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.ReadWrite))
{
if (this.fileSize > 0) fstream.SetLength(this.fileSize);
fstream.Close();
}
if (this.data.Count != this.threads.Length)
{
this.data.Clear();
for (int i = 0; i < this.threads.Length; i++)
{
this.data.Add(i + 1, 0);//初始化每条线程已经下载的数据长度为0
}
}
for (int i = 0; i < this.threads.Length; i++)
{//开启线程进行下载
long downLength = this.data[i + 1];
if (downLength < this.block && this.downloadSize < this.fileSize)
{
//判断线程是否已经完成下载,否则继续下载
}
else
{
this.threads[i] = null;
}
}
bool notFinish = true;//下载未完成
while (notFinish)
{// 循环判断所有线程是否完成下载
Thread.Sleep(900);
notFinish = false;//假定全部线程下载完成
for (int i = 0; i < this.threads.Length; i++)
{
if (this.threads[i] != null && !this.threads[i].isFinish())
{//如果发现线程未完成下载
notFinish = true;//设置标志为下载没有完成
if (this.threads[i].getDownLength() == -1)
{//如果下载失败,再重新下载
this.threads[i] = new DownloadThread(this, downloadUrl, this.saveFile, this.block, this.data[i + 1], i + 1);
this.threads[i].ThreadRun();
}
}
}
if (listener != null)
{
listener.OnDownloadSize(this.downloadSize);//通知目前已经下载完成的数据长度
Console.WriteLine(this.downloadSize);
}
}
}
catch (Exception e)
{
Console.WriteLine(e.Message);
throw new Exception("下载文件失败");
}
return this.downloadSize;
}
}
}