【问题标题】:Limit Threads count限制线程数
【发布时间】:2010-07-26 10:59:18
【问题描述】:

我有一个包含我要下载的项目的列表。我使用 for 循环来迭代列表。

对于此列表中的每个项目,我都会启动一个引用该项目的新线程。我的问题是我想同时限制 maxDownload。

for (int i = downloadList.Count - 1; i >= 0; i--)
{
    downloadItem item = downloadList[i];
    if (item.Status != 1 && item.Status != 2)
    {
        ThreadStart starter = delegate { this.DownloadItem(ref item); };
        Thread t = new Thread(starter);
        t.IsBackground = true;
        t.Name = item.Name;
        t.Priority = ThreadPriority.Normal;
        t.Start();
    }
}

我阅读了一些关于 ThreadPool 的内容,但是我无法引用我的项目。有人能帮我吗?谢谢! :)

编辑:

我对此进行了测试:

ThreadPool.SetMaxThreads(maxDownloads, maxDownloads);
ThreadPool.SetMinThreads(maxDownloads, maxDownloads);
ThreadPool.QueueUserWorkItem(DownloadItem, ref item);

我不知道如何用这个线程引用我的 downloadItem .....

【问题讨论】:

  • 由线程池服务的工作队列听起来是正确的方法 - 您能否更具体地说明您尝试使用它时遇到的问题?
  • 查看线程池是如何使用的:msdn.microsoft.com/en-us/library/3dasc8as(VS.80).aspx您没有在代码中使用它们。
  • 你几乎肯定会更好地研究如何使用线程池而不是自己创建一大堆短期线程

标签: c# multithreading limit threadpool


【解决方案1】:

如果您使用的是 .NET 4,我强烈建议您使用 Parallel.ForEach(可能在 downloadList.Reverse() 上)

所以,类似:

Parallel.ForEach(downloadList.Reverse(), 
                 new ParallelOptions { MaxDegreeOfParallelism = 8 },
                 item => this.DownloadItem(item));

如果你不希望调用线程阻塞,你当然可以 QueueUserWorkItem 这个调用。

【讨论】:

  • 如果你在 .NET4 上并且不想使用 Parallel.ForEach,那么新的 Task 东西也值得一看,因为它解决了使用 ThreadPool 的很多问题直接。
  • 警告:不要假设Parallel.For, Parallel.ForEach, and Parallel.ForAll 不是并行执行的。 MSDN如果需要确保并行执行,请谨慎使用
【解决方案2】:

我在 .Net 3.5 中通过创建线程并将它们加载到队列中解决了这个问题。然后我从队列中读取一个线程,启动它,并增加正在运行的线程计数。我一直这样做,直到达到上限。

当每个线程完成时,它会调用一个回调方法,该方法会减少运行计数并向队列读取器发出信号以启动更多线程。对于额外的控制,您可以使用字典来跟踪正在运行的线程,以ManagedThreadId 为键,这样您就可以通知线程提前停止或报告进度。

示例控制台应用程序:

using System;
using System.Collections.Generic;
using System.Threading;

namespace ThreadTest
{
    class Program
    {
        static void Main(string[] args)
        {
            Supervisor supervisor = new Supervisor();
            supervisor.LaunchThreads();
            Console.ReadLine();
            supervisor.KillActiveThreads();
            Console.ReadLine();
        }

        public delegate void WorkerCallbackDelegate(int threadIdArg);
        public static object locker = new object();

        class Supervisor
        {
            Queue<Thread> pendingThreads = new Queue<Thread>();
            Dictionary<int, Worker> activeWorkers = new Dictionary<int, Worker>();

            public void LaunchThreads()
            {
                for (int i = 0; i < 20; i++)
                {
                    Worker worker = new Worker();
                    worker.DoneCallBack = new WorkerCallbackDelegate(WorkerCallback);
                    Thread thread = new Thread(worker.DoWork);
                    thread.IsBackground = true;
                    thread.Start();
                    lock (locker)
                    {
                        activeWorkers.Add(thread.ManagedThreadId, worker);
                    }
                }
            }

            public void KillActiveThreads()
            {
                lock (locker)
                {
                    foreach (Worker worker in activeWorkers.Values)
                    {
                        worker.StopWork();
                    }
                }
            }

            public void WorkerCallback(int threadIdArg)
            {
                lock (locker)
                {
                    activeWorkers.Remove(threadIdArg);
                    if (activeWorkers.Count == 0)
                    {
                        Console.WriteLine("no more active threads");
                    }
                }
            }
        }

        class Worker
        {
            public WorkerCallbackDelegate DoneCallBack { get; set; }
            volatile bool quitEarly;

            public void DoWork()
            {
                quitEarly = false;
                Console.WriteLine(Thread.CurrentThread.ManagedThreadId.ToString() + " started");
                DateTime startTime = DateTime.Now;
                while (!quitEarly && ((DateTime.Now - startTime).TotalSeconds < new Random().Next(1, 10)))
                {
                    Thread.Sleep(1000);
                }
                Console.WriteLine(Thread.CurrentThread.ManagedThreadId.ToString() + " stopped");
                DoneCallBack(Thread.CurrentThread.ManagedThreadId);
            }

            public void StopWork()
            {
                quitEarly = true;
            }
        }
    }
}

【讨论】:

    【解决方案3】:

    处理此问题的最佳方法是仅创建 maxDownloads 线程数。将所有工作项放入队列中,让线程相互竞争以确定哪个线程处理每个工作项。

    var queue = new ConcurrentQueue<downloadItem>(downloadList);
    for (int i = 0; i < Math.Min(maxDownloads, queue.Count))
    {
      var thread = new Thread(
        () =>
        {
          while (true)
          {
            downloadItem item = null;
            if (queue.TryDequeue(out item))
            {
              // Process the next work item.
              DownloadItem(item);
            }
            else
            {
              // No more work items are left.
              break;
            }
          }
        });
        thread.IsBackground = true;
        thread.Start();
    }
    

    您还可以使用信号量来限制处理工作项的线程数。这在实际线程数未知时尤其有用,如果您使用的是ThreadPool

    var semaphore = new Semaphore(maxDownloads, maxDownloads);
    for (int i = 0; i < downloadList.Count; i++)
    {
      downloadItem item = downloadList[i];
      ThreadPool.QueueUserWorkItem(
        (state) =>
        {
          semaphore.WaitOne();
          try
          {
            DownloadItem(item);
          }
          finally
          {
            semaphore.Release();
          }
        });
    }
    

    我不是特别喜欢这两种方法。第一个问题是创建了不固定数量的线程。通常建议避免在for 循环中创建线程,因为这往往不能很好地扩展。第二个问题是信号量会阻塞一些ThreadPool 线程。也不建议这样做,因为您有效地声明了其中一个线程,然后什么也不做。这可能会影响碰巧共享ThreadPool 的其他不相关任务的性能。我认为在这种情况下,这两个选项中的任何一个都可以,因为制作一个更具可扩展性的模式比它的价值更多。

    【讨论】:

      【解决方案4】:

      我不明白你为什么要尝试使用ref 关键字。默认情况下,对象在 C# 中通过引用传递,并且在您的原始代码中,在将 item 传递给 DownloadItem 之后,您不会使用它。因此,我建议使用您尝试过的ThreadPool 方法,但不要使用ref 参数。

      希望对您有所帮助。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2012-05-07
        • 2016-08-25
        • 1970-01-01
        • 2011-09-09
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多