- r - 以节省内存的方式增长 data.frame
- ruby-on-rails - ruby/ruby on rails 内存泄漏检测
- android - 无法解析导入android.support.v7.app
- UNIX 域套接字与共享内存(映射文件)
我有一个类负责在下载管理器中下载文件。该类负责下载文件并将其写入给定路径。
要下载的文件大小通常在 1 到 5 MB 之间,但也可能更大。我正在使用 WebClient 类的实例从 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);
}
}
与下载项目的文件大小相比,每次下载都会导致非常大的内存增加。如果我下载一个大小约为 3 MB 的文件,内存使用量将增加约 8 MB。
如您所见,下载会产生很多 LOH,下载后不会清除这些 LOH。即使强制 GC 或设置 GCSettings.LargeObjectHeapCompactionMode = GCLargeObjectHeapCompactionMode.CompactOnce;
也无助于防止这种内存泄漏。
比较快照 1 和 2 可以看到内存量是由字节数组产生的,这可能是下载结果。
多次下载表明内存泄漏是多么可怕。
在我看来,这是由 WebClient 实例引起的。但是我无法确定究竟是什么导致了这个问题。如果我强制GC也没关系。这里的屏幕显示它没有强制 gc:
是什么导致过热,我该如何解决?这是一个重大错误,假设下载 100 次或更多,该进程将耗尽内存。
编辑
按照建议,我注释掉了负责设置标签并将 M4A 转换为 MP3 的部分。但是转换器只是对 FFMPEG 的调用,所以它不应该是内存泄漏:
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);
}
}
DownloadCompleted()
方法现在看起来像这样:
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);
}
}
作为补充,我也提交了 DownloadManager
类,因为它正在处理整个下载操作。也许这可能是问题的根源。
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
最佳答案
最后,经过数十次分析和内存检查,问题现在得到解决。
正如@SimonMourier 已经说过的,这个问题与 UploadFile
、DownloadData
、DownloadString
和 DownloadFile
方法。查看它们的后端,您可以看到它们都在使用带有此签名的
WebClient
类中的私有(private) DownloadBits
方法:
private byte[] DownloadBits(WebRequest request, Stream writeStream, CompletionDelegate completionDelegate, AsyncOperation asyncOp)
关于返回类型,很明显为什么行为就像我发现的那样:使用上述方法时,内容保存在字节数组中。因此,如果文件大小 > 85,000 字节,则不建议使用这些方法,因为这会导致填充 LOH,直到达到内存限制。如果文件很小,但随着大小的增加,LOH 也会以倍数增长,这可能并不重要。
作为补充,我的最终解决方案:
public class DownloadItem : DownloadManagerItem
{
#region Fields
private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
private WebClient _webClient;
#endregion
#region Properties
public string SavePath { get; }
public bool Overwrite { get; }
public DownloadFormat DownloadFormat { get; }
#endregion
public DownloadItem(PlaylistItem item, string savePath, DownloadFormat downloadFormat, bool overwrite = false)
: base(item)
{
SavePath = savePath;
Overwrite = overwrite;
DownloadFormat = downloadFormat;
}
public override 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;
}
using (_webClient = new WebClient())
{
_webClient.Headers.Add("user-agent",
"Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.2; .NET CLR 1.0.3705;)");
try
{
_webClient.OpenReadCompleted += WebClientOnOpenReadCompleted;
_webClient.OpenReadAsync(new Uri(Item.DownloadUrl));
}
catch (Exception ex)
{
Logger.Warn(ex, "Error downloading track {0}", Item.VideoId);
OnDownloadItemDownloadCompleted(new DownloadCompletedEventArgs(true, ex));
}
}
}
private void WebClientOnOpenReadCompleted(object sender, OpenReadCompletedEventArgs openReadCompletedEventArgs)
{
_webClient.Dispose();
if (openReadCompletedEventArgs.Cancelled)
{
OnDownloadItemDownloadCompleted(new DownloadCompletedEventArgs(true, openReadCompletedEventArgs.Error));
return;
}
if (!Overwrite && File.Exists(SavePath))
return;
var totalLength = 0;
try
{
totalLength = int.Parse(((WebClient)sender).ResponseHeaders["Content-Length"]);
}
catch (Exception)
{
// ignored
}
try
{
long processed = 0;
var tmpPath = Path.GetTempFileName();
using (var stream = openReadCompletedEventArgs.Result)
using (var fs = File.Create(tmpPath))
{
var buffer = new byte[16 * 1024];
int read;
while ((read = stream.Read(buffer, 0, buffer.Length)) > 0)
{
fs.Write(buffer, 0, read);
processed += read;
OnDownloadItemDownloadProgressChanged(new DownloadProgressChangedEventArgs(processed, totalLength));
}
}
File.Move(tmpPath, SavePath);
OnDownloadItemDownloadCompleted(new DownloadCompletedEventArgs(false));
}
catch (Exception ex)
{
OnDownloadItemDownloadCompleted(new DownloadCompletedEventArgs(true, ex));
}
}
public override void StopDownload()
{
_webClient?.CancelAsync();
}
public override void Dispose()
{
_webClient?.Dispose();
}
public override int GetHashCode()
{
return Item.GetHashCode();
}
public override bool Equals(object obj)
{
var item = obj as DownloadItem;
return Item.Equals(item?.Item);
}
}
不过感谢您的帮助!
关于C# WebClient - 下载文件后 LOH 大幅增加,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/39997931/
我正在编写一个类库来在我无法控制的站点上执行操作。该网站正在接受表单帖子作为输入。 谁能告诉我这两种方法除了上传数据的形式之外是否有区别? System.Net.WebClient.Uploa
用于工作的代码。 有问题的网址是 https://yobit.net/api/3/info 它适用于 IE。它曾经与 webclient 一起使用。它现在在 webclient 中不起作用。我想知道问
因此,我将我的 WebClient 包装在一个 using 语句中。但是我突然想知道,如果我的对象实现了 IDisposable 并且包装在 using 语句中,我是否需要取消订阅事件? 下面是我当前
我正在 VS15 测试版中工作并尝试使用 WebClient。虽然 System.Net 被引用,并且智能感知建议 WebClient 类可用,但在构建时我收到以下错误: The type or na
我想知道是否可以将 cookie 从一个 Web 客户端复制到另一个 Web 客户端。 原因 我正在使用并行 Web 请求,它会在每个新线程上创建 Web 客户端的新实例。 问题 信息敏感,需要使用p
我正在尝试使用 WebClient,但它给我错误,所以我检查了几个论坛(包括这个),他们告诉我把它放在哪里 在文件的顶部: using System.Net 在我想使用 WebClient 的地方之后
我正在尝试使用 WebClient 实现以下场景。使用 RestTemplate 很简单,但我不能再这样做了。 伪java代码中Spring Controller 的相关部分: Mono t1 = w
我正在使用 Spring WebClient 调用休息服务。如下所述的 post 调用代码。 Mono response = client.post()
正在尝试使用 WebClient在 Blazor 项目中。 得到以下错误: 在 blazor.webassembly.js:1 WASM: System.Net.WebException: An ex
我正在使用 ASP.NET Core 并尝试将文件下载到绝对路径。 但我遇到的问题是文件总是被下载到项目目录,文件名本身得到整个路径的名称。 我的代码: string path = @"C:\User
我需要自动化涉及使用登录表单的网站的流程。我需要在登录页面之后的页面中捕获一些数据。 我知道如何对普通页面进行屏幕抓取,但不知道如何抓取安全站点背后的页面。 这可以通过 .NET WebClient
我正在尝试逐步下载一系列序列化数据。目标是从服务器发送一个大块,并在下载时在客户端对其进行部分处理。 我正在使用 System.Net.WebClient 类并将其 AllowReadStreamBu
我在 Windows 桌面应用程序上使用此代码来获取组合框的值,之后我需要选择哪个组合框将使用 JavaScript 使用新信息更新页面 private WebBrowser withEventsFi
我正在尝试通过 C# 代码获取网站的 HTML 源代码。当我使用 Windows 身份验证访问站点时,以下代码有效: using (WebClient client = new WebClient()
我只是使用WebClient.DownloadString(),但速度慢得惊人。最大速度为 40kbs 我尝试将 WebClient.Proxy 设置为 null,但这不起作用,而且我还没有达到最大互
为了利用新的 WebClient API,我在我的 Intellij 项目中包含了 spring-webflux。 dependencies { implementation 'org.spr
我已经开始使用 WebClient,并使用过滤器方法添加请求/响应日志记录: WebClient.builder() .baseUrl(properties.getEndpoint())
我正在使用 WebClient.DownloadFile 将图像下载到本地存储库,如下所示: WebClient myWC = new WebClient();
我尝试使用网络客户端非阻塞方法验证验证码响应。所以它的工作,但我需要我的方法返回 boolean 值而不是异常。我如何从订阅返回值? webClient
这个问题已经有答案了: What does a "Cannot find symbol" or "Cannot resolve symbol" error mean? (18 个回答) 已关闭 3 年
我是一名优秀的程序员,十分优秀!