C# For循环中并发的思路
问题
最开始的问题,是我需要对一组的网页链接进行下载,一开始单线程的时候,就是一个网页下载,保存,结束,然后下载第二个网页。
结果当然是没有问题的,可是这效率也太低了啊,那就想了几个问题:
1、能不能同时下载多个网页?
这个是多线程问题,我一开始使用的是async/await
看代码:
private async void DownloadText(BookModel book)
{
var directories = directory.GetList<Model.DirectoryModel>(d => d.BookId == book.Id && d.State == true);
foreach (var directory in directories)
{
await Task.Run(()=> { DownloadText(directory, book); });
}
}
下载方法是async 方法,说明内部有异步的代码。
await 是异步的位置,for中,异步执行这一个方法,循环注册执行之后,for循环就结束了。
这里就有一个问题。。。我for循环是结束了,可是我下载还没有结束啊,都在异步中呢,能不能在for结束的后面,让它等待一下,等所有代码结束?
async/await我没有找到解决的办法,换一个写法。
2、等待所有异步方法结束
private void DownloadText(BookModel book)
{
var directories = directory.GetList<Model.DirectoryModel>(d => d.BookId == book.Id && d.State == true);
Task<bool>[] tasks = new Task<bool>[directories.Count];
for(int i = 0; i < directories.Count; i++)
{
//这一步是必须的,不能将directories[i]直接放到Task中做DownloadText的参数,否者i会每次循环后发生变化,传到DownloadText里面就不是你想要传的那个对象了。
var dir = directories[i];
tasks[i] = Task<bool>.Factory.StartNew(() => { return DownloadText(dir, book); });
//这种写法是不可行的,i会被外部变化,directories[i]传入的不一定是你预期的那个参数
//tasks[i] = Task<bool>.Factory.StartNew(() => { return DownloadText(directories[i], book); });
}
Task.WaitAll(tasks);
}
WaitAll会等待所有异步结束,这样,就可以等待你for循环中,所有的异步都结束之后,才会进行下一步操作。
3、控制Task的并发数量
2的内容中,Task是没有数量控制的,如果For循环中有1000个下载链接,然后瞬间Task执行1000个,然后等待。。。想想那个后果。
嗯,事实上,我这边是已经遇到了这个情况,最多的时候500+,程序会直接卡滞数秒。
而效率方面,单线程下载一秒钟2-3个网页,并发之后是5-6个网页,我不知道为什么效率就提高了这么点,而程序卡滞的却很严重。
想想解决的办法?可以限制并发的数量么?很显然,我找到了办法:
https://blog.csdn.net/starfd/article/details/79711915
控制并发执行的Task数量
需要在NuGet中下载一个MSFT.ParallelExtensionsExtras,直接NuGet中搜索就可以了。
然后定义一个并发数MaxTask,传到Task.Factory.StartNew()里面去。
private void DownloadText(BookModel book)
{
DownLoadTextChanging?.Invoke(book, null);
var directories = directory.GetList<Model.DirectoryModel>(d => d.BookId == book.Id && d.State == true && d.Len == null);
var scheduler = new LimitedConcurrencyLevelTaskScheduler(2);
//记录一下,单线程下载,一秒钟下载2-3章。
//异步下载一秒钟5-6章,而且程序很容易卡滞。
Task<bool>[] tasks = new Task<bool>[directories.Count];
for(int i = 0; i < directories.Count; i++)
{
//这一步是必须的,不能将directories[i]直接放到Task中做DownloadText的参数,否者i会每次循环后发生变化,传到DownloadText里面就不是你想要传的那个对象了。
var dir = directories[i];
tasks[i] = Task<bool>.Factory.StartNew(() => { return DownloadText(dir, book); }, CancellationToken.None, TaskCreationOptions.None, scheduler);
}
Task.WaitAll(tasks);
DownLoadTextChanged?.Invoke(book, null);
}
都不需要太多,我异步的数量写了2,就同样一秒钟可以下载5-6章,和刚才没有限制的并发是一样的,或许Task其内部有默认的数量限制?
这个暂且不知,但我限制了数量之后,效率没有降低,程序却已经不会卡了。
4、for循环中执行固定数量的并行
先说明,这个我还没有测试,我在论坛里询问,刚刚看到这个回复,先写上来,我回头试一下。
Parallel.ForEach(list, new ParallelOptions() {MaxDegreeOfParallelism=20 }, x => Console.WriteLine(x.Name));
new ParallelOptions() {MaxDegreeOfParallelism=20 } 代表线程数量,看字面意思,应该是可以指定For中的并发数,这个回头试试。