你如何获得C#中运行线程的列表?

我在C#中创建动态线程,我需要获取那些运行线程的状态。

List[] list; list = dbConnect.Select(); for (int i = 0; i { sendMessage(list[0]['1']); //calling callback function }); th.Name = "SID"+i; th.Start(); } for (int i = 0; i < list[0].Count; i++) { // here how can i get list of running thread here. } 

如何获得正在运行的线程列表?

创建一个List并将每个新线程存储在您的第一个for循环中。

 List[] list; List threads = new List(); list = dbConnect.Select(); for (int i = 0; i < list[0].Count; i++) { Thread th = new Thread(() =>{ sendMessage(list[0]['1']); //calling callback function }); th.Name = "SID"+i; th.Start(); threads.add(th) } for (int i = 0; i < list[0].Count; i++) { threads[i].DoStuff() } 

但是,如果你不需要i你可以使第二个循环成为foreach而不是for


作为旁注,如果你的sendMessage函数执行时间不长,你应该重量轻一些完整的Thread,使用ThreadPool.QueueUserWorkItem或者如果你可以使用它,那么

在线程上

我会避免自己明确创建线程。

使用ThreadPool.QueueUserWorkItem要好得多,或者你可以使用.Net 4.0,你可以获得function更强大的Task并行库 ,它还允许你以更强大的方式使用ThreadPool线程( Task.Factory.StartNew值得一看)

如果我们选择明确创建线程的方法怎么办?

假设您的列表[0] .Count返回1000个项目。 我们还假设您在高端(在撰写本文时)16核机器上执行此操作。 直接影响是我们有1000个线程竞争这些有限的资源(16个核心)。

任务数量越大,每个任务运行的时间越长,在上下文切换中花费的时间就越多。 此外,创建线程很昂贵,如果使用重用现有线程的方法,则可以避免显式创建每个线程的开销。

因此,虽然multithreading的初始意图可能是提高速度,但正如我们所看到的,它可能会产生相反的效果。

我们如何克服“过度”线程?

这是ThreadPool发挥作用的地方。

线程池是一组线程,可用于在后台执行许多任务。

他们是如何工作的:

一旦池中的线程完成其任务,它就会返回到等待线程的队列,在那里可以重用它。 这种重用使应用程序能够避免为每个任务创建新线程的成本。

线程池通常具有最大线程数。 如果所有线程都忙,则将其他任务放入队列中,直到可以在线程可用时为其提供服务。

所以我们可以看到,通过使用线程池线程,我们两者都更有效

  • 最大化实际工作方面。 由于我们没有使用线程使处理器饱和,因此在线程之间切换所花费的时间更少,并且实际执行线程应该执行的代码的时间更长。
  • 更快的线程启动 :每个线程池线程都可以使用,而不是等到构造新线程。
  • 最小化内存消耗而言 ,线程池将线程数限制为线程池大小,以排除超出线程池大小限制的任何请求。 (参见ThreadPool.GetMaxThreads )。 这种设计选择背后的主要原因当然是我们不会过多地使用过多的线程请求来保持有限数量的内核,从而将上下文切换保持在较低级别。

太多的理论,让我们把所有这些理论付诸实践!

是的,很高兴在理论上知道所有这些,但让我们把它付诸实践,看看数字告诉我们的是什么,应用程序的简化原始版本可以给我们粗略指示数量级的差异。 我们将在新的Thread,ThreadPool和任务并行库(TPL)之间进行比较

新线程

  static void Main(string[] args) { int itemCount = 1000; Stopwatch stopwatch = new Stopwatch(); long initialMemoryFootPrint = GC.GetTotalMemory(true); stopwatch.Start(); for (int i = 0; i < itemCount; i++) { int iCopy = i; // You should not use 'i' directly in the thread start as it creates a closure over a changing value which is not thread safe. You should create a copy that will be used for that specific variable. Thread thread = new Thread(() => { // lets simulate something that takes a while int k = 0; while (true) { if (k++ > 100000) break; } if ((iCopy + 1) % 200 == 0) // By the way, what does your sendMessage(list[0]['1']); mean? what is this '1'? if it is i you are not thread safe. Console.WriteLine(iCopy + " - Time elapsed: (ms)" + stopwatch.ElapsedMilliseconds); }); thread.Name = "SID" + iCopy; // you can also use i here. thread.Start(); } Console.ReadKey(); Console.WriteLine(GC.GetTotalMemory(false) - initialMemoryFootPrint); Console.ReadKey(); } 

结果:

新线程基准

ThreadPool.EnqueueUserWorkItem

  static void Main(string[] args) { int itemCount = 1000; Stopwatch stopwatch = new Stopwatch(); long initialMemoryFootPrint = GC.GetTotalMemory(true); stopwatch.Start(); for (int i = 0; i < itemCount; i++) { int iCopy = i; // You should not use 'i' directly in the thread start as it creates a closure over a changing value which is not thread safe. You should create a copy that will be used for that specific variable. ThreadPool.QueueUserWorkItem((w) => { // lets simulate something that takes a while int k = 0; while (true) { if (k++ > 100000) break; } if ((iCopy + 1) % 200 == 0) Console.WriteLine(iCopy + " - Time elapsed: (ms)" + stopwatch.ElapsedMilliseconds); }); } Console.ReadKey(); Console.WriteLine("Memory usage: " + (GC.GetTotalMemory(false) - initialMemoryFootPrint)); Console.ReadKey(); } 

结果:

ThreadPool基准测试

任务并行库(TPL)

  static void Main(string[] args) { int itemCount = 1000; Stopwatch stopwatch = new Stopwatch(); long initialMemoryFootPrint = GC.GetTotalMemory(true); stopwatch.Start(); for (int i = 0; i < itemCount; i++) { int iCopy = i; // You should not use 'i' directly in the thread start as it creates a closure over a changing value which is not thread safe. You should create a copy that will be used for that specific variable. Task.Factory.StartNew(() => { // lets simulate something that takes a while int k = 0; while (true) { if (k++ > 100000) break; } if ((iCopy + 1) % 200 == 0) // By the way, what does your sendMessage(list[0]['1']); mean? what is this '1'? if it is i you are not thread safe. Console.WriteLine(iCopy + " - Time elapsed: (ms)" + stopwatch.ElapsedMilliseconds); }); } Console.ReadKey(); Console.WriteLine("Memory usage: " + (GC.GetTotalMemory(false) - initialMemoryFootPrint)); Console.ReadKey(); } 

结果:

任务并行库结果

所以我们可以看到:

 +--------+------------+------------+--------+ | | new Thread | ThreadPool | TPL | +--------+------------+------------+--------+ | Time | 6749 | 228ms | 222ms | | Memory | ≈300kb | ≈103kb | ≈123kb | +--------+------------+------------+--------+ 

以上内容很符合我们理论上的预期。 与ThreadPool相比,新线程的内存很高,整体性能也较慢。 ThreadPool和TPL具有相同的性能,TPL具有比纯线程池稍高的内存占用,但考虑到任务提供的额外灵活性(例如取消,等待完成查询任务状态),它可能是值得付出的代价

在这一点上,我们已经certificate使用ThreadPool线程是速度和内存方面的首选。

不过,我们还没有回答你的问题。 如何跟踪运行的线程的状态。

回答你的问题

鉴于我们收集的见解,这就是我接近它的方式:

  List[] list = listdbConnect.Select() int itemCount = list[0].Count; Task[] tasks = new Task[itemCount]; stopwatch.Start(); for (int i = 0; i < itemCount; i++) { tasks[i] = Task.Factory.StartNew(() => { // NOTE: Do not use i in here as it is not thread safe to do so! sendMessage(list[0]['1']); //calling callback function }); } // if required you can wait for all tasks to complete Task.WaitAll(tasks); // or for any task you can check its state with properties such as: tasks[1].IsCanceled tasks[1].IsCompleted tasks[1].IsFaulted tasks[1].Status 

最后要注意的是,你不能在Thread.Start中使用变量i,因为它会在变化的变量上创建一个闭包,它实际上可以在所有线程之间共享。 为了解决这个问题(假设您需要访问i),只需创建变量的副本并传入副本,这将使每个线程有一个闭包,这将使线程安全。

祝好运!

使用Process.Threads

 var currentProcess = Process.GetCurrentProcess(); var threads = currentProcess.Threads; 

注意:当前进程拥有的任何线程都将显示在此处,包括那些未由您明确创建的线程。

如果您只想要创建的线程,那么为什么不在创建它们时跟踪它们呢?

 Process.GetCurrentProcess().Threads 

这将为您提供当前进程中运行的所有线程的列表,但请注意,除了您自己创建的线程之外,还有其他线程。

使用Process.Threads遍历您的线程。