这些 1k 线程来自哪里
Where do these 1k threads come from
我正在尝试创建一个多线程从网站下载图像的应用程序,作为对线程的介绍。 (之前从未正确使用线程)
但目前它似乎创建了 1000 多个线程,我不确定它们来自哪里。
我首先将一个线程排入线程池,对于初学者来说,我在作业数组中只有 1 个作业
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 张图像 can/should。
您的 wc WebClinet 将超出范围并在异步回调之前被随机垃圾收集。此外,在所有异步调用中,您必须允许立即 return 和实际委托函数 return。所以 processPage 必须在两个地方。此外,原始循环中的 j 可能超出范围,具体取决于原始循环中 Download 的声明位置。
首先,您是如何测量线程数的?为什么您认为您的应用程序中有数千个?您正在使用 ThreadPool
,因此您不会自己创建它们,并且 ThreadPool
不会根据需要创建如此大量的它们。
其次,您在代码中混合了同步和异步操作。由于您不能使用 TPL
和 async/await
,让我们仔细检查您的代码并计算您正在创建的 unit-of-works
,这样您就可以将它们最小化。执行此操作后,ThreadPool
中排队的项目数将会减少,您的应用程序将获得所需的性能。
你没有设置 SetMaxThreads
method in your application, so, according the MSDN:
Maximum Number of Thread Pool Threads
The number of operations that can be queued to the thread pool is limited only by available memory;
however, the thread pool limits the number of threads that can be
active in the process simultaneously. By default, the limit is 25
worker threads per CPU and 1,000 I/O completion threads.
因此您必须将最大值设置为 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;
因此,一般来说,避免上下文切换操作并妥善处理您的资源。
我正在尝试创建一个多线程从网站下载图像的应用程序,作为对线程的介绍。 (之前从未正确使用线程)
但目前它似乎创建了 1000 多个线程,我不确定它们来自哪里。
我首先将一个线程排入线程池,对于初学者来说,我在作业数组中只有 1 个作业
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 张图像 can/should。
您的 wc WebClinet 将超出范围并在异步回调之前被随机垃圾收集。此外,在所有异步调用中,您必须允许立即 return 和实际委托函数 return。所以 processPage 必须在两个地方。此外,原始循环中的 j 可能超出范围,具体取决于原始循环中 Download 的声明位置。
首先,您是如何测量线程数的?为什么您认为您的应用程序中有数千个?您正在使用 ThreadPool
,因此您不会自己创建它们,并且 ThreadPool
不会根据需要创建如此大量的它们。
其次,您在代码中混合了同步和异步操作。由于您不能使用 TPL
和 async/await
,让我们仔细检查您的代码并计算您正在创建的 unit-of-works
,这样您就可以将它们最小化。执行此操作后,ThreadPool
中排队的项目数将会减少,您的应用程序将获得所需的性能。
你没有设置
SetMaxThreads
method in your application, so, according the MSDN:Maximum Number of Thread Pool Threads
The number of operations that can be queued to the thread pool is limited only by available memory; however, the thread pool limits the number of threads that can be active in the process simultaneously. By default, the limit is 25 worker threads per CPU and 1,000 I/O completion threads.因此您必须将最大值设置为
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;
因此,一般来说,避免上下文切换操作并妥善处理您的资源。