C#Parallel.ForEach()内存使用量不断增长
在连续运行9小时后,我仍然观察到内存使用量在增加,尽管内存使用量增长缓慢 只是想知道,是不是因为我没有释放内存使用的跟踪文件C#Parallel.ForEach()内存使用量不断增长,c#,multithreading,.net-core,C#,Multithreading,.net Core,在连续运行9小时后,我仍然观察到内存使用量在增加,尽管内存使用量增长缓慢 只是想知道,是不是因为我没有释放内存使用的跟踪文件 顺便说一句,我使用更新文件元数据,不幸的是,它没有实现IDisposable接口。使用WebClient.DownloadFile()直接下载到一个文件,这样您就不会将整个文件都存储在内存中。该方法用于并行化CPU绑定的工作负载。下载文件是I/O绑定的工作负载,因此并行.ForEach不适合这种情况,因为它不必要地阻止线程池线程。正确的方法是异步执行,使用async/aw
顺便说一句,我使用更新文件元数据,不幸的是,它没有实现IDisposable接口。使用
WebClient.DownloadFile()
直接下载到一个文件,这样您就不会将整个文件都存储在内存中。该方法用于并行化CPU绑定的工作负载。下载文件是I/O绑定的工作负载,因此并行.ForEach不适合这种情况,因为它不必要地阻止线程池
线程。正确的方法是异步执行,使用async/await。提出异步web请求的推荐类是,而控制并发级别的最佳选项是库。对于这种情况,使用这个库中最简单的组件就足够了,类:
异步任务下载列表异步(列表)
{
使用(var httpClient=new httpClient())
{
var rest=ExcludeDownloaded(列表);
var block=新操作块(异步链接=>
{
等待下载FileAsync(httpClient,链接);
},新的ExecutionDataflowBlockOptions()
{
MaxDegreeOfParallelism=10
});
foreach(rest中的var链接)
{
wait block.SendAsync(链接);
}
block.Complete();
等待区块完成;
}
}
异步任务下载文件异步(HttpClient HttpClient,字符串链接)
{
var fileName=Guid.NewGuid().ToString();//生成唯一文件名的代码;
var filePath=Path.Combine(保存路径,文件名);
如果(File.Exists(filePath))返回;
var response=wait httpClient.GetAsync(link);
response.EnsureSuccessStatusCode();
使用(var contentStream=await response.Content.ReadAsStreamAsync())
使用(var fileStream=newfilestream)(filePath,FileMode.Create,
FileAccess.Write,FileShare.None,32768,FileOptions.Asynchronous)
{
等待contentStream.CopyToAsync(文件流);
}
}
使用HttpClient
下载文件的代码并不像WebClient.DownloadFile()
那么简单,但为了保持整个过程的异步(从web读取和写入磁盘),您必须这样做
警告:异步文件系统操作当前在.NET中。为了获得最大效率,最好避免在
FileStream
构造函数中使用FileOptions.Asynchronous
选项。这不一定是内存泄漏,可能是垃圾收集器没有运行。File.writealBytes(文件名,数据)代码>您有多大的数据
?你试过按块写吗?因为你并行处理这些项目,你在内存中保存了每个下载文件的内容——同时保存了多个。这就是为什么它会增加的原因,特别是当它们是大文件时。正如@Sean所提到的,GC很可能还没有清理。hi@PavelAnikhouski大多数时候,文件将小于1MB,但也可能超过100MB,目前最大的是155MB。如何按块编写?@riQQ DownloadFile()解决了这个问题。谢谢出于好奇,如果我使用File.writealBytes(),每次使用后如何释放它的内存?@Franva基本上,你是在分配大字节数组。它们是由.NET专门处理的。您可以在此处阅读更多内容:GC.Collect()
如果不再持有对对象/字节数组的任何引用,则释放内存。@riQQ确实如此。但是请看一看:重要-我们不建议您在新开发中使用WebClient类。相反,请使用System.Net.Http.HttpClient类。@riQQHttpClient
应该提供更好的性能,因为它重用连接。这是因为您不必为每个请求创建一个新实例,就像您必须使用WebClient
一样。这是一个漂亮的解决方案,有丰富的解释、答案和注释~!很好的例子~!谢谢我会在午休时间尝试一下(我已经开始工作了)>\u@Franva如果你想做一个公平的比较,你应该在Parallel.ForEach
方法之前加上这一行:ThreadPool.SetMinThreads(50,10);
,因此线程池
有50个线程可用于执行50个并发web请求。如果没有这一行,您将引入由饥饿的线程池
导致的隐式限制。测试结果表明,远程服务器或web连接无法处理50个并发操作Action,或存储硬件。我建议您尝试使用较小的并发级别。50似乎远远不是最佳值。谢谢@TheodorZoulias,我将尝试并更新它。感谢您的帮助和解释。我已经了解了多线程编程的见解。
public string SavePath { get; set; } = @"I:\files\";
public void DownloadList(List<string> list)
{
var rest = ExcludeDownloaded(list);
var result = Parallel.ForEach(rest, link=>
{
Download(link);
});
}
private void Download(string link)
{
using(var net = new System.Net.WebClient())
{
var data = net.DownloadData(link);
var fileName = code to generate unique fileName;
if (File.Exists(fileName))
return;
File.WriteAllBytes(fileName, data);
}
}
var downloader = new DownloaderService();
var links = downloader.GetLinks();
downloader.DownloadList(links);
private void Download(string link)
{
using(var net = new System.Net.WebClient())
{
var fileName = code to generate unique fileName;
if (File.Exists(fileName))
return;
var data = net.DownloadFile(link, fileName);
Track theTrack = new Track(fileName);
theTrack.Title = GetCDName();
theTrack.Save();
}
}
async Task DownloadListAsync(List<string> list)
{
using (var httpClient = new HttpClient())
{
var rest = ExcludeDownloaded(list);
var block = new ActionBlock<string>(async link =>
{
await DownloadFileAsync(httpClient, link);
}, new ExecutionDataflowBlockOptions()
{
MaxDegreeOfParallelism = 10
});
foreach (var link in rest)
{
await block.SendAsync(link);
}
block.Complete();
await block.Completion;
}
}
async Task DownloadFileAsync(HttpClient httpClient, string link)
{
var fileName = Guid.NewGuid().ToString(); // code to generate unique fileName;
var filePath = Path.Combine(SavePath, fileName);
if (File.Exists(filePath)) return;
var response = await httpClient.GetAsync(link);
response.EnsureSuccessStatusCode();
using (var contentStream = await response.Content.ReadAsStreamAsync())
using (var fileStream = new FileStream(filePath, FileMode.Create,
FileAccess.Write, FileShare.None, 32768, FileOptions.Asynchronous))
{
await contentStream.CopyToAsync(fileStream);
}
}