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

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

  1. 你没有设置 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

  2. 我在你的代码中找不到检查每个作业的 42 图像的地方,你只是在 ProcessPage 方法中增加值。

  3. 检查 ManagedThreadId 以获得 WebClient.DownloadStringCompleted 的句柄 - 它是否在不同的线程中执行。
  4. 您正在向 ThreadPool 队列中添加新项目,为什么要使用异步操作进行下载?使用 synchronious overload,像这样:

    ProcessPage(wc.DownloadString(downloadLink), false, j);
    

    这不会在 ThreadPool 队列中创建另一个项目,并且您不会在此处进行同步上下文切换。

  5. 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);
                    }
                }
            }
        }
    }
    
  6. DownloadImage方法中你也使用了异步加载。这也会在 ThreadPoll 队列中添加项目,我认为您可以避免这种情况,也可以使用 synchronious overload

    wc.DownloadFile(url, id + ext);
    return true; 
    

因此,一般来说,避免上下文切换操作并妥善处理您的资源。