在现代网络环境中,文件下载是一个常见的需求。为了提高下载效率,经常需要同时下载多个文件。本文将介绍如何使用C#和WinForms创建一个多线程文件下载器应用程序,允许用户轻松下载任意数量的文件。
本应用程序基于之前文章中介绍的wwwdsl组件。下载存档包含了示例应用程序和包含wwwdsl组件的项目。
解决方案的核心是允许用户轻松启动多个文件的下载。用户不需要关心下载进度(例如,等待网页上的按钮)。用户需要向应用程序提供定义处理过程的配方文件。如何创建配方文件,请参考之前的文章。解决方案包含了两个来源的文件:一个是从web服务器下载普通文件(download.wwwdsl),另一个是Rapidshare配方(rs.wwwdsl)。用户可以通过“配方...”按钮注册配方。
在弹出的对话框中,用户需要选择配方文件,给配方命名,并标记是否允许同时处理。对于普通文件下载,应标记为允许;对于Rapidshare,如果不是付费用户,则不建议允许。
设置好配方后,用户可以通过“添加条目...”选择要下载的文件。可以在文本框中粘贴多个下载位置的多行。同时,也需要为文件选择配方。
文件的下载顺序取决于主窗口列表中的位置(当然,只有在可以下载的情况下;如果不可以,则选择下一个项目)。用户可以使用“向上”和“向下”按钮来改变顺序。
最后一个设置是最大工作线程数,即最大并发下载数。设置完成后,用户可以使用“开始”按钮开始下载。如果出于某种原因需要中止所有下载,用户可以按“停止”按钮。再次按“开始”将重新启动未完成的下载。可以使用弹出菜单(或按“删除”键)删除项目。
应用程序允许多个并发下载,因此它是一个多线程应用程序。当用户按下“开始”时,会启动线程。线程选择要处理的项目。选择在临界区进行,以避免竞态条件(同一个项目被两个线程选中)。如果没有更多的项目可以下载,线程会等待监视器的条件变量以等待变化或新项目。
private List m_entries;
...
private void WorkerFun(ThreadData td)
{
while (!td.Stop)
{
Entry entry = null;
RecipeEvaluator rpc;
WwwDslEvaluationContext evalContext;
lock (m_entries)
{
rpc = null;
foreach (var e in m_entries.Where(e => e.State == EntryState.Pending || e.State == EntryState.Waiting))
{
rpc = EntryCheck(e);
if (rpc != null)
{
entry = e;
break;
}
}
if (rpc == null)
{
Monitor.Wait(m_entries);
if (td.Stop)
{
return;
}
continue;
}
rpc.Processed = true;
entry.State = EntryState.Processing;
evalContext = new WwwDslEvaluationContext(rpc.CompilerContext.Runtime, rpc.RootNode);
m_processedEntries.Add(evalContext, entry);
m_threads[Thread.CurrentThread].EntryData = entry;
}
...
}
}
这里有一个与从不同线程刷新网格控件相关的额外说明。它们不能这样做(只有主线程可以操作GUI)。因此,ControlRefresh方法检查控件是否可以被操作(使用InvokeRequired属性)。在这种情况下,调用BeginInvoke开始在主线程上下文中的ControlRefresh方法。
private DateTime _lastRefresh = DateTime.Now;
private void ControlRefresh(bool forced)
{
if (m_control.InvokeRequired)
{
if (forced || (DateTime.Now - _lastRefresh.AddSeconds(1)).TotalSeconds >= 1)
{
m_control.BeginInvoke(new MethodInvoker(delegate { ControlRefresh(forced); }));
_lastRefresh = DateTime.Now;
}
}
else
{
m_control.Refresh();
}
}