.net多线程基础OR终极
2012-03-01 18:12 河蟹社会 阅读(498) 评论(0) 收藏 举报先表明,向作者致敬http://www.cnblogs.com/leslies2/archive/2012/02/07/2310495.html 风尘浪子
前半部分是复制风尘浪子的,从 三 开始,互联网收集整理. 感谢互联网,感谢open source. 重要是,大家能够领悟,掌握和运用多线程的知识.
一、线程的定义
1. 1 进程、应用程序域与线程的关系
进程(Process)是Windows系统中的一个基本概念,它包含着一个运行程序所需要的资源。进程之间是相对独立的,一个进程无法访问另一个 进程的数据(除非利用分布式计算方式),一个进程运行的失败也不会影响其他进程的运行,Windows系统就是利用进程把工作划分为多个独立的区域的。进 程可以理解为一个程序的基本边界。
应用程序域(AppDomain)是一个程序运行的逻辑区域,它可以视为一个轻量级的进程,.NET的程序集正是在应用程序域中运行的,一个进程可 以包含有多个应用程序域,一个应用程序域也可以包含多个程序集。在一个应用程序域中包含了一个或多个上下文context,使用上下文CLR就能够把某些 特殊对象的状态放置在不同容器当中。
线程(Thread)是进程中的基本执行单元,在进程入口执行的第一个线程被视为这个进程的主线程。在.NET应用程序中,都是以Main()方法 作为入口的,当调用此方法时系统就会自动创建一个主线程。线程主要是由CPU寄存器、调用栈和线程本地存储器(Thread Local Storage,TLS)组成的。CPU寄存器主要记录当前所执行线程的状态,调用栈主要用于维护线程所调用到的内存与数据,TLS主要用于存放线程的状 态信息。
进程、应用程序域、线程的关系如下图,一个进程内可以包括多个应用程序域,也有包括多个线程,线程也可以穿梭于多个应用程序域当中。但在同一个时刻,线程只会处于一个应用程序域内。
1.2 多线程
在单CPU系统的一个单位时间(time slice)内,CPU只能运行单个线程,运行顺序取决于线程的优先级别。如果在单位时间内线程未能完成执行,系统就会把线程的状态信息保存到线程的本地 存储器(TLS) 中,以便下次执行时恢复执行。而多线程只是系统带来的一个假像,它在多个单位时间内进行多个线程的切换。因为切换频密而且单位时间非常短暂,所以多线程可 被视作同时运行。
适当使用多线程能提高系统的性能,比如:在系统请求大容量的数据时使用多线程,把数据输出工作交给异步线程,使主线程保持其稳定性去处理其他问题。但需要注意一点,因为CPU需要花费不少的时间在线程的切换上,所以过多地使用多线程反而会导致性能的下降。
二、线程的基础知识
2.1 System.Threading.Thread类
System.Threading.Thread是用于控制线程的基础类,通过Thread可以控制当前应用程序域中线程的创建、挂起、停止、销毁。
它包括以下常用公共属性:
| 属性名称 | 说明 |
|---|---|
| CurrentContext | 获取线程正在其中执行的当前上下文。 |
| CurrentThread | 获取当前正在运行的线程。 |
| ExecutionContext | 获取一个 ExecutionContext 对象,该对象包含有关当前线程的各种上下文的信息。 |
| IsAlive | 获取一个值,该值指示当前线程的执行状态。 |
| IsBackground | 获取或设置一个值,该值指示某个线程是否为后台线程。 |
| IsThreadPoolThread | 获取一个值,该值指示线程是否属于托管线程池。 |
| ManagedThreadId | 获取当前托管线程的唯一标识符。 |
| Name | 获取或设置线程的名称。 |
| Priority | 获取或设置一个值,该值指示线程的调度优先级。 |
| ThreadState | 获取一个值,该值包含当前线程的状态。 |
2.1.1 线程的标识符
ManagedThreadId是确认线程的唯一标识符,程序在大部分情况下都是通过Thread.ManagedThreadId来辨别线程的。 而Name是一个可变值,在默认时候,Name为一个空值 Null,开发人员可以通过程序设置线程的名称,但这只是一个辅助功能。
2.1.2 线程的优先级别
.NET为线程设置了Priority属性来定义线程执行的优先级别,里面包含5个选项,其中Normal是默认值。除非系统有特殊要求,否则不应该随便设置线程的优先级别。
| 成员名称 | 说明 |
|---|---|
| Lowest | 可以将 Thread 安排在具有任何其他优先级的线程之后。 |
| BelowNormal | 可以将 Thread 安排在具有 Normal 优先级的线程之后,在具有 Lowest 优先级的线程之前。 |
| Normal | 默认选择。可以将 Thread 安排在具有 AboveNormal 优先级的线程之后,在具有 BelowNormal 优先级的线程之前。 |
| AboveNormal | 可以将 Thread 安排在具有 Highest 优先级的线程之后,在具有 Normal 优先级的线程之前。 |
| Highest | 可以将 Thread 安排在具有任何其他优先级的线程之前。 |
2.1.3 线程的状态
通过ThreadState可以检测线程是处于Unstarted、Sleeping、Running 等等状态,它比 IsAlive 属性能提供更多的特定信息。
前面说过,一个应用程序域中可能包括多个上下文,而通过CurrentContext可以获取线程当前的上下文。
CurrentThread是最常用的一个属性,它是用于获取当前运行的线程。
2.1.4 System.Threading.Thread的方法
Thread 中包括了多个方法来控制线程的创建、挂起、停止、销毁,以后来的例子中会经常使用。
| 方法名称 | 说明 |
|---|---|
| Abort() | 终止本线程。 |
| GetDomain() | 返回当前线程正在其中运行的当前域。 |
| GetDomainId() | 返回当前线程正在其中运行的当前域Id。 |
| Interrupt() | 中断处于 WaitSleepJoin 线程状态的线程。 |
| Join() | 已重载。 阻塞调用线程,直到某个线程终止时为止。 |
| Resume() | 继续运行已挂起的线程。 |
| Start() | 执行本线程。 |
| Suspend() | 挂起当前线程,如果当前线程已属于挂起状态则此不起作用 |
| Sleep() | 把正在运行的线程挂起一段时间。 |
2.1.5 开发实例
以下这个例子,就是通过Thread显示当前线程信息
1 static void Main(string[] args)
2 {
3 Thread thread = Thread.CurrentThread;
4 thread.Name = "Main Thread";
5 string threadMessage = string.Format("Thread ID:{0}\n Current AppDomainId:{1}\n "+
6 "Current ContextId:{2}\n Thread Name:{3}\n "+
7 "Thread State:{4}\n Thread Priority:{5}\n",
8 thread.ManagedThreadId, Thread.GetDomainID(), Thread.CurrentContext.ContextID,
9 thread.Name, thread.ThreadState, thread.Priority);
10 Console.WriteLine(threadMessage);
11 Console.ReadKey();
12 }
三、以ThreadStart方式实现多线程
3.1使用ThreadStart
class Program
{
static void Main(string[] args)
{
//Thread th = new Thread(Sleep);
Thread th = new Thread(new ThreadStart(Sleep));
th.Start();
for (int i = 0; i < 10; i++)
{
Console.WriteLine("这里是主线程在工作" + i);
}
}
//模拟执行长时间的任务
static void Sleep()
{
ThreadMessage("Sleep");
Console.WriteLine("我还没有执行完呢,请耐心等待....");
Thread.Sleep(3000);
}
static void ThreadMessage(string data)
{
string message = string.Format("ThreadName is {0} ThreadId is:{1}",
data, Thread.CurrentThread.ManagedThreadId);
Console.WriteLine(message);
}
}
运行后你会发现,Sleep()方法 还没有执行完(睡眠3分钟模拟长时间任务),主线程已经执行完成. 这就是多线程的好处.
3.2 ParameterizedThreadStart 带参数的
1 class Program
2 {
3 static void Main(string[] args)
4 {
5 //Thread th = new Thread(Sleep);
6 Thread th = new Thread(new ParameterizedThreadStart(Sleep));
7 th.Start("Sleep");
8 for (int i = 0; i < 10; i++)
9 {
10 Console.WriteLine("这里是主线程在工作" + i);
11 }
12
13 }
14
15 //模拟执行长时间的任务
16 static void Sleep(object state)
17 {
18 string name = (string)state;
19 ThreadMessage(name);
20 Console.WriteLine("我还没有执行完呢,请耐心等待....");
21 Thread.Sleep(3000);
22 }
23
24 static void ThreadMessage(string data)
25 {
26 string message = string.Format("ThreadName is {0} ThreadId is:{1}",
27 data, Thread.CurrentThread.ManagedThreadId);
28 Console.WriteLine(message);
29 }
30 }
运行结果和上面那差不多一样.
3.3 匿名函数(委托)在多线程的运用
想必大家发现了这2个例子中都有一句注释的Thread th = new Thread(Sleep),这是为什么呢? 你可以把注释打开,下面的th注释掉,运行下,你会发现,两次都一样,这又是为什么呢?因为在实例化th的时候,有4个重载方法,其中2个就是ThreadStart和ParameterizedThreadStart

我们都知道ThreadStart和ParameterizedThreadStart和2个委托,而委托最大的作用就是传递一个方法, 在c#2.0就引入了匿名方法(3.0以及更高,lambda表达式取代了匿名方法)我们来看看匿名方法在这里能给我们带来点什么惊喜(方便)
1 class Program
2 {
3 static void Main(string[] args)
4 {
5 //Thread th = new Thread(delegate() { //在new ThreadStart()中
6 // ShowMessage("ShowMessage");
7 //});
8 //Thread th = new Thread(new ThreadStart(delegate() {
9 // ShowMessage("ShowMessage");
10 //}));
11 //Thread th = new Thread(new ThreadStart(() =>
12 //{
13 // ShowMessage("ShowMessage");
14 //}));
15
16 Thread th = new Thread(delegate(object o) //new ParameterizedThreadStart()中的
17 {
18 ShowMessage("ShowMessage");
19 });
20 //Thread th = new Thread(new ParameterizedThreadStart(delegate(object o) {
21 // ShowMessage("ShowMessage");
22 //}));
23 //Thread th = new Thread(t => ShowMessage("show"));
24 th.Start();
25 for (int i = 0; i < 10; i++)
26 {
27 Console.WriteLine("这里是主线程在工作" + i);
28 }
29 }
30
31 //模拟执行长时间的任务
32 static void Sleep(object state)
33 {
34 string name = (string)state;
35 ThreadMessage(name);
36 Console.WriteLine("我还没有执行完呢,请耐心等待....");
37 Thread.Sleep(3000);
38 }
39
40 //这里的参数不为object,你可以定义任何类型,任何参数
41 static void ShowMessage(string message)
42 {
43 ThreadMessage(message);
44 Console.WriteLine("我还没有执行完呢,请耐心等待....");
45 Thread.Sleep(3000);
46 }
47
48 static void ThreadMessage(string data)
49 {
50 string message = string.Format("ThreadName is {0} ThreadId is:{1}",
51 data, Thread.CurrentThread.ManagedThreadId);
52 Console.WriteLine(message);
53 }
54 }
上面的几种方式都可以用. 我为什么要用个ShowMessage(string msg) 方法来测试呢 ? 细心你会发现ParameterizedThreadStart委托他定义的参数为object,用匿名函数就可以解决这问题.如果你对委托,匿名函数不太熟悉的话,你就要补习一下关于委托的知识了.
3.4 前台线程and后台线程
注意以上两个例子都没有使用Console.ReadKey(),但系统依然会等待异步线程完成后才会结束。这是因为使用Thread.Start()启动的线程默认为前台线程,而系统必须等待所有前台线程运行结束后,应用程序域才会自动卸载。
在第二节曾经介绍过线程Thread有一个属性IsBackground,通过把此属性设置为true,就可以把线程设置为后台线程!这时应用程序域将在主线程完成时就被卸载(主线程关闭),而不会等待异步线程的运行。
3.5 线程的一些方法
Thread.Sleep()大家都很熟悉了,休眠多长时间,里面是毫秒.1秒=1000毫秒. Join() 表面意思是把线程加入,也就是这线程完事后主线程才被卸载.
你可以把子线程设置成后台线程,然后调用这个方法,就不会发现'一闪而过'的现象了. Thread.Suspend()与 Thread.Resume()是在Framework1.0 就已经存在的老方法了,它们分别可以挂起、恢复线程。但在Framework2.0中就已经明确排斥这两个方法。这是因为一旦某个线程占用了已有的资源,再使用Suspend()使线程长期处于挂起状态,当在其他线程调用这些资源的时候就会引起死锁!所以在没有必要的情况下应该避免使用这两个方法。
若想终止正在运行的线程,可以使用Abort()方法。在使用Abort()的时候,将引发一个特殊异常 ThreadAbortException 。
若想在线程终止前恢复线程的执行,可以在捕获异常后 ,在catch(ThreadAbortException ex){...} 中调用Thread.ResetAbort()取消终止。
下面的例子是 终止线程和取消终止的例子(拷贝风尘浪子的)
1 class Program
2 {
3 static void Main(string[] args)
4 {
5 Console.WriteLine("Main threadId is:" +
6 Thread.CurrentThread.ManagedThreadId);
7
8 Thread thread = new Thread(new ThreadStart(AsyncThread));
9 thread.Start();
10 Console.ReadKey();
11 }
12
13 //以异步方式调用
14 static void AsyncThread()
15 {
16 try
17 {
18 string message = string.Format("\nAsync threadId is:{0}",
19 Thread.CurrentThread.ManagedThreadId);
20 Console.WriteLine(message);
21
22 for (int n = 0; n < 10; n++)
23 {
24 //当n等于4时,终止线程
25 if (n >= 4)
26 {
27 Thread.CurrentThread.Abort(n);
28 }
29 Thread.Sleep(300);
30 Console.WriteLine("The number is:" + n.ToString());
31 }
32 }
33 catch (ThreadAbortException ex)
34 {
35 //输出终止线程时n的值
36 if (ex.ExceptionState != null)
37 Console.WriteLine(string.Format("Thread abort when the number is: {0}!",
38 ex.ExceptionState.ToString()));
39
40 //取消终止,继续执行线程
41 Thread.ResetAbort();
42 Console.WriteLine("Thread ResetAbort!");
43 }
44
45 //线程结束
46 Console.WriteLine("Thread Close!");
47 }
48 }
到此,我们学会了运用多线程,可不能沾沾自喜,这可只是刚刚开始. 前面说了通过ThreadStart创建的线程比较难管理,创建过多性能也会下降.主要是因为ThreadStart创建的线程不能循环利用,比如我们For循环个list,每一个model都开启个线程去执行任务,当前面的线程执行完了,也就销毁了,后面的还是要重新创建,不停的创建线程是很耗时的.由此可见 .NET为线程管理专门设置了一个CLR线程池.
四、CLR线程池的工作者线程
4.1 关于CLR线程池
使用ThreadStart与ParameterizedThreadStart建立新线程非常简单,但通过此方法建立的线程难于管理,若建立过多的线程反而会影响系统的性能。
有 见及此,.NET引入CLR线程池这个概念。CLR线程池并不会在CLR初始化的时候立刻建立线程,而是在应用程序要创建线程来执行任务时,线程池才初始 化一个线程。线程的初始化与其他的线程一样。在完成任务以后,该线程不会自行销毁,而是以挂起的状态返回到线程池。直到应用程序再次向线程池发出请求时, 线程池里挂起的线程就会再度激活执行任务。这样既节省了建立线程所造成的性能损耗,也可以让多个任务反复重用同一线程,从而在应用程序生存期内节约大量开销.
4.2 工作者线程与I/O线程
CLR线程池分为工作者线程(workerThreads)与I/O线程 (completionPortThreads) 两种,工作者线程是主要用作管理CLR内部对象的运作,I/O(Input/Output) 线程顾名思义是用于与外部系统交换信息,IO线程的细节将在下一节详细说明。
通过ThreadPool.GetMax(out int workerThreads,out int completionPortThreads )和 ThreadPool.SetMax( int workerThreads, int completionPortThreads)两个方法可以分别读取和设置CLR线程池中工作者线程与I/O线程的最大线程数。在 Framework2.0中最大线程默认为25*CPU数,在Framewok3.0、4.0中最大线程数默认为250*CPU数,在近年 I3,I5,I7 CPU出现后,线程池的最大值一般默认为1000、2000。
若想测试线程池中有多少的线程正在投入使用,可以通过ThreadPool.GetAvailableThreads( out int workerThreads,out int completionPortThreads ) 方法。
使用CLR线程池的工作者线程一般有两种方式,一是直接通过 ThreadPool.QueueUserWorkItem() 方法,二是通过委托(异步操作是加入在线程池中的),下面将逐一细说。
4.3 通过QueueUserWorkItem启动工作者线程
ThreadPool线程池中包含有两个静态方法可以直接启动工作者线程:
一为 ThreadPool.QueueUserWorkItem(WaitCallback)
二为 ThreadPool.QueueUserWorkItem(WaitCallback,Object)
先把WaitCallback委托指向一个带有Object参数的无返回值方法,再使用 ThreadPool.QueueUserWorkItem(WaitCallback) 就可以异步启动此方法,此时异步方法的参数被视为null 。
1 class Program
2 {
3 static void Main(string[] args)
4 {
5 ThreadPool.QueueUserWorkItem(new WaitCallback(Sleep));
7 for (int i = 0; i < 10; i++)
8 {
9 Console.WriteLine("这里是主线程在工作" + i);
10 }
11 Console.ReadKey();
12 }
13
14 static void Sleep(object state)
15 {
17 ThreadMessage("sleep");
18 Console.WriteLine("我还没有执行完呢,请耐心等待....");
19 Thread.Sleep(3000);
20 }
21
28
29 static void ThreadMessage(string data)
30 {
31 string message = string.Format("ThreadName is {0} ThreadId is:{1}",
32 data, Thread.CurrentThread.ManagedThreadId);
33 Console.WriteLine(message);
34 }
35 }
这是个不带参数的, ThreadPool.QueueUserWorkItem()有2个参数,第二个是个object类型
1 class Program
2 {
3 static void Main(string[] args)
4 {
5 Person person = new Person()
6 {
7 Name = "Somnus",
8 Age = 10
9 };
10 ThreadPool.QueueUserWorkItem(new WaitCallback(ShowMessage),person);
11 for (int i = 0; i < 10; i++)
12 {
13 Console.WriteLine("这里是主线程在工作" + i);
14 }
15 Console.ReadKey();
16 }
17
18 static void ShowMessage(object state)
19 {
20 Person person = (Person)state;
21 Console.WriteLine("学生姓名是{0},年龄为{1}",person.Name,person.Age);
22 Thread.Sleep(3000);
23 }
24 }
25
26 class Person
27 {
28 public string Name { get; set; }
29 public int Age { get; set; }
30 //public WaitHandle Wait { get; set; }
31 }
我们创建了一个Person类,在加入线程池的时候,我们传了一个person , 在ShowMessage()方法中,得到传过来的person,并显示人的信息.
那在这里,我们可以效仿3.3匿名函数在线程池中的应用呢? 答案是可以的.
4.4 线程池中的异常处理
多线程执行任务的时候,有时候总是要出错嘛, 重要的是,我们能catch的住异常.
1 class Program
2 {
3 static void Main(string[] args)
4 {
5 Person person = new Person()
6 {
7 Name = "Somnus",
8 Age = 10
9 };
10 try
11 {
12 ThreadPool.QueueUserWorkItem(new WaitCallback(ShowMessage), person);
13 }
14 catch (Exception ex)
15 {
16 //写日志
17 }
18 for (int i = 0; i < 10; i++)
19 {
20 Console.WriteLine("这里是主线程在工作" + i);
21 }
22 Console.ReadKey();
23 }
24
25 static void ShowMessage(object state)
26 {
27 Person person = (Person)state;
28 throw new Exception("这里出错了,快catch住我");
29 Console.WriteLine("学生姓名是{0},年龄为{1}", person.Name, person.Age);
30 Thread.Sleep(3000);
31 }
32 }
33
34 class Person
35 {
36 public string Name { get; set; }
37 public int Age { get; set; }
38 //public WaitHandle Wait { get; set; }
39 }
这样? No~ 结果会差强人意的,我们并没有catch住他. 那该在哪try呢. 对, 在委托调用那方法中. 我们这样干,
1 class Program
2 {
3 static void Main(string[] args)
4 {
5 Person person = new Person()
6 {
7 Name = "Somnus",
8 Age = 10
9 };
10
11 ThreadPool.QueueUserWorkItem(new WaitCallback(ShowMessage), person);
12
13 for (int i = 0; i < 10; i++)
14 {
15 Console.WriteLine("这里是主线程在工作" + i);
16 }
17 Console.ReadKey();
18 }
19
20 static void ShowMessage(object state)
21 {
22 try
23 {
24 Person person = (Person)state;
25 throw new Exception("这里出错了,快catch住我");
26 Console.WriteLine("学生姓名是{0},年龄为{1}", person.Name, person.Age);
27 Thread.Sleep(3000);
28 }
29 catch (Exception ex)
30 {
31 //写日志
32 }
33 }
34 }
35
36 class Person
37 {
38 public string Name { get; set; }
39 public int Age { get; set; }
40 //public WaitHandle Wait { get; set; }
41 }
这样,程序就不会报错了.
如果有好几个任务方法, 那我们要写好几个try{}catch{}这样肯定不是我们想要的,看看下面的封装
1 class Program
2 {
3 static void Main(string[] args)
4 {
5 Person person = new Person()
6 {
7 Name = "Somnus",
8 Age = 10
9 };
10 ThreadExecutor.Execute(new WaitCallback(ShowMessage),person);
11 for (int i = 0; i < 10; i++)
12 {
13 Console.WriteLine("这里是主线程在工作" + i);
14 }
15 Console.ReadKey();
16 }
17
18 static void ShowMessage(object state)
19 {
20 Person person = (Person)state;
21 throw new Exception("这里出错了,快catch住我");
22 Console.WriteLine("学生姓名是{0},年龄为{1}", person.Name, person.Age);
23 Thread.Sleep(3000);
24 }
25 }
26
27 class Person
28 {
29 public string Name { get; set; }
30 public int Age { get; set; }
31 //public WaitHandle Wait { get; set; }
32 }
33
34 public class ThreadExecutor
35 {
36
37 public static bool Execute(System.Threading.WaitCallback callback, object state)
38 {
39 try
40 {
41 return System.Threading.ThreadPool.QueueUserWorkItem((data) =>
42 {
43 try
44 {
45 callback(data);
46 }
47 catch (Exception ex)
48 {
49 //写日志
50 }
51 }, state);
52 }
53 catch (Exception e)
54 {
55 //写日志
56 }
57 return false;
58 }
59 public static bool Execute(System.Threading.WaitCallback callback)
60 {
61 try
62 {
63 return System.Threading.ThreadPool.QueueUserWorkItem((data) =>
64 {
65 try
66 {
67 callback(data);
68 }
69 catch (Exception ex)
70 {
71 //写日志
72 }
73 });
74 }
75 catch (Exception e)
76 {
77 //写日志
78 }
79 return false;
80 }
81 }
其中Execute()方法就是把ThreadPool.QueueUserWorkItem用匿名函数的方法进行了封装,catch住了异常.
我们在前面说过了,线程池中的线程,默认是后台线程,前面几个例子,最后都有句Console.ReadKey(),就是如果不手动关闭主程序,主程序是不会自动关的.
这肯定不是我们想要的,那我们怎么判断所有子线程运行完毕后,关闭主线程呢
4.5 WaitHandle.WaitAll() 等待所有子线程完成后,关闭主线程 多个线程之间的协调工作
1 class Program
2 {
3 static void Main(string[] args)
4 {
5
6 WaitHandle[] waits = new WaitHandle[2]
7 {
8 new AutoResetEvent(false),
9 new AutoResetEvent(false)
10 };
11 Person person = new Person()
12 {
13 Name = "Somnus",
14 Age = 10,
15 Wait=waits[0]
16 };
17 Person person2 = new Person()
18 {
19 Name = "cnblogs",
20 Age = 20,
21 Wait = waits[1]
22 };
23 ThreadExecutor.Execute(new WaitCallback(ShowMessage),person);
24 ThreadExecutor.Execute(new WaitCallback(ShowMessage), person2);
25 for (int i = 0; i < 10; i++)
26 {
27 Console.WriteLine("这里是主线程在工作" + i);
28 }
29 WaitHandle.WaitAll(waits);
30 //WaitHandle.WaitAny(waits);
31 }
32
33 static void ShowMessage(object state)
34 {
35 Person person = (Person)state;
36 AutoResetEvent are = (AutoResetEvent)person.Wait;
37 Console.WriteLine("学生姓名是{0},年龄为{1}", person.Name, person.Age);
38 Thread.Sleep(3000);
39 are.Set();
40 }
41 }
42
43 class Person
44 {
45 public string Name { get; set; }
46 public int Age { get; set; }
47 public WaitHandle Wait { get; set; }
48 }
49
50 public class ThreadExecutor
51 {
52
53 public static bool Execute(System.Threading.WaitCallback callback, object state)
54 {
55 try
56 {
57 return System.Threading.ThreadPool.QueueUserWorkItem((data) =>
58 {
59 try
60 {
61 callback(data);
62 }
63 catch (Exception ex)
64 {
65 //写日志
66 }
67 }, state);
68 }
69 catch (Exception e)
70 {
71 //写日志
72 }
73 return false;
74 }
75 public static bool Execute(System.Threading.WaitCallback callback)
76 {
77 try
78 {
79 return System.Threading.ThreadPool.QueueUserWorkItem((data) =>
80 {
81 try
82 {
83 callback(data);
84 }
85 catch (Exception ex)
86 {
87 //写日志
88 }
89 });
90 }
91 catch (Exception e)
92 {
93 //写日志
94 }
95 return false;
96 }
97 }
WaitHandle.WaitAll(),最大可监测64个WaitHandler ,如果你需要的多线程比较多,你可以分批,中间Sleep()一段时间,就可以了.
WaitHandle.WaitAny(),其中某一个线程完成后,就退出主线程.
4.6 委托类
使用CLR线程池中的工作者线程,最灵活最常用的方式就是使用委托的异步方法.委托包括下面3个重要方法:Invoke(),BeginInvoke(),EndInvoke()
当调用Invoke()方法时,对应此委托的所有方法都会被执行。而BeginInvoke与EndInvoke则支持委托方法的异步调用,由BeginInvoke启动的线程都属于CLR线程池中的工作者线程。
1 class Program
2 {
3 static void Main(string[] args)
4 {
5 SleepDelegate sleepDelegate = new SleepDelegate(Sleep);
6 IAsyncResult result= sleepDelegate.BeginInvoke("Sleep",null,null);
7 string data = sleepDelegate.EndInvoke(result);
8 Console.WriteLine(data);
9 Console.ReadKey();
10 }
11
12 delegate string SleepDelegate(object o);
13
14 //模拟执行长时间的任务
15 static string Sleep(object state)
16 {
17 string name = (string)state;
18 ThreadMessage(name);
19 Console.WriteLine("我还没有执行完呢,请耐心等待....");
20 Thread.Sleep(3000);
21 return "Hello" + name;
22 }
23
24 static void ThreadMessage(string data)
25 {
26 string message = string.Format("ThreadName is {0} ThreadId is:{1}",
27 data, Thread.CurrentThread.ManagedThreadId);
28 Console.WriteLine(message);
29 }
30 }
委托还有个可以调用回调函数的
1 class Program
2 {
3 static void Main(string[] args)
4 {
5 SleepDelegate sleepDelegate = new SleepDelegate(Sleep);
6 IAsyncResult result= sleepDelegate.BeginInvoke("Sleep",new AsyncCallback(Complete),"这里可以传个object值啊");
7 Console.ReadKey();
8 }
9
10 delegate string SleepDelegate(object o);
11
12 //模拟执行长时间的任务
13 static string Sleep(object state)
14 {
15 string name = (string)state;
16 ThreadMessage(name);
17 Console.WriteLine("我还没有执行完呢,请耐心等待....");
18 Thread.Sleep(3000);
19 return "Hello" + name;
20 }
21
22 static void Complete(IAsyncResult iresult)
23 {
24 AsyncResult result = (AsyncResult)iresult;
25 SleepDelegate sleepDelegate = (SleepDelegate)result.AsyncDelegate;
26 string obj = (string)result.AsyncState;
27 string data = sleepDelegate.EndInvoke(result);
28 Console.WriteLine(obj + data);
29 }
30
31 static void ThreadMessage(string data)
32 {
33 string message = string.Format("ThreadName is {0} ThreadId is:{1}",
34 data, Thread.CurrentThread.ManagedThreadId);
35 Console.WriteLine(message);
36 }
37 }
ok,就写到这吧,委托异步处理的异常处理和等待所有线程完成,都可以参照4.4和4.5.
同时,在delegate.EndInvoke() 处也可以catch住异常.(这个在执行exe时候可看到效果,直接调试程序要报错)
这篇文章,是自己对多线程学习和总结吧. 对于多线程中的,线程安全,不是太了解,可能是下一步要探究的对象吧.
浙公网安备 33010602011771号