这些 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 不会根据需要创建如此大量的它们。

其次,您在代码中混合了同步和异步操作。由于您不能使用TPLasync/await,所以让我们检查您的代码并计算您正在创建的unit-of-works,以便您可以最小化它们。执行此操作后,ThreadPool 中的排队项目数将减少,您的应用程序将获得所需的性能。

    你没有在你的应用程序中设置SetMaxThreads方法,所以,according the MSDN:

    最大线程池线程数 可以排队到线程池的操作数量仅受可用内存的限制; 但是,线程池限制了可以使用的线程数 同时活跃在这个过程中。 默认限制为 25 每个 CPU 的工作线程和 1,000 个 I/O 完成线程。

    因此您必须将最大值设置为5

    我在您的代码中找不到用于检查每个作业的 42 图像的位置,您只是在增加 ProcessPage 方法中的值。

    检查ManagedThreadIdWebClient.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 线程从何而来的主要内容,如果未能解决你的问题,请参考以下文章

Java 内存:引用从何而来?

“纯虚函数调用”崩溃从何而来?

Linux系统启动过程的打印信息从何而来?

我的“存储 PD 容量”费用从何而来?

javadoc 中的@uml.property 标签从何而来?

自然数从何而来?