这些1k个线程是从哪里来的?

4

我正在尝试创建一个应用程序,通过多线程从网站下载图片,作为线程介绍的一部分。(以前从未正确使用过线程)

但目前似乎创建了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张图片。


4
为什么不直接使用async,而要在另一个线程中运行异步操作?在这种情况下,线程提供了哪些好处? - Tigran
@Tigran 可能没有,只是试图掌握线程的使用,如果在线程情况下使用阻塞调用而不是异步调用会更有意义吗? - MX D
2
如果你正在使用async,请不要使用线程。如果你要控制并发工作负载,使用线程,因此生成所需的线程数量即可,不要超过。 - Tigran
@MaxYankov .NET 2.0/3.5 - MX D
@MXD 你具体如何测量线程数量?你运行的是哪个系统? - Max Yankov
显示剩余13条评论
2个回答

2
首先,你是如何测量线程计数的?为什么认为应用程序中有数千个线程?由于使用了ThreadPool,因此你不需要自己创建它们,而且ThreadPool也不会为自己的需求创建如此大量的线程。
其次,你在代码中混合使用同步和异步操作。既然不能使用TPLasync/await,那么让我们检查你的代码并计算你创建的unit-of-works数量,以便将其最小化。完成后,ThreadPool中排队的项目数量将减少,应用程序将获得所需的性能提升。
  1. You don't set the 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.

    So you must set the maximum to the 5.

  2. I can't find a place in your code where you check the 42 images per Job, you are only incrementing the value in ProcessPage method.

  3. Check the ManagedThreadId for the handle of WebClient.DownloadStringCompleted - does it execute in different thread or not.
  4. You are adding the new item in ThreadPool queue, why are you using the asynchronious operation for Downloading? Use a synchronious overload, like this:

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

    This will not create another one item in ThreadPool queue, and you wouldn't have a sinchronisation context switch here.

  5. In ProcessPage your wc variable doesn't being garbage collected, so you aren't freeing all your resourses here. Add using statement here:

    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. In DownloadImage method you also use the asynchronious load. This also adds item in ThreadPoll queue, and I think that you can avoid this, and use synchronious overload too:

    wc.DownloadFile(url, id + ext);
    return true; 
    
因此,一般情况下,避免上下文切换操作并适当处理您的资源。

0

在异步回调之前,您的wc WebClinet将会超出作用域,并被随机垃圾收集。此外,在所有的异步调用中,您必须允许即时返回和实际委托函数的返回。因此,processPage将需要在两个地方进行处理。另外,在原始循环中的j变量可能会超出作用域,具体取决于原始循环中的Download是在哪里声明的。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接