繁体   English   中英

C# Parallel.ForEach() 内存使用量不断增长

[英]C# Parallel.ForEach() memory usage keeps growing

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);

我观察到该项目的 RAM 使用量不断增长在此处输入图片说明

我猜 Parallel.ForEach() 有问题,但我想不通。

是否存在内存泄漏,或者发生了什么?


更新 1

改成新代码后

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();
    }
}

在此处输入图片说明

在保持运行 9 小时后,我仍然观察到内存使用量增加,但使用量增长缓慢。

只是想知道,是不是因为我没有释放 theTrack 文件的内存使用?

顺便说一句,我使用ALT 包来更新文件元数据,不幸的是,它没有实现 IDisposable 接口。

使用WebClient.DownloadFile()直接下载到文件,这样您就不会在内存中保存整个文件。

Parallel.ForEach方法旨在并行化受 CPU 限制的工作负载。 下载文件是 I/O 绑定的工作负载,因此Parallel.ForEach不适合这种情况,因为它不必要地阻塞了ThreadPool线程。 正确的做法是异步的,使用 async/await。 推荐的异步 Web 请求类是HttpClient ,控制并发级别的一个很好的选择是TPL 数据流库。 对于这种情况,使用这个库中最简单的组件ActionBlock类就足够了:

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);
    }
}

使用HttpClient下载文件的代码不像WebClient.DownloadFile()那样简单,但这是您必须执行的操作,以保持整个过程异步(从 Web 读取和写入磁盘)。


警告:异步文件系统操作目前在 .NET 中没有有效实现 为了获得最大效率,最好避免在FileStream构造函数中使用FileOptions.Asynchronous选项。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM