Tôi có một lớp chịu trách nhiệm tải xuống tệp trong trình quản lý tải xuống. Lớp này chịu trách nhiệm tải xuống tệp và ghi nó vào đường dẫn đã cho.C# WebClient - Mức tăng lớn của LOH sau khi tải xuống các tệp
Kích thước của tệp để tải xuống thay đổi bình thường từ 1 đến 5 MB nhưng cũng có thể lớn hơn nhiều. Tôi đang sử dụng một thể hiện của lớp WebClient để lấy tệp từ internet.
public class DownloadItem
{
#region Events
public delegate void DownloadItemDownloadCompletedEventHandler(object sender, DownloadCompletedEventArgs args);
public event DownloadItemDownloadCompletedEventHandler DownloadItemDownloadCompleted;
protected virtual void OnDownloadItemDownloadCompleted(DownloadCompletedEventArgs e)
{
DownloadItemDownloadCompleted?.Invoke(this, e);
}
public delegate void DownloadItemDownloadProgressChangedEventHandler(object sender, DownloadProgressChangedEventArgs args);
public event DownloadItemDownloadProgressChangedEventHandler DownloadItemDownloadProgressChanged;
protected virtual void OnDownloadItemDownloadProgressChanged(DownloadProgressChangedEventArgs e)
{
DownloadItemDownloadProgressChanged?.Invoke(this, e);
}
#endregion
#region Fields
private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
private WebClient _client;
#endregion
#region Properties
public PlaylistItem Item { get; }
public string SavePath { get; }
public bool Overwrite { get; }
#endregion
public DownloadItem(PlaylistItem item, string savePath, bool overwrite = false)
{
Item = item;
SavePath = savePath;
Overwrite = overwrite;
}
public void StartDownload()
{
if (File.Exists(SavePath) && !Overwrite)
{
OnDownloadItemDownloadCompleted(new DownloadCompletedEventArgs(true));
return;
}
OnDownloadItemDownloadProgressChanged(new DownloadProgressChangedEventArgs(1));
Item.RetreiveDownloadUrl();
if (string.IsNullOrEmpty(Item.DownloadUrl))
{
OnDownloadItemDownloadCompleted(new DownloadCompletedEventArgs(true, new InvalidOperationException("Could not retreive download url")));
return;
}
// GCSettings.LargeObjectHeapCompactionMode = GCLargeObjectHeapCompactionMode.CompactOnce;
using (_client = new WebClient())
{
_client.Headers.Add("user-agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.2; .NET CLR 1.0.3705;)");
try
{
_client.DownloadDataCompleted +=
(sender, args) =>
{
Task.Run(() =>
{
DownloadCompleted(args);
});
};
_client.DownloadProgressChanged += (sender, args) => OnDownloadItemDownloadProgressChanged(new DownloadProgressChangedEventArgs(args.ProgressPercentage));
_client.DownloadDataAsync(new Uri(Item.DownloadUrl));
}
catch (Exception ex)
{
Logger.Warn(ex, "Error downloading track {0}", Item.VideoId);
OnDownloadItemDownloadCompleted(new DownloadCompletedEventArgs(true, ex));
}
}
}
private void DownloadCompleted(DownloadDataCompletedEventArgs args)
{
// _client = null;
// GCSettings.LargeObjectHeapCompactionMode = GCLargeObjectHeapCompactionMode.CompactOnce;
// GC.Collect(2, GCCollectionMode.Forced);
if (args.Cancelled)
{
OnDownloadItemDownloadCompleted(new DownloadCompletedEventArgs(true, args.Error));
return;
}
try
{
File.WriteAllBytes(SavePath, args.Result);
using (var file = TagLib.File.Create(SavePath))
{
file.Save();
}
try
{
MusicFormatConverter.M4AToMp3(SavePath);
}
catch (Exception)
{
// ignored
}
OnDownloadItemDownloadCompleted(new DownloadCompletedEventArgs(false));
}
catch (Exception ex)
{
OnDownloadItemDownloadCompleted(new DownloadCompletedEventArgs(true, ex));
Logger.Error(ex, "Error writing track file for track {0}", Item.VideoId);
}
}
public void StopDownload()
{
_client?.CancelAsync();
}
public override int GetHashCode()
{
return Item.GetHashCode();
}
public override bool Equals(object obj)
{
var item = obj as DownloadItem;
return Item.Equals(item?.Item);
}
}
Mọi lượt tải xuống đều làm tăng bộ nhớ rất lớn so với kích thước tệp của mục đã tải xuống. Nếu tôi tải xuống một tệp có kích thước ~ 3 MB thì mức sử dụng bộ nhớ sẽ tăng khoảng 8 MB.
Như bạn có thể thấy việc tải xuống được sản xuất nhiều LOH mà không được xóa sau khi tải về. Ngay cả việc buộc GC hoặc thiết lập GCSettings.LargeObjectHeapCompactionMode = GCLargeObjectHeapCompactionMode.CompactOnce;
không giúp ngăn chặn rò rỉ bộ nhớ này.
So sánh Snapshot 1 và 2 bạn có thể thấy rằng dung lượng bộ nhớ được sản xuất bởi các mảng byte mà có thể là kết quả download.
Thực hiện một số lần tải xuống cho thấy mức độ rò rỉ bộ nhớ này khủng khiếp đến mức nào.
Theo tôi, điều này gây ra bởi cá thể WebClient theo bất kỳ cách nào. Tuy nhiên tôi không thể thực sự xác định chính xác những gì đang gây ra vấn đề này. Nó thậm chí không quan trọng nếu tôi buộc GC. Màn hình này cho thấy đây đó mà không cần gc buộc:
gì đang gây ra quá nóng này và làm thế nào tôi có thể sửa chữa nó? Đây là một lỗi lớn và tưởng tượng 100 hoặc nhiều lượt tải xuống quá trình sẽ hết bộ nhớ.
Sửa
Như đề nghị tôi nhận xét ra phần trách nhiệm thiết lập các thẻ và chuyển đổi M4A sang MP3. Tuy nhiên công cụ chuyển đổi chỉ là một tiếng gọi của FFMPEG vì vậy nó không phải là một rò rỉ bộ nhớ:
class MusicFormatConverter
{
public static void M4AToMp3(string filePath, bool deleteOriginal = true)
{
if(string.IsNullOrEmpty(filePath) || !filePath.EndsWith(".m4a"))
throw new ArgumentException(nameof(filePath));
var toolPath = Path.Combine("tools", "ffmpeg.exe");
var convertedFilePath = filePath.Replace(".m4a", ".mp3");
File.Delete(convertedFilePath);
var process = new Process
{
StartInfo =
{
FileName = toolPath,
#if !DEBUG
WindowStyle = ProcessWindowStyle.Hidden,
#endif
Arguments = $"-i \"{filePath}\" -acodec libmp3lame -ab 128k \"{convertedFilePath}\""
}
};
process.Start();
process.WaitForExit();
if(!File.Exists(convertedFilePath))
throw new InvalidOperationException("File was not converted successfully!");
if(deleteOriginal)
File.Delete(filePath);
}
}
Phương pháp DownloadCompleted()
bây giờ trông như thế này:
private void DownloadCompleted(DownloadDataCompletedEventArgs args)
{
// _client = null;
// GCSettings.LargeObjectHeapCompactionMode = GCLargeObjectHeapCompactionMode.CompactOnce;
// GC.Collect(2, GCCollectionMode.Forced);
if (args.Cancelled)
{
OnDownloadItemDownloadCompleted(new DownloadCompletedEventArgs(true, args.Error));
return;
}
try
{
File.WriteAllBytes(SavePath, args.Result);
/*
using (var file = TagLib.File.Create(SavePath))
{
file.Save();
}
try
{
MusicFormatConverter.M4AToMp3(SavePath);
}
catch (Exception)
{
// ignore
}
*/
OnDownloadItemDownloadCompleted(new DownloadCompletedEventArgs(false));
}
catch (Exception ex)
{
OnDownloadItemDownloadCompleted(new DownloadCompletedEventArgs(true, ex));
Logger.Error(ex, "Error writing track file for track {0}", Item.VideoId);
}
}
Kết quả sau khi tải 7 hạng mục: Có vẻ như đây không phải là rò rỉ bộ nhớ.
Ngoài ra, tôi cũng đang nộp lớp DownloadManager
vì nó đang xử lý toàn bộ quá trình tải xuống. Có lẽ đây có thể là nguồn gốc của vấn đề.
public class DownloadManager
{
#region Fields
private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
private readonly Queue<DownloadItem> _queue;
private readonly List<DownloadItem> _activeDownloads;
private bool _active;
private Thread _thread;
#endregion
#region Construction
public DownloadManager()
{
_queue = new Queue<DownloadItem>();
_activeDownloads = new List<DownloadItem>();
}
#endregion
#region Methods
public void AddToQueue(DownloadItem item)
{
_queue.Enqueue(item);
StartManager();
}
public void Abort()
{
_thread?.Abort();
_queue.Clear();
_activeDownloads.Clear();
}
private void StartManager()
{
if(_active) return;
_active = true;
_thread = new Thread(() =>
{
try
{
while (_queue.Count > 0 && _queue.Peek() != null)
{
DownloadItem();
while (_activeDownloads.Count >= Properties.Settings.Default.ParallelDownloads)
{
Thread.Sleep(10);
}
}
_active = false;
}
catch (ThreadInterruptedException)
{
// ignored
}
});
_thread.Start();
}
private void DownloadItem()
{
if (_activeDownloads.Count >= Properties.Settings.Default.ParallelDownloads) return;
DownloadItem item;
try
{
item = _queue.Dequeue();
}
catch
{
return;
}
if (item != null)
{
item.DownloadItemDownloadCompleted += (sender, args) =>
{
if(args.Error != null)
Logger.Error(args.Error, "Error downloading track {0}", ((DownloadItem)sender).Item.VideoId);
_activeDownloads.Remove((DownloadItem) sender);
};
_activeDownloads.Add(item);
Task.Run(() => item.StartDownload());
}
}
#endregion
Phiên bản .NET của bạn là gì? Từ mã của bạn, nó nói: NET CLR 1.0.3705 – Matt
Tôi đang sử dụng .NET Framework 4.5.2 – chris579
WebClient không bị rò rỉ. Rõ ràng bạn nên quan tâm nhiều hơn về "Taglib" và "MusicFormatConverter", các lớp không giống như WebClient là * không * được kiểm tra hàng triệu lần mỗi ngày. Sử dụng một profiler bộ nhớ phong nha để có được trước. –