用C#编写多线程客户端程序
CLR为每一个托管进程维护了一个线程池,这意味着当你的应用程序主线程需要进行某些异步处理时,你可以很容易的从线程池中借助某个线程实现特定的处理。一旦处理工作完成,线程被归还到线程池以便以后使用。让我们看一个例子,修改使用线程池。
注意Figure 3 中FlawMultiThreadForm.cs中红色部分表示的行;它们是由Figure 1 中的单线程变为多线程程序 时唯一要修改的代码。如果你编译Figure 3 所示的代码,并设置运行20秒,你将看到当处理20个响铃的请求时,仍然能够响应用户的交互。在客户端程序中使用多线程来响应用户交互是一个吸引人的原因。
Figure 3 FlawedMultiThreadedForm.cs
using System; using System.Drawing; using System.Threading; using System.Windows.Forms; using Microsoft.VisualBasic; class App { // Application entry point public static void Main() { // Run a Windows Forms message loop Application.Run(new FlawedMultiThreadedForm()); } } // A Form-derived type class FlawedMultiThreadedForm : Form { // Constructor method public FlawedMultiThreadedForm() { // Create a text box text.Location = new Point(10, 10); text.Size = new Size(50, 20); Controls.Add(text); // Create a button button.Text = "Beep"; button.Size = new Size(50, 20); button.Location = new Point(80, 10); // Register Click event handler button.Click += new EventHandler(OnClick); Controls.Add(button); } // Method called by the button's Click event void OnClick(Object sender, EventArgs args) { // Get an int from a string Int32 count = 0; try { count = Int32.Parse(text.Text); } catch (FormatException) {} // Count to that number WaitCallback async = new WaitCallback(Count); ThreadPool.QueueUserWorkItem(async, count); } // Async method beeps once per second void Count(Object param) { Int32 seconds = (Int32) param; for (Int32 index = 0; index < seconds; index++) { Interaction.Beep(); Thread.Sleep(1000); } } // Some private fields by which to reference controls Button button = new Button(); TextBox text = new TextBox(); }
然而,在Figure 3 中所做的变化,却引入了一个新问题(如 Figure 3 的名字一样);现在用户可以启动多个同时响铃的长操作。在许多实时应用中这会导致线程间的冲突。为了修正这个线程同步请求,我将讲述这些,但首先熟悉一下CLR''''s线程池。
类库中的System.Threading.ThreadPool类提供了一个访问CLR''''s线程池的API接口, ThreadPool类型不能被实例化,它由静态成员组成。ThreadPool类型最重要的方法是对ThreadPool.QueueUserWorkItem的两个重载。这两种方法让你定义一个你愿意被线程池中的一个线程进行回调的函数。通过使用类库中的WaitCallback委托类型的一个实例来定义你的方法。一种重载让你对异步方法定义一个参数;这是Figure 3 所使用的版本。
下面的两行代码创建一个委托实例,代表了一个Count方法,接下来的调用排队等候让线程池中的方法进行回调。
WaitCallback async = new WaitCallback(Count); ThreadPool.QueueUserWorkItem(async, count);
ThreadPool.QueueUserWorkItem 的两个方法让你在队列中定义一个异步回调方法,然后立即返回。 同时线程池监视这个队列,接着出列方法,并使用线程池中的一个或多个线程调用该方法。这是CLR''''s线程池的主要用法。
CLR''''s线程池也被系统的其它APIs所使用。例如, System.Threading.Timer对象在定时间隔到来时将会在线程池中排队等候回调。 ThreadPool.RegisterWaitForSingleObject 方法当响应内核系统同步对象有信号时会在线程池中排队等候调用。最后,回调由类库中的不同异步方法执行,这些异步方法又由CLR''''s线程池来执行。
一般来说,一个应用程序仅仅对于简单的异步操作需要使用多线程时毫无疑问应该使用线程池。相比较手工创建一个线程对象,这种方法是被推荐的。调用ThreadPool.QueueUserWorkItem执行简单,而且相对于重复的手动创建线程来说能够更好的利用系统资源。