在.NET客户端程序中使用多线程2010-09-24 MSDN Jason Clark通常认为在编写程序中用到多线程是一个高级的编程任务,容易发生错误。在本月的栏目中,我将在一个Windows窗体应用程序中使用多线程,它具有实际的意义,同时尽量使事情简单。我的目标是在一个普通的需求描述中用最好的办法讲解多线程;客户仍然比较喜欢使用户交互方式的应用程序。多线程通常和服务器端软件,可扩展性及性能技术联系在一起。 然而,在微软.NET框架中,许多服务器端应用程序都驻留在ASP.NET体系结构中。同样,这些应用程序在逻辑上是单线程的, 因为IIS和ASP.NET在ASP.NET Web Form或Web服务程序中执行了许多或所有的多线程。 在ASP.NET应用程序中你一般可以忽略线程性。 这就是为什么在.NET框架中,多线程更倾向于在客户端使用的一个原因,比如在保证同用户交互的同时而执行一个很长的操作。线程背景线程执行代码。它们由操作系统实现,是CPU本身的一种抽象。许多系统都只有一个CPU, 线程是把CPU快速的处理能力分开而执行多个操作的一种方法,使它们看起来好像同步似的。即使一个系统由多个CPU, 但运行的线程一般要比处理器多。在一个Windows为基础的应用程序中,每一个进程至少要有一个线程,它能够执行机器语言指令。 一旦一个进程的所有线程都中止了,进程本身和它所占用的资源将会被Windows清除。许多应用程序都被设计为单线程程序,这意味着该程序实现的进程从来不会有超过一个线程在执行,即使在系统中有多个同样的处理在进行。一般一个进程不会关心系统中其他进程的线程的执行。然而,在单个进程里的所有线程不仅共享虚拟地址空间,而且许多进程级的资源也被共享, 比如文件和窗口句柄等。由于进程资源共享的特征,一个线程必须考虑同一进程中其它线程正在做什么。线程同步是在多线程的进程中保持各线程互不冲突的一门艺术。这也使得多线程比较困难。最好的方式是只有在需要时才使用多线程,尽量保持事情简单。而且要避免线程同步的情况。在本栏目中,我将向你展示如何为一个普通的客户应用程序做这些事情。为什么使用多个线程?已经有许多单线程的客户端应用程序,而且每天还有许多正在被写。在许多情况下,单线程的行为已经足够了。然而,在某些特定的应用程序中加入一些异步行为可以提高你的经验。典型的数据库前端程序是一个很好的例子。数据库查询需要花费大量时间完成。在一个单线程的应用程序里,这些查询会导致window消息处理能力阻塞,导致程序的用户交互被冻结。解决办法就是,这个我将要详细描述,用一个线程处理来自操作系统的消息,而另外一个线程做一个很长的工作。在你的代码中使用第二个线程的重要原因就是即使在幕后有一个繁忙的工作在进行,也要保证你的程序的用户交互有响应。我们首先看一下执行一长串操作的单线程的GUI程序。然后我们将用额外的线程整理该程序。Figure 1 是用C#写的一个程序的完整源代码。它创建了一个带有文本框和按钮的窗体。如果你在文本框中键入了一个数字,然后按下按钮,这个程序将处理你输入的那个数字,它表示秒数,每秒钟响铃一次代表后台的处理。除了Figure 1 的代码外,你可以从本文开头的链接中下载完整的代码。下载或键入Figure 1 所示的代码,在读之前编译运行它,(编译前,在Visual Studio.NET中右击你的工程,加入Microsoft Visual Basic运行时引用)当你试着运行Figure 1 中的SingleThreadedForm.cs应用程序时,你马上就会看到几个问题。
Figure 1 SingleThreadedForm.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 SingleThreadedForm());
}
}
// A Form-derived type
class SingleThreadedForm : Form {
// Constructor method
public SingleThreadedForm() {
// 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
Count(count);
}
// Method beeps once per second
void Count(Int32 seconds) {
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();
}
在你第一次测试运行时,在文本框中输入20,按下按钮。你将看到程序的用户交互变得完全没有响应了。你不能单击按钮或者编辑文本框,程序也不能被从容的关闭,如果你覆盖该窗体接着会显示一个窗口的部分区域,它将不再重绘自己(见 Figure 2),这个程序被锁定足足20秒, 然而它还可以继续响铃,证明它还没有真正的死掉。这个简单的程序解释了单线程GUI程序的问题。

我将用多线程解决第一个问题:未响应的用户交互,但首先我将解释是什么导致了这种现象。