这些 1k 线程从何而来
Posted
技术标签:
【中文标题】这些 1k 线程从何而来【英文标题】:Where do these 1k threads come from 【发布时间】:2015-07-22 11:01:20 【问题描述】:我正在尝试创建一个多线程从网站下载图像的应用程序,作为线程的介绍。 (以前从未正确使用过线程)
但目前它似乎创建了 1000 多个线程,我不确定它们来自哪里。
我首先将一个线程排队到线程池中,对于初学者来说,作业数组中只有一个作业
foreach (Job j in Jobs)
ThreadPool.QueueUserWorkItem(Download, j);
它在一个新线程上启动 void Download(object obj)
,它循环一定数量的页面(需要的图像/每页 42 个图像)
for (var i = 0; i < pages; i++)
var downloadLink = new System.Uri("http://www." + j.Provider.ToString() + "/index.php?page=post&s=list&tags=" + j.Tags + "&pid=" + i * 42);
using (var wc = new WebClient())
try
wc.DownloadStringAsync(downloadLink);
wc.DownloadStringCompleted += (sender, e) =>
response = e.Result;
ProcessPage(response, false, j);
;
catch (System.Exception e)
// Unity editor equivalent of console.writeline
Debug.Log(e);
如果我错了,请纠正我,下一个 void 会在同一个线程上调用
void ProcessPage(string response, bool secondPass, Job j)
var wc = new WebClient();
LinkItem[] linkResponse = LinkFinder.Find(response).ToArray();
foreach (LinkItem i in linkResponse)
if (secondPass)
if (string.IsNullOrEmpty(i.Href))
continue;
else if (i.Href.Contains("http://loreipsum."))
if (DownloadImage(i.Href, ID(i.Href)))
j.Downloaded++;
else
if (i.Href.Contains(";id="))
var alterResponse = wc.DownloadString("http://www." + j.Provider.ToString() + "/index.php?page=post&s=view&id=" + ID(i.Href));
ProcessPage(alterResponse, true, j);
最后传递到最后一个函数,下载实际图片
bool DownloadImage(string target, int id)
var url = new System.Uri(target);
var fi = new System.IO.FileInfo(url.AbsolutePath);
var ext = fi.Extension;
if (!string.IsNullOrEmpty(ext))
using (var wc = new WebClient())
try
wc.DownloadFileAsync(url, id + ext);
return true;
catch(System.Exception e)
if (DEBUG) Debug.Log(e);
else
Debug.Log("Returned Without a extension: " + url + " || " + fi.FullName);
return false;
return true;
我不确定我是如何开始这么多线程的,但我很想知道。
编辑
此程序的目标是同时下载不同作业中的作业(最多 5 个),每个作业一次最多下载 42 张图像。
所以在任何时候都可以/应该最多下载 210 张图片。
【问题讨论】:
你在另一个线程中运行异步操作。为什么不只使用异步,就是这样。 ?在这种情况下,线程为您提供了哪些好处? @Tigran 可能没有,只是想掌握线程的窍门,然后在线程情况下使用阻塞调用而不是异步调用更有意义吗? 如果你使用异步,不要使用线程。如果您要控制并发工作负载,请使用线程,因此根据需要跨越尽可能多的线程,而不是更多。 VS:调试->Windows->线程。它应该解释为什么你有这么多线程。 (也不知道你为什么用 unity3d 标记帖子 - 也许它有什么特别之处)。 @AlexeiLevenkov 在统一引擎(游戏引擎)中制作它,与通常的 .net 相比,它的功能有限。 【参考方案1】:首先,您是如何测量线程数的?为什么你认为你的应用程序中有数千个?您正在使用ThreadPool
,因此您不会自己创建它们,ThreadPool
不会根据需要创建如此大量的它们。
其次,您在代码中混合了同步和异步操作。由于您不能使用TPL
和async/await
,所以让我们检查您的代码并计算您正在创建的unit-of-works
,以便您可以最小化它们。执行此操作后,ThreadPool
中的排队项目数将减少,您的应用程序将获得所需的性能。
你没有在你的应用程序中设置SetMaxThreads
方法,所以,according the MSDN:
最大线程池线程数 可以排队到线程池的操作数量仅受可用内存的限制; 但是,线程池限制了可以使用的线程数 同时活跃在这个过程中。 默认限制为 25 每个 CPU 的工作线程和 1,000 个 I/O 完成线程。
因此您必须将最大值设置为5
。
我在您的代码中找不到用于检查每个作业的 42
图像的位置,您只是在增加 ProcessPage
方法中的值。
ManagedThreadId
中WebClient.DownloadStringCompleted
的句柄 - 它是否在不同的线程中执行。
您在ThreadPool
队列中添加新项目,为什么要使用异步操作进行下载?使用synchronious overload,像这样:
ProcessPage(wc.DownloadString(downloadLink), false, j);
这不会在ThreadPool
队列中创建另一个项目,并且您不会在此处进行同步上下文切换。
在ProcessPage
中,您的wc
变量不会被垃圾回收,因此您不会在这里释放所有资源。在此处添加using
声明:
void ProcessPage(string response, bool secondPass, Job j)
using (var wc = new WebClient())
LinkItem[] linkResponse = LinkFinder.Find(response).ToArray();
foreach (LinkItem i in linkResponse)
if (secondPass)
if (string.IsNullOrEmpty(i.Href))
continue;
else if (i.Href.Contains("http://loreipsum."))
if (DownloadImage(i.Href, ID(i.Href)))
j.Downloaded++;
else
if (i.Href.Contains(";id="))
var alterResponse = wc.DownloadString("http://www." + j.Provider.ToString() + "/index.php?page=post&s=view&id=" + ID(i.Href));
ProcessPage(alterResponse, true, j);
在DownloadImage
方法中,您还使用异步加载。这也会在ThreadPoll
队列中添加项目,我认为您可以避免这种情况,也可以使用synchronious overload:
wc.DownloadFile(url, id + ext);
return true;
因此,一般而言,请避免上下文切换操作并正确处置您的资源。
【讨论】:
【参考方案2】:您的 wc WebClinet 将超出范围并在异步回调之前被随机垃圾收集。此外,在所有异步调用中,您必须允许立即返回和实际的委托函数返回。所以 processPage 必须在两个地方。此外,原始循环中的 j 可能超出范围,具体取决于原始循环中的 Download 声明的位置。
【讨论】:
以上是关于这些 1k 线程从何而来的主要内容,如果未能解决你的问题,请参考以下文章