簡體   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