在 C# 应用程序中使用大量线程时出现内存不足异常
本文关键字:线程 异常 内存不足 应用程序 | 更新日期: 2023-09-27 18:35:07
我正在用C#编写一个应用程序,该应用程序循环浏览维基百科本地数据库副本的文章。我使用一堆正则表达式在这些文章中找到正确的信息,启动一个线程来获取每篇文章的图像,保存信息并转到下一篇文章。
我需要使用代理列表来下载这些图像,以免被谷歌禁止。由于代理可能很慢,我使用线程进行并行下载。
如果我不使用线程,则应用程序工作正常,但需要一段时间才能获取所有信息。
如果我使用线程,应用程序将一直工作,直到它使用大约 500 个线程,然后我收到内存不足异常。
问题是它只使用~300Mo的RAM,所以它不会使用可用的总内存(8Go)和分配给单个32位应用程序的内存的所有内存。
每个应用程序的线程数有限制吗?
编辑:
这是下载海报的代码(从getPosterAsc()开始)。
string ddlValue = "";
private void tryDownload(object obj)
{
WebClient webClientProxy = new WebClient();
Tuple<WebProxy, int> proxy = (Tuple<WebProxy, int>)((object[])obj)[0];
if (proxy != null)
webClientProxy.Proxy = proxy.Item1;
try
{
ddlValue = webClientProxy.DownloadString((string)((object[])obj)[1]);
}
catch (Exception ex) {
ddlValue = "";
Console.WriteLine("trydownload:" + ex.Message);
}
webClientProxy.Dispose();
}
public void getPoster(object options = null)
{
if (options == null)
options = new object[2] { toSave, false };
if (!AppVar.debugMode && AppVar.getImages && this.getImage)
{
if (this.original_name != "" && !this.ambName && this.suitable)
{
Log.CountImgInc();
MatchCollection MatchList;
string basic_options = "";
string value = "";
WebClient webClient = new WebClient();
Regex reg;
bool found = false;
if (original_name.Split(' ').Length > 1) image_options = "";
if (!found)
{
bool succes = false;
int countTry = 0;
while (!succes)
{
Tuple<WebProxy, int> proxy = null;
if (countTry != 5)
proxy = Proxy.getProxy();
try
{
Thread t = new Thread(tryDownload);
if (!(bool)((object[])options)[1])
t.Start(new object[] { proxy, @"http://www.google.com/search?as_st=y&tbm=isch&as_q=" + image_options + "+" + basic_options + "+" + image_options_before + "%22" + simplify(original_name) + "%22+" + " OR %22" + original_name + "%22+" + image_options_after + this.image_format });
else
t.Start(new object[] { proxy, @"http://www.google.com/search?as_st=y&tbm=isch&as_q=" + image_options + "+" + basic_options + "+" + image_options_before + "%22" + simplify(original_name) + "%22+" + " OR %22" + original_name + "%22+" + image_options_after + "&biw=1218&bih=927&tbs=isz:ex,iszw:758,iszh:140,ift:jpg&tbm=isch&source=lnt&sa=X&ei=kuG7T6qaOYKr-gafsOHNCg&ved=0CIwBEKcFKAE" });
if (!t.Join(40000))
{
Proxy.badProxy(proxy.Item1.Address.Host, proxy.Item1.Address.Port);
continue;
}
else
{
value = ddlValue;
if (value != "")
succes = true;
else
Proxy.badProxy(proxy.Item1.Address.Host, proxy.Item1.Address.Port);
}
}
catch (Exception ex)
{
if (proxy != null)
Proxy.badProxy(proxy.Item1.Address.Host, proxy.Item1.Address.Port);
}
countTry++;
}
reg = new Regex(@"imgurl'=(.*?)&imgrefurl", RegexOptions.IgnoreCase);
MatchList = reg.Matches(value);
if (MatchList.Count > 0)
{
bool foundgg = false;
int j = 0;
while (!foundgg && MatchList.Count > j)
{
if (MatchList[j].Groups[1].Value.Substring(MatchList[j].Groups[1].Value.Length - 3, 3) == "jpg")
{
try
{
string guid = Guid.NewGuid().ToString();
webClient.DownloadFile(MatchList[j].Groups[1].Value, @"c:'temp'" + guid + ".jpg");
FileInfo fi = new FileInfo(@"c:'temp'" + guid + ".jpg");
this.image_size = fi.Length;
using (Image img = Image.FromFile(@"c:'temp'" + guid + ".jpg"))
{
int minHeight = this.cov_min_height;
if ((bool)((object[])options)[1])
minHeight = 100;
if (img.RawFormat.Equals(System.Drawing.Imaging.ImageFormat.Jpeg) && img.HorizontalResolution > 70 && img.Size.Height > minHeight && img.Size.Width > this.cov_min_width && this.image_size < 250000)
{
foundgg = true;
image_name = guid;
image_height = img.Height;
image_width = img.Width;
img.Dispose();
if ((bool)((object[])options)[0])
{
Mediatly.savePoster(this, (bool)((object[])options)[1]);
}
}
else
{
img.Dispose();
File.Delete(@"c:'temp'" + guid.ToString() + ".jpg");
}
}
}
catch (Exception ex)
{
}
}
j++;
}
}
}
webClient.Dispose();
Log.CountImgDec();
}
}
}
public void getPosterAsc(bool save = false, bool banner = false)
{
ThreadPool.QueueUserWorkItem(new WaitCallback(getPoster), new object[2] { save, banner });
}
我会确保您使用线程池来"管理"您的线程。正如有人所说,每个线程消耗大约 1MB 的内存,根据系统硬件,这可能会导致您的问题。
解决此问题的一种潜在方法是使用线程池。这通过在可能的情况下共享和回收线程来减少生成所有线程所产生的开销。这允许低级线程工具(有许多线程处于活动状态),但限制了这样做的性能损失。
线程池还对它将同时运行的工作线程数(请注意,这些线程都将是后台线程)保持限制。过多的操作线程会带来很大的管理开销,并且可能"使 CPU 缓存无效"。达到将施加的线程池限制后,其他作业将排队,并在另一个工作线程空闲时执行。我觉得这是一种更有效、更安全、更节省资源的方式来满足您的需求。
根据您当前的代码,有多种方法可以进入线程池:
-
BackgroundWorker
. -
ThreadPool.QueueUserWorkItem
. - 异步委托。
- 普林克。
就个人而言,我会使用 TPL,因为它很棒!我希望这有所帮助。
perfmon 检查实际使用内存的内容,特别是密切注意"修改的页面列表字节数"值。这在多线程应用程序上可能特别麻烦,其中对文件的引用保留了特定的时间长度 - 高利用率此值的通常(临时)解决方案是增加可用的虚拟内存。
此外,如果在Windows Server 2008上运行高线程应用程序,则需要从Microsoft应用dynacache,以防止系统文件缓存有效地占用可用内存。
上述两个问题都可能直接与处理大量数据的 .net 多线程应用程序相关,不幸的是,它们没有显示为您的应用程序正在使用,因此可能很难跟踪(正如我在痛苦的几天中发现的那样)
当您使用 32 位可执行文件时,默认情况下实际上只能分配 2Gb 而不是 8Gb(有关更多信息,请参阅此处:http://blogs.msdn.com/b/tom/archive/2008/04/10/chat-question-memory-limits-for-32-bit-and-64-bit-processes.aspx)
尝试限制您的工作线程,这样您就不会使用那么多,并确保在线程执行的代码上没有内存泄漏。
使用 try... 包装您的线程执行catch(如果您在线程执行代码上收到 OutOfMemoryException),因为它可能与您下载的图像有关
我最近在我的一个应用程序中遇到了一个问题,看起来与此非常相似。 它与在单个"字符串"对象中存储和使用的数据量有关。 如果我不得不猜测,您的内存不足异常来自初始分配
ddlValue = webClientProxy.DownloadString((string)((object[])obj)[1]);
如果可以重写它来执行此操作,是否可以找到一种方法将 Web 返回作为流访问,而不是将整个响应读取为字符串。 然后,您可以使用流读取器按行分析 Web 响应。
是的,我知道这听起来很复杂,但它与我最终不得不在自己的代码中使用的解决方案相匹配。 我正在处理太大而无法存储为单个字符串的内容,并且必须直接从流中访问它们。