浅谈C#中的多线程编程[1]
时间:2011-03-09 来源:Ellic
记录,做到温故而知新。
一、相关概念:
什么是进程?
当一个程序开始运行时,它就是一个进程,进程包括运行中的程序和程序所使用到的内存和系统资源。而一个进程又是由多个线程所组成的。
什么是线程?
线程是程序中的一个执行流,每个线程都有自己的专有寄存器(栈指针、程序计数器等),但代码区是共享的,即不同的线程可以执行同样的函数。
什么是多线程?
多线程是指程序中包含多个执行流,即在一个程序中可以同时运行多个不同的线程来执行不同的任务,也就是说允许单个程序创建多个并行执行的线程来完成各自的任务。
多线程的好处:
可以提高CPU的利用率。在多线程程序中,一个线程必须等待的时候,CPU可以运行其它的线程而不是等待,这样就大大提高了程序的效率。
多线程编程有好处,也有不好的地方,如:
线程也是对象,需要占用内存,线程越多占用内存越多;
多线程需要协调和管理,所以需要CPU时间跟踪线程;
线程之间对共享资源的访问会相互影响,必须解决竞用共享资源的问题;
线程太多会导致控制太复杂,最终可能带来产生Bug的风险。
二、Thread类
先来理解下Thread类,这是实现多线程编程的基础。Thread类位于System.Threading命名空间,其构造函数为:
public Thread(ParameterrizedThreadStart start)
public Thread(ThreadStart start)
public Thread(ParameterizedThreadStart start, int maxStackSize)
public Thread(ThreadStart start, int maxStackSize)
这些构造函数涉及3种类型的参数:
1)ParameterizedThreadStart:是一个委托类型,表示此线程开始执行时要调用的方法,支持向调用的方法传递一个参数。
2)ThreadStart:是一个委托类型,表示此线程开始执行时要调用的方法没有参数的委托类型,不支持向调用的方法传递一个参数。
3)maxStackSize:表示线程要使用的最大堆栈大小,如果为0刚使用可执行文件的文件头中指定的默认最大堆栈大小。
Thread类有几个至关重要的方法,描述如下:
Start():启动线程;
Sleep(int):静态方法,暂停当前线程指定的毫秒数;
Abort():通常使用该方法来终止一个线程;
Suspend():该方法并不终止未完成的线程,它仅仅挂起线程,以后还可恢复;
Resume():恢复被Suspend()方法挂起的线程的执行。
Thread类基本的使用代码如下:
using System.Threading;
namespace ProgrammingCSharp4
{
public class Program
{
public static void Main(string[] args)
{
Console.WriteLine("[主线程id:{0}]",Thread.CurrentThread.ManagedThreadId);
Thread thread1 = new Thread(Print);
thread1.Start();
Thread thread2 = new Thread(PrintEx);
thread2.Start("测试");
Console.ReadKey(true);
}
public static void Print()
{
int threadId = Thread.CurrentThread.ManagedThreadId;
Console.WriteLine("[当前线程id:{0}]{1}",threadId,DateTime.Now.ToShortTimeString());
}
public static void PrintEx(object obj)
{
int threadId = Thread.CurrentThread.ManagedThreadId;
Console.WriteLine("[当前线程id:{0}]{1}",threadId,obj);
}
}
}
三、线程池的使用
C#中,我们可以通过一种叫做“池(Pool)”的技术来优化对象的使用,其原理是预先创建一定数量的对象放在“池”里,在对象被请求的时候,某个对象被从“池”中取出供使用,使用完毕再重新放回“池”中,等待下一次的请求。我们可以自己实现这样的线程池,也可以使用.NET Framework为我们所提供的线程池,它叫做TreadPool,位于System.Threading命名空间。
需要注意的是线程池中的线程均为后台线程,即它们的IsBackground属性为True。也就是说在所有的前台线程都已经退出后,ThreadPool中的线程不会让应用程序继续保持运行。
所以当我们执行一些简单的、耗时短暂的任务时,才应该使用到线程池,在下面的情况下则不应该使用线程池技术:
Ø 当需要创建一个前台线程时;
Ø 线程池中的线程都是默认优先级的,因此当需要创建具有特定优先级的线程时就不应该使用线程池;
Ø 当需要某个任务只和特定的线程关联时,因为无法选择执行任务时使用的是哪一个具体的线程;
Ø 当需要中止特定的线程时不应使用线程池,它不提供这个功能;
Ø 线程执行时间比较长时;
要使用ThreadPool中的线程,需要使用TheadPool.QueueUserWorkItem这个静态方法指定线程要调用的方法,该方法有两个重载版本:
public static bool QueueUserWorkItem(WaitCallBack callBack)
public static bool QueueUserWorkItem(WaitCallBack callBack,object state)
WaitCallBack是一个委托类型,下面是线程池使用的例子:
using System.Threading;
namespace AsynchronousSample
{
class Program
{
public static void Main(string[] args)
{
ThreadPool.QueueUserWorkItem(Counter);
ThreadPool.QueueUserWorkItem(Counter,"AsynchronousSample Test");
Console.WriteLine("[Thread ID = {0}]The main thread is started.",Thread.CurrentThread.ManagedThreadId);
Thread.Sleep(500);
Console.WriteLine("The main thread is closed.");
Console.ReadKey(true);
}
private static void Counter(object state)
{
Console.WriteLine("Counting begins, from 1 to 100:");
Console.WriteLine("the Parameter is:{0}",state);
for(int counter = 0 ; counter <100;counter++)
{
if(null==state)
{
Console.WriteLine("[Thread ID = {0}] {1}",Thread.CurrentThread.ManagedThreadId,counter);
}else
{
Console.WriteLine("[Thread ID = {0}] {1}",Thread.CurrentThread.ManagedThreadId,state);
}
Thread.Sleep(100);
}
}
}
}
未完待续,下次接着复习Thread的相关属性与用法。