VB.net 2010 视频教程 VB.net 2010 视频教程 python基础视频教程
SQL Server 2008 视频教程 c#入门经典教程 Visual Basic从门到精通视频教程
当前位置:
首页 > c#编程 >
  • C#线程 入门-线程池

线程池

每当启动线程时,都会花费数百微秒来组织诸如新鲜的私有局部变量堆栈之类的事情。每个线程(默认情况下)也消耗大约1 MB的内存。线程池通过共享和回收线程来减少这些开销,从而允许在非常细粒度的级别上应用多线程,而不会影响性能。当利用多核处理器以“分而治之”的方式并行执行计算密集型代码时,这很有用。

线程池还限制了将同时运行的工作线程总数。过多的活动线程限制了操作系统的管理负担,并使CPU缓存无效。一旦达到限制,作业将排队并仅在另一个作业完成时才开始。这使任意并发的应用程序(例如Web服务器)成为可能。 (异步方法模式是一种高级技术,通过高效利用池化线程来进一步实现这一点;我们在C#4.0的第23章“ Nutshell”中对此进行了描述)。

有多种进入线程池的方法:

  1. 通过任务并行库(来自Framework 4.0)
  2. 通过调用ThreadPool.QueueUserWorkItem
  3. 通过异步委托
  4. 通过BackgroundWorker

以下构造间接使用线程池:

  • WCF,远程,ASP.NET和ASMX Web服务应用程序服务器
  • System.Timers.Timer和System.Threading.Timer
  • 以Async结尾的框架方法,例如WebClient上的框架方法(基于事件的异步模式),以及大多数BeginXXX方法(异步编程模型模式)
  • PLINQ

任务并行库(TPL)和PLINQ具有足够的功能和高级功能,即使在线程池不重要的情况下,您也希望使用它们来协助多线程。我们将在第5部分中详细讨论这些内容。现在,我们将简要介绍如何使用Task类作为在池线程上运行委托的简单方法。

使用池线程时需要注意以下几点:

  • 您无法设置池线程的名称,从而使调试更加困难(尽管您可以在Visual Studio的“线程”窗口中进行调试时附加说明)。
  • 池线程始终是后台线程(这通常不是问题)。
  • 除非您调用ThreadPool.SetMinThreads(请参阅优化线程池),否则阻塞池中的线程可能会在应用程序的早期阶段触发额外的延迟。
  • 您可以自由更改池线程的优先级-在释放回池时,它将恢复为正常。

 

您可以通过Thread.CurrentThread.IsThreadPoolThread属性查询当前是否在池化线程上执行。

通过TPL进入线程池

您可以使用“任务并行库”中的“任务”类轻松地输入线程池。 Task类是在Framework 4.0中引入的:如果您熟悉较早的构造,请考虑将非通用Task类替换为ThreadPool.QueueUserWorkItem,而将通用Task <TResult>替换为异步委托。与旧版本相比,新版本的结构更快,更方便且更灵活。

 

要使用非泛型Task类,请调用Task.Factory.StartNew,并传入目标方法的委托:

1
2
3
4
5
6
7
8
9
static void Main()    // The Task class is in System.Threading.Tasks
{
  Task.Factory.StartNew (Go);
}
 
static void Go()
{
  Console.WriteLine ("Hello from the thread pool!");
}

  

Task.Factory.StartNew返回一个Task对象,您可以使用该对象来监视任务-例如,您可以通过调用其Wait方法来等待它完成。

 

调用任务的Wait方法时,所有未处理的异常都可以方便地重新抛出到主机线程中。 (如果您不调用Wait而是放弃任务,则未处理的异常将像普通线程一样关闭进程。)

 

通用Task <TResult>类是非通用Task的子类。它使您可以在完成执行后从任务中获取返回值。在下面的示例中,我们使用Task <TResult>下载网页:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
static void Main()
{
  // Start the task executing:
  Task<string> task = Task.Factory.StartNew<string>
    ( () => DownloadString ("http://www.linqpad.net") );
 
  // We can do other work here and it will execute in parallel:
  RunSomeOtherMethod();
 
  // When we need the task's return value, we query its Result property:
  // If it's still executing, the current thread will now block (wait)
  // until the task finishes:
  string result = task.Result;
}
 
static string DownloadString (string uri)
{
  using (var wc = new System.Net.WebClient())
    return wc.DownloadString (uri);
}

 

(突出显示<string>类型的参数是为了清楚:如果我们省略它,则可以推断出它。)

查询包含在AggregateException中的任务的Result属性时,所有未处理的异常都会自动重新抛出。但是,如果您无法查询其Result属性(并且不调用Wait),则任何未处理的异常都会使该过程失败。

任务并行库具有更多功能,特别适合利用多核处理器。我们将在第5部分中继续讨论TPL。

不通过TPL进入线程池

如果目标是.NET Framework的早期版本(4.0之前),则不能使用任务并行库。相反,您必须使用一种较旧的结构来输入线程池:ThreadPool.QueueUserWorkItem和异步委托。两者之间的区别在于异步委托使您可以从线程返回数据。异步委托也将任何异常封送回调用方。

QueueUserWorkItem

要使用QueueUserWorkItem,只需使用要在池线程上运行的委托调用此方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
static void Main()
{
  ThreadPool.QueueUserWorkItem (Go);
  ThreadPool.QueueUserWorkItem (Go, 123);
  Console.ReadLine();
}
 
static void Go (object data)   // data will be null with the first call.
{
  Console.WriteLine ("Hello from the thread pool! " + data);
}
Hello from the thread pool!
Hello from the thread pool! 123

我们的目标方法Go必须接受单个对象参数(以满足WaitCallback委托)。就像使用ParameterizedThreadStart一样,这提供了一种将数据传递给方法的便捷方法。与Task不同,QueueUserWorkItem不会返回对象来帮助您随后管理执行。另外,您必须在目标代码中显式处理异常-未处理的异常将使程序瘫痪。


相关教程