我想实现一个批量下载的任务:
比如有最多有5个任务,每个任务有3(可调)个线程各自进行下载和保存,类似迅雷等软件, 此功能已实现。
但现有一个问题,网络请求是最耗资源的,本地文件存储相对快一些(如果下载的速度比操作本地I/O还快,我想就不叫下载了)。我打算给每个任务再多开一个线程来专门做文件存储的任务,在下载线程收到下载数据后交给保存线程,下载线程立即去请求下一个数据块。想知道这三个线程如何安全地把数据交给本地给I/O线程,有没有成熟的模型?
使用线程池和自己控制线程皆可,我都想知道?
对于IO密集型操作,你应该使用 APM 模型,也就是利用 Socket,File 的异步版本来编写程序,遇到在异步回调中需要长时间执行的代码,可以将其放置到线程池中去执行.
线程池中如何控制每个线程的暂停和继续,以及如何控制对同一个文件进行并行写入,我不会这个,所以暂时用不了线程池。
@沧海一杰: 在I/O密集型的应用程序中,应该采用数据流,而非控制流,通过并发数据结构来同步.因为I/O总是很缓慢.
@Launcher: 那么想像迅雷一样,十个任务,每个任务三个线程同时下载,如何停止其中中的某几个任务呢? 采用数据流是在哪一步来停止呢?
@沧海一杰: 我现在在停止是给每个任务里放一个全局变量,它们下载过程中会不断判断这个标志,如果为ture就退出,我在stop方法里,
ExitFlag =true;
while(true)
{
var flag =true;
foreach(var t in theads)
if(t.IsAlive)
flag =false;
if(flag)
break;
}
Debug.Print("Stopped");
@沧海一杰: 你可以看一下这片文章:http://www.csharpwin.com/dotnetspace/6462r8404.shtml
然后你可以通过给 WebReqState 添加一个 bool isCancelled 成员变量和一个 Cancel(当然通常我们不会这么做,会设计一个 CancellationToken 类) 方法来取消任务.在每次 BeginXXX 之前判断一次 isCancelled 来决定是否继续.
因为你的程序主要是I/O,线程会经常性的被阻塞用于等待 I/O(可以断定,在你写的代码中,被阻塞的线程在I/O返回前是无法用于执行其它代码);而使用 APM 时,线程会立即返回,同时允许再次被调度用于执行其它代码,这样你的程序在I/O请求较多时,就不会创建大量的线程去等待I/O完成。
@Launcher: 这个IO其实倒不是最大的瓶颈,最大的瓶颈是网络延迟。上一个数据块和下一个数据块会有阻塞,所以我用了异步,然后轮询,在轮询期间我完成了同步IO保存,性能还是可以的。
@沧海一杰: 其实吧这些知识你可以找本《操作系统》的书一看就知道了,但是你好像不喜欢看书,所以我这里就简单说下。I/O(Input/Output),输入/输出,指代的是在计算机和外部环境之间移动数据。外部环境由各种外部设备组成,包括辅助存储设备(如硬盘)、通信设备和终端。你这里的“网络延迟”,实际上就是我说的“I/O阻塞”的一种具体表现。以后吧,如果你再碰到计算机术语,如果没有系统的学习过计算机理论知识,那么最好还是先了解下。
@Launcher: 是啊,好长时间没看书了。多谢!
保存本地个人觉得不要每个下载线程都开一个本地IO线程。5个任务一个本地IO线程应该就行了,除非你下载速度非常惊人。
像迅雷这类软件都有个缓存设置
用一个文件缓存队列,和五个下载线程互锁,每当一个下载线程的缓存量达到设定值的时候缓存队列加锁,把缓存复制到缓存队列,然后释放锁,下载线程继续下载。一个本地IO线程锁定缓存队列,拷贝到新队列(为了防止写入磁盘时持续锁定原队列阻碍下载),清空原队列,释放原队列的锁。本地IO线程从新队列逐个写入磁盘。完了继续以上步骤。我自己一个项目实现的方式,仅供参考
搞一个List做Buffer
搞一个ManualResetEvent作通知
然后网络线程循环中
var data = download(from, to);
lock(buffer){
buffer.Add(data);
event.Set();
}
在IO线程循环中
event.WaitOne();
List copy = new List();
lock(buffer) {
copy.AddRange(buffer);
buffer.Clear();
event.Reset();
}
foreach(var data in copy) {
save(data);
}
大致是这么个意思吧