线程、任务和同步
使用线程有几个原因。假设从应用程序中进行网络调用需要一定的时间。用户不希望分割用户界,并且让用户一直等待直到从服务器返回一个响应为止。用户可以同时执行其他一些操作,或者甚至取消发送给服务器的请求。这些都可以使用线程来实现。
对于所有需要等待的操作,例如,因为文件、数据库或网络访问都需要一定的时间,此时就可以启动一个新线程,同时完成其他任务。即使是处理密集型的任务,线程也是有帮助的。一个进程的多个线程可以同时运行在不同的CPU上,或多核CPU的不同内核上。
还必须注意运行多个线程时的一些问题。它们可以同时运行,但如果线程访问相同的数据,就很容易出问题。必须实现同步机制。
线程概述
线程是程序中独立的指令流。使用C#编写任务程序时,都有一个入口点:Main()方法。程序从Main()方法的第一条语句开始执行,直到这个方法返回为止。
这种程序结构非常适合于其中有一个可识别的任务序列的程序,但程序常常需要同时完成多个任务。线程对客户端和服务器端应用。在Visual Studio编辑器中输入C#代码时,系统会分析代码,用下划线标出遗漏的分号或其他语法错误,这用一个后台线程完成。Microsoft Word的拼写检查器也会做相同的事。一个线程等待用户输入,而另一个线程进行后台搜索。第3个线程将写入的数据存储在临时文件中,第4个线程从Internet上下载其他数据。
运行在服务器上的应用程序中,等待客户请求的线程,称为侦听器线程。只要接收到请求,就把它传递给另一个工作线程,之后继续与客户通信。侦听器线程会立即返回,接收下一个客户发送的下一个请求。
进程包含资源,如Window句柄、文件系统句柄或其他内核对象。每个进程都分配了虚拟内存。一个进程至少包含一个线程。操作系统会调度线程。线程有一个优先级、实际上正在处理的程序的位置计数器、一个存储其局部变量的栈。每个线程都有自己的栈,但程序代码的内存和堆由一个进程的所有线程共享。这使一个进程的所有线程之间的通信非常快——该进程的所有线程都寻址相同的虚拟内存。但是这也使处理比较困难,因为多个线程可以修改同一个内存位置。
进程管理的资源包括虚拟内存和Window句柄,其中至少包含一个线程。线程时运行程序所必需的。
异步委托
创建线程的一种简单方式是定义一个委托,并异步调用它。委托是方法的类型安全的引用。Delegate类还支持异步地调用方法。在后台,Delegate类会创建一个执行任务的线程。
委托使用线程池来完成异步任务。
为了说明委托的异步特性,从一个需要一定的时间才能执行完毕的方法开始。TakesAWhile()方法至少需要经过第2个变量传递的毫秒数才能执行完,因为它调用Thread.Sleep()方法。
static int TakeAWhile(int data, int ms)
{
Console.WriteLine("TakeAWhile started");
Thread.Sleep(ms);
Console.WriteLine("TakeAWhile completed");
return ++data;
}
要从委托中调用这个方法,必须定义一个有相同参数和返回类型的委托,如下面的TakeAWhileDelegate()方法所示:
public delegate int TakesAWhileDelegate(int data, int ms);
现在可以使用不同的技术异步地调用委托,并返回结果。