阳信一实招生信息:多线程

来源:百度文库 编辑:九乡新闻网 时间:2024/04/29 02:31:36
多线程
【回目录】
多线程是程序员面试时常常会面对的问题,对多线程概念的掌握和理解水平,也会被一些老鸟用来衡量一个人的编程实力的重要参考指标。不论是实际工作需要还是为了应付面试,掌握多线程都是程序员职业生涯中一个必须经过的环节。其实当你把“多线程”和你的“职业生涯”联系在一起考虑的时候,就会觉得“多线程”是多么的渺小,对,没有跨越不过的山。不过就算它很渺小,但也有可能改变你的人生轨迹。不用担心,如果你对多线程还不太熟悉,那么我们就一起来看看什么是多线程吧。
跟前几篇的风格一样,我会在开篇的时候举一个现实生活中的例子,通过这个例子来映射一些晦涩枯燥的计算机编程专业知识,在让读者朋友很好地理解理论概念的同时,又避免了阅读教科书时的枯燥感觉。这次我要举的例子是公司。不一定是IT公司,尽量和编程领域远一点儿吧,那就假设是一家搬家公司吧。
假如我们把公司看做是一个进程,那么人就是其中的线程。进程必须得有一个主线程,公司在创业初期往往可能出现一人打天下的现象,但是,至少得有一个人,公司才能运作。公司创业初期,业务还不算太多,往往就是老板一个人身兼数职,一天如果只有1、2趟活儿,应该还是忙得过来的。时间长了,随着业务的发展、口碑的建立,生意越来越兴隆,一个人肯定就忙不过来了。假设一天有5个活儿,老板一个人必须搬完A家才能搬B家,搬到黄昏估计也就搬到C家,D和E家都还在焦急地等待着呢。老板一个人要充当搬运工、司机、业务联系人、法人代表、出纳等众多角色,累死累活公司的规模也上不去,人手不够制约了公司的发展。那么怎么办,很简单,增加人手,用编程的话来说就是“再起个线程”。
我们现在就用代码来描述这样的场景吧,首先,我们准备成立一家搬家公司,于是要准备好将来和客户签的合同书:
1: public class Contract 2: { 3: public string ID { get; private set; } 4: public string From { get; set; } 5: public string To { get; set; } 6: public decimal Fee { get; set; } 7:  8: public Contract() 9: { 10: this.ID = Guid.NewGuid().ToString() 11: } 12: }
简是简单了点儿,好歹也是份合同,现在我们就去申请注册一家公司,并组建好初创团队,哪怕目前还只有老板一个人:
1: public class HouseMovingCompany 2: { 3: private static HouseMovingCompany _instance = null; 4: public static HouseMovingCompany Instance 5: { 6: get { return (_instance == null _instance = new HouseMovingCompany() : _instance); } 7: } 8:  9: public List Contracts { get; private set; } 10:  11: public HouseMovingCompany() 12: { 13: this.Contracts = new List(); 14: } 15:  16: public void MoveHouse() 17: { 18: if (this.Contracts == null || this.Contracts.Count == 0) 19: { 20: return; 21: } 22:  23: Contract contract = contract = this.Contracts[0]; 24: this.Contracts.RemoveAt(0); 25:  26: if (!String.IsNullOrEmpty(contract.From) && !String.IsNullOrEmpty(contract.To)) 27: { 28: Console.WriteLine("Move the house from {0} to {1}.", contract.From, contract.To); 29: } 30:  31: Thread.Sleep(5000); 32: } 33: }
好了,现在公司实体有了,老板就可以开始忙活了:
1: static void Main(string[] args) 2: { 3: HouseMovingCompany.Instance.Contracts.Add(new Contract { From = "WuDaokou", To = "LinDa Road", Fee = 500 }); 4:  5: while (HouseMovingCompany.Instance.Contracts.Count > 0) 6: { 7: HouseMovingCompany.Instance.MoveHouse(); 8: } 9: }
我们在前面设置了每次搬家耗时5秒钟,咱们把它想象成5个小时。嗯,一天接一个单子,还可以接受,但是随着老板生意日渐兴隆,有时候一天要接3个单子,这就最少要工作15个小时了,还要操心公司的运营等问题,的确忙不过来了,而且照这样算,老板一天不可能完成5个或5个以上的单子,严重制约了公司的发展:
1: static void Main(string[] args) 2: { 3: HouseMovingCompany.Instance.Contracts.Add(new Contract { From = "WuDaokou", To = "LinDa Road", Fee = 500 }); 4: HouseMovingCompany.Instance.Contracts.Add(new Contract { From = "XiDan", To = "WangFujing", Fee = 1000 }); 5: HouseMovingCompany.Instance.Contracts.Add(new Contract { From = "XiangShan", To = "The Forbidden City", Fee = 10000 }); 6:  7: while (HouseMovingCompany.Instance.Contracts.Count > 0) 8: { 9: HouseMovingCompany.Instance.MoveHouse(); 10: } 11: }
一天夜里,老板拖着疲倦的身子回到家里,一进门就一头倒在床上,他极力睁着快睁不开的眼睛,努力地对自己说:“不行,我一定要想个办法,不然我会被累死的!”。
其实办法很简单,谁都知道,招聘几个员工,再买几辆车,大家分头行动,不仅分担了工作负担,而且在规模扩大的同时还可以完成更多更大的单子。好,我们现在就借助多线程机制来实现我们的想法:
1: static void Main(string[] args) 2: { 3: HouseMovingCompany.Instance.Contracts.Add(new Contract { From = "WuDaokou", To = "LinDa Road", Fee = 500 }); 4: HouseMovingCompany.Instance.Contracts.Add(new Contract { From = "XiDan", To = "WangFujing", Fee = 1000 }); 5: HouseMovingCompany.Instance.Contracts.Add(new Contract { From = "XiangShan", To = "The Forbidden City", Fee = 10000 }); 6:  7: Thread thread = null; 8:  9: while (HouseMovingCompany.Instance.Contracts.Count > 0) 10: { 11: thread = new Thread(new ThreadStart(HouseMovingCompany.Instance.MoveHouse)); 12:  13: thread.Start(); 14: } 15: }
在这段程序中,我们分头行动,让每项搬家任务都由一个小团队去完成,结果我们发现,现在做三个单子的时间跟做一个单子的时间是一样的,提高了效率也扩大了公司规模。但是,既然引入了新的工作机制,我们在公司内部也不得不做一些小小的调整:
1: public void MoveHouse() 2: { 3: if (this.Contracts == null || this.Contracts.Count == 0) 4: { 5: return; 6: } 7:  8: Contract contract = null; 9:  10: lock (this.Contracts) 11: { 12: contract = this.Contracts[0]; 13: this.Contracts.RemoveAt(0); 14: } 15:  16: if (!String.IsNullOrEmpty(contract.From) && !String.IsNullOrEmpty(contract.To)) 17: { 18: Console.WriteLine("Move the house from {0} to {1}.", contract.From, contract.To); 19: } 20:  21: Thread.Sleep(5000); 22: }
调整的只是MoveHouse这个方法内部的一些实现细节。公司接到的单子都保存在Contracts中,所以搬家的时候需要去拿一个单子然后根据单子上的信息来工作。原先我们只有一个线程在操作Contracts,倒也不觉得什么,现在有多个线程都在对Contracts中的元素进行存取,我们不得不提防一些意外发生。这就是在使用多线程的时候常常需要考虑的并发问题,所以我们用了lock关键字,当一个线程要操作Contracts时,它先把Contracts锁起来,其实就是声明一下:“现在我在操作它,你们谁都不要动,等我弄完了再说。”在lock块结束时被锁定的对象才会被解锁,其它的线程现在才可以去操作它。
有了多线程机制,你会发现程序可以在更短的时间内完成更多的事情。本文没有将多线程机制中的所有概念面面俱到地列举出来,但是已经向你展示了该如何使用多线程以及什么时候可以考虑使用多线程,其它的一些细节有待你去进一步探索,例如,你可以设置线程的优先级(假设逻辑上跟Fee挂钩,类似于‘加急’)等等。
掌握多线程机制,并让它使你的应用程序变得更加强悍吧。
C#多线程学习(一) 多线程的相关概念
作者:钢钢  来源:博客园  发布时间: 2008-09-20 18:42  阅读: 42851 次  原文链接  全屏阅读[收藏]
摘要:关于线程的相关概念
本系列文章导航
C#多线程学习(一) 多线程的相关概念
C#多线程学习(二) 如何操纵一个线程
C#多线程学习(三) 生产者和消费者
C#多线程学习(四) 多线程的自动管理(线程池)
C#多线程学习(五) 多线程的自动管理(定时器)
C#多线程学习(六) 互斥对象
什么是进程?
当一个程序开始运行时,它就是一个进程,进程包括运行中的程序和程序所使用到的内存和系统资源。而一个进程又是由多个线程所组成的。
什么是线程?
线程是程序中的一个执行流,每个线程都有自己的专有寄存器(栈指针、程序计数器等),但代码区是共享的,即不同的线程可以执行同样的函数。
什么是多线程?
多线程是指程序中包含多个执行流,即在一个程序中可以同时运行多个不同的线程来执行不同的任务,也就是说允许单个程序创建多个并行执行的线程来完成各自的任务。
多线程的好处:
可以提高CPU的利用率。在多线程程序中,一个线程必须等待的时候,CPU可以运行其它的线程而不是等待,这样就大大提高了程序的效率。
多线程的不利方面:
线程也是程序,所以线程需要占用内存,线程越多占用内存也越多; 多线程需要协调和管理,所以需要CPU时间跟踪线程; 线程之间对共享资源的访问会相互影响,必须解决竞用共享资源的问题;线程太多会导致控制太复杂,最终可能造成很多Bug;
接下来将对C#编程中的多线程机制进行探讨。为了省去创建GUI那些繁琐的步骤,更清晰地逼近线程的本质,接下来的所有程序都是控制台程序,程序最后的Console.ReadLine()是为了使程序中途停下来,以便看清楚执行过程中的输出。
任何程序在执行时,至少有一个主线程。
一个直观印象的线程示例:
using System;
using System.Threading;
namespace ThreadTest
{
class RunIt
{
[STAThread]
static void Main(string[] args)
{
Thread.CurrentThread.Name="System Thread";//给当前线程起名为"System Thread"
Console.WriteLine(Thread.CurrentThread.Name+"'Status:"+Thread.CurrentThread.ThreadState);
Console.ReadLine();
}
}
}
输出如下:
System Thread's Status:Running
在这里,我们通过Thread类的静态属性CurrentThread获取了当前执行的线程,对其Name属性赋值“System Thread”,最后还输出了它的当前状态(ThreadState)。
所谓静态属性,就是这个类所有对象所公有的属性,不管你创建了多少个这个类的实例,但是类的静态属性在内存中只有一个。很容易理解CurrentThread为什么是静态的——虽然有多个线程同时存在,但是在某一个时刻,CPU只能执行其中一个。
在程序的头部,我们使用了如下命名空间:
using System;
using System.Threading;
在.net framework class library中,所有与多线程机制应用相关的类都是放在System.Threading命名空间中的。如果你想在你的应用程序中使用多线程,就必须包含这个类。
我们通过其中提供的Thread类来创建和控制线程,ThreadPool类用于管理线程池等。(此外还提供解决了线程执行安排,死锁,线程间通讯等实际问题的机制。)
Thread类有几个至关重要的方法,描述如下:
Start():启动线程;
Sleep(int):静态方法,暂停当前线程指定的毫秒数;
Abort():通常使用该方法来终止一个线程;
Suspend():该方法并不终止未完成的线程,它仅仅挂起线程,以后还可恢复;
Resume():恢复被Suspend()方法挂起的线程的执行。
C#多线程学习(二) 如何操纵一个线程
作者:钢钢  来源:博客园  发布时间: 2008-09-20 19:03  阅读: 30815 次  原文链接  全屏阅读[收藏]
[1] C#多线程学习(二) 如何操纵一个线程
[2]C#多线程学习(二) 如何操纵一个线程
本系列文章导航
C#多线程学习(一) 多线程的相关概念
C#多线程学习(二) 如何操纵一个线程
C#多线程学习(三) 生产者和消费者
C#多线程学习(四) 多线程的自动管理(线程池)
C#多线程学习(五) 多线程的自动管理(定时器)
C#多线程学习(六) 互斥对象
下面我们就动手来创建一个线程,使用Thread类创建线程时,只需提供线程入口即可。(线程入口使程序知道该让这个线程干什么事)
在C#中,线程入口是通过ThreadStart代理(delegate)来提供的,你可以把ThreadStart理解为一个函数指针,指向线程要执行的函数,当调用Thread.Start()方法后,线程就开始执行ThreadStart所代表或者说指向的函数。
打开你的VS.net,新建一个控制台应用程序(Console Application),编写完全控制一个线程的代码示例:
using System;
using System.Threading;
namespace ThreadTest
{
public class Alpha
{
public void Beta()
{
while (true)
{
Console.WriteLine("Alpha.Beta is running in its own thread.");
}
}
};
public class Simple
{
public static int Main()
{
Console.WriteLine("Thread Start/Stop/Join Sample");
Alpha oAlpha = new Alpha();
file://这里创建一个线程,使之执行Alpha类的Beta()方法
Thread oThread = new Thread(new ThreadStart(oAlpha.Beta));
oThread.Start();
while (!oThread.IsAlive)
Thread.Sleep(1);
oThread.Abort();
oThread.Join();
Console.WriteLine();
Console.WriteLine("Alpha.Beta has finished");
try
{
Console.WriteLine("Try to restart the Alpha.Beta thread");
oThread.Start();
}
catch (ThreadStateException)
{
Console.Write("ThreadStateException trying to restart Alpha.Beta. ");
Console.WriteLine("Expected since aborted threads cannot be restarted.");
Console.ReadLine();
}
return 0;
}
}
}
这段程序包含两个类Alpha和Simple,在创建线程oThread时我们用指向Alpha.Beta()方法的初始化了ThreadStart代理(delegate)对象,当我们创建的线程oThread调用oThread.Start()方法启动时,实际上程序运行的是Alpha.Beta()方法:
Alpha oAlpha = new Alpha();
Thread oThread = new Thread(new ThreadStart(oAlpha.Beta));
oThread.Start();
然后在Main()函数的while循环中,我们使用静态方法Thread.Sleep()让主线程停了1ms,这段时间CPU转向执行线程oThread。然后我们试图用Thread.Abort()方法终止线程oThread,注意后面的oThread.Join(),Thread.Join()方法使主线程等待,直到oThread线程结束。你可以给Thread.Join()方法指定一个int型的参数作为等待的最长时间。之后,我们试图用Thread.Start()方法重新启动线程oThread,但是显然Abort()方法带来的后果是不可恢复的终止线程,所以最后程序会抛出ThreadStateException异常。
C#多线程学习(二) 如何操纵一个线程
作者:钢钢  来源:博客园  发布时间: 2008-09-20 19:03  阅读: 30816 次  原文链接  全屏阅读[收藏]
[1]C#多线程学习(二) 如何操纵一个线程
[2] C#多线程学习(二) 如何操纵一个线程
本系列文章导航
C#多线程学习(一) 多线程的相关概念
C#多线程学习(二) 如何操纵一个线程
C#多线程学习(三) 生产者和消费者
C#多线程学习(四) 多线程的自动管理(线程池)
C#多线程学习(五) 多线程的自动管理(定时器)
C#多线程学习(六) 互斥对象
主线程Main()函数
所有线程都是依附于Main()函数所在的线程的,Main()函数是C#程序的入口,起始线程可以称之为主线程。如果所有的前台线程都停止了,那么主线程可以终止,而所有的后台线程都将无条件终止。所有的线程虽然在微观上是串行执行的,但是在宏观上你完全可以认为它们在并行执行。
Thread.ThreadState 属性
这个属性代表了线程运行时状态,在不同的情况下有不同的值,我们有时候可以通过对该值的判断来设计程序流程。
ThreadState 属性的取值如下:
Aborted:线程已停止;
AbortRequested:线程的Thread.Abort()方法已被调用,但是线程还未停止;
Background:线程在后台执行,与属性Thread.IsBackground有关;
Running:线程正在正常运行;
Stopped:线程已经被停止;
StopRequested:线程正在被要求停止;
Suspended:线程已经被挂起(此状态下,可以通过调用Resume()方法重新运行);
SuspendRequested:线程正在要求被挂起,但是未来得及响应;
Unstarted:未调用Thread.Start()开始线程的运行;
WaitSleepJoin:线程因为调用了Wait(),Sleep()或Join()等方法处于封锁状态;
上面提到了Background状态表示该线程在后台运行,那么后台运行的线程有什么特别的地方呢?其实后台线程跟前台线程只有一个区别,那就是后台线程不妨碍程序的终止。一旦一个进程所有的前台线程都终止后,CLR(通用语言运行环境)将通过调用任意一个存活中的后台进程的Abort()方法来彻底终止进程。
线程的优先级
当线程之间争夺CPU时间时,CPU 是按照线程的优先级给予服务的。在C#应用程序中,用户可以设定5个不同的优先级,由高到低分别是Highest,AboveNormal,Normal,BelowNormal,Lowest,在创建线程时如果不指定优先级,那么系统默认为ThreadPriority.Normal。
给一个线程指定优先级,我们可以使用如下代码:
//设定优先级为最低
myThread.Priority=ThreadPriority.Lowest;
通过设定线程的优先级,我们可以安排一些相对重要的线程优先执行,例如对用户的响应等等。
C#多线程学习(三) 生产者和消费者
作者:钢钢  来源:博客园  发布时间: 2008-09-20 19:16  阅读: 20389 次  原文链接  全屏阅读[收藏]
[1] C#多线程学习(三) 生产者和消费者
[2]C#多线程学习(三) 生产者和消费者
本系列文章导航
C#多线程学习(一) 多线程的相关概念
C#多线程学习(二) 如何操纵一个线程
C#多线程学习(三) 生产者和消费者
C#多线程学习(四) 多线程的自动管理(线程池)
C#多线程学习(五) 多线程的自动管理(定时器)
C#多线程学习(六) 互斥对象
前面说过,每个线程都有自己的资源,但是代码区是共享的,即每个线程都可以执行相同的函数。这可能带来的问题就是几个线程同时执行一个函数,导致数据的混乱,产生不可预料的结果,因此我们必须避免这种情况的发生。
C#提供了一个关键字lock,它可以把一段代码定义为互斥段(critical section),互斥段在一个时刻内只允许一个线程进入执行,而其他线程必须等待。在C#中,关键字lock定义如下:
lock() statement_block
代表你希望跟踪的对象,通常是对象引用。
如果你想保护一个类的实例,一般地,你可以使用this; 如果你想保护一个静态变量(如互斥代码段在一个静态方法内部),一般使用类名就可以了。
而statement_block就是互斥段的代码,这段代码在一个时刻内只可能被一个线程执行。
下面是一个使用lock关键字的典型例子,在注释里说明了lock关键字的用法和用途。
示例如下:
Code
using System;
using System.Threading;
namespace ThreadSimple
{
internal class Account
{
int balance;
Random r = new Random();
internal Account(int initial)
{
balance = initial;
}
internal int Withdraw(int amount)
{
if (balance < 0)
{
//如果balance小于0则抛出异常
throw new Exception("Negative Balance");
}
//下面的代码保证在当前线程修改balance的值完成之前
//不会有其他线程也执行这段代码来修改balance的值
//因此,balance的值是不可能小于0 的
lock (this)
{
Console.WriteLine("Current Thread:"+Thread.CurrentThread.Name);
//如果没有lock关键字的保护,那么可能在执行完if的条件判断之后
//另外一个线程却执行了balance=balance-amount修改了balance的值
//而这个修改对这个线程是不可见的,所以可能导致这时if的条件已经不成立了
//但是,这个线程却继续执行balance=balance-amount,所以导致balance可能小于0
if (balance >= amount)
{
Thread.Sleep(5);
balance = balance - amount;
return amount;
}
else
{
return 0; // transaction rejected
}
}
}
internal void DoTransactions()
{
for (int i = 0; i < 100; i++)
Withdraw(r.Next(-50, 100));
}
}
internal class Test
{
static internal Thread[] threads = new Thread[10];
public static void Main()
{
Account acc = new Account (0);
for (int i = 0; i < 10; i++)
{
Thread t = new Thread(new ThreadStart(acc.DoTransactions));
threads[i] = t;
}
for (int i = 0; i < 10; i++)
threads[i].Name=i.ToString();
for (int i = 0; i < 10; i++)
threads[i].Start();
Console.ReadLine();
}
}
}
Monitor 类锁定一个对象
当多线程公用一个对象时,也会出现和公用代码类似的问题,这种问题就不应该使用lock关键字了,这里需要用到System.Threading中的一个类Monitor,我们可以称之为监视器,Monitor提供了使线程共享资源的方案。
Monitor类可以锁定一个对象,一个线程只有得到这把锁才可以对该对象进行操作。对象锁机制保证了在可能引起混乱的情况下一个时刻只有一个线程可以访问这个对象。 Monitor必须和一个具体的对象相关联,但是由于它是一个静态的类,所以不能使用它来定义对象,而且它的所有方法都是静态的,不能使用对象来引用。下面代码说明了使用Monitor锁定一个对象的情形:
......
Queue oQueue=new Queue();
......
Monitor.Enter(oQueue);
......//现在oQueue对象只能被当前线程操纵了
Monitor.Exit(oQueue);//释放锁
如上所示,当一个线程调用Monitor.Enter()方法锁定一个对象时,这个对象就归它所有了,其它线程想要访问这个对象,只有等待它使用Monitor.Exit()方法释放锁。为了保证线程最终都能释放锁,你可以把Monitor.Exit()方法写在try-catch-finally结构中的finally代码块里。
C#多线程学习(三) 生产者和消费者
作者:钢钢  来源:博客园  发布时间: 2008-09-20 19:16  阅读: 20390 次  原文链接  全屏阅读[收藏]
[1]C#多线程学习(三) 生产者和消费者
[2] C#多线程学习(三) 生产者和消费者
本系列文章导航
C#多线程学习(一) 多线程的相关概念
C#多线程学习(二) 如何操纵一个线程
C#多线程学习(三) 生产者和消费者
C#多线程学习(四) 多线程的自动管理(线程池)
C#多线程学习(五) 多线程的自动管理(定时器)
C#多线程学习(六) 互斥对象
对于任何一个被Monitor锁定的对象,内存中都保存着与它相关的一些信息:
其一是现在持有锁的线程的引用;
其二是一个预备队列,队列中保存了已经准备好获取锁的线程;
其三是一个等待队列,队列中保存着当前正在等待这个对象状态改变的队列的引用。
当拥有对象锁的线程准备释放锁时,它使用Monitor.Pulse()方法通知等待队列中的第一个线程,于是该线程被转移到预备队列中,当对象锁被释放时,在预备队列中的线程可以立即获得对象锁。
下面是一个展示如何使用lock关键字和Monitor类来实现线程的同步和通讯的例子,也是一个典型的生产者与消费者问题。
这个例程中,生产者线程和消费者线程是交替进行的,生产者写入一个数,消费者立即读取并且显示(注释中介绍了该程序的精要所在)。
用到的系统命名空间如下:
using System;
using System.Threading;
首先,定义一个被操作的对象的类Cell,在这个类里,有两个方法:ReadFromCell()和WriteToCell。消费者线程将调用ReadFromCell()读取cellContents的内容并且显示出来,生产者进程将调用WriteToCell()方法向cellContents写入数据。
示例如下:
Code
public class Cell
{
int cellContents; // Cell对象里边的内容
bool readerFlag = false; // 状态标志,为true时可以读取,为false则正在写入
public int ReadFromCell( )
{
lock(this) // Lock关键字保证了什么,请大家看前面对lock的介绍
{
if (!readerFlag)//如果现在不可读取
{
try
{
//等待WriteToCell方法中调用Monitor.Pulse()方法
Monitor.Wait(this);
}
catch (SynchronizationLockException e)
{
Console.WriteLine(e);
}
catch (ThreadInterruptedException e)
{
Console.WriteLine(e);
}
}
Console.WriteLine("Consume: {0}",cellContents);
readerFlag = false;
//重置readerFlag标志,表示消费行为已经完成
Monitor.Pulse(this);
//通知WriteToCell()方法(该方法在另外一个线程中执行,等待中)
}
return cellContents;
}
public void WriteToCell(int n)
{
lock(this)
{
if (readerFlag)
{
try
{
Monitor.Wait(this);
}
catch (SynchronizationLockException e)
{
//当同步方法(指Monitor类除Enter之外的方法)在非同步的代码区被调用
Console.WriteLine(e);
}
catch (ThreadInterruptedException e)
{
//当线程在等待状态的时候中止
Console.WriteLine(e);
}
}
cellContents = n;
Console.WriteLine("Produce: {0}",cellContents);
readerFlag = true;
Monitor.Pulse(this);
//通知另外一个线程中正在等待的ReadFromCell()方法
}
}
}
下面定义生产者类 CellProd 和消费者类 CellCons ,它们都只有一个方法ThreadRun(),以便在Main()函数中提供给线程的ThreadStart代理对象,作为线程的入口。
public class CellProd
{
Cell cell; // 被操作的Cell对象
int quantity = 1; // 生产者生产次数,初始化为1
public CellProd(Cell box, int request)
{
//构造函数
cell = box;
quantity = request;
}
public void ThreadRun( )
{
for(int looper=1; looper<=quantity; looper++)
cell.WriteToCell(looper); //生产者向操作对象写入信息
}
}
public class CellCons
{
Cell cell;
int quantity = 1;
public CellCons(Cell box, int request)
{
//构造函数
cell = box;
quantity = request;
}
public void ThreadRun( )
{
int valReturned;
for(int looper=1; looper<=quantity; looper++)
valReturned=cell.ReadFromCell( );//消费者从操作对象中读取信息
}
}
然后在下面这个类MonitorSample的Main()函数中,我们要做的就是创建两个线程分别作为生产者和消费者,使用CellProd.ThreadRun()方法和CellCons.ThreadRun()方法对同一个Cell对象进行操作。
Code
public class MonitorSample
{
public static void Main(String[] args)
{
int result = 0; //一个标志位,如果是0表示程序没有出错,如果是1表明有错误发生
Cell cell = new Cell( );
//下面使用cell初始化CellProd和CellCons两个类,生产和消费次数均为20次
CellProd prod = new CellProd(cell, 20);
CellCons cons = new CellCons(cell, 20);
Thread producer = new Thread(new ThreadStart(prod.ThreadRun));
Thread consumer = new Thread(new ThreadStart(cons.ThreadRun));
//生产者线程和消费者线程都已经被创建,但是没有开始执行
try
{
producer.Start( );
consumer.Start( );
producer.Join( );
consumer.Join( );
Console.ReadLine();
}
catch (ThreadStateException e)
{
//当线程因为所处状态的原因而不能执行被请求的操作
Console.WriteLine(e);
result = 1;
}
catch (ThreadInterruptedException e)
{
//当线程在等待状态的时候中止
Console.WriteLine(e);
result = 1;
}
//尽管Main()函数没有返回值,但下面这条语句可以向父进程返回执行结果
Environment.ExitCode = result;
}
}
在上面的例程中,同步是通过等待Monitor.Pulse()来完成的。首先生产者生产了一个值,而同一时刻消费者处于等待状态,直到收到生产者的“脉冲(Pulse)”通知它生产已经完成,此后消费者进入消费状态,而生产者开始等待消费者完成操作后将调用Monitor.Pulese()发出的“脉冲”。
它的执行结果很简单:
Produce: 1
Consume: 1
Produce: 2
Consume: 2
Produce: 3
Consume: 3
...
...
Produce: 20
Consume: 20
事实上,这个简单的例子已经帮助我们解决了多线程应用程序中可能出现的大问题,只要领悟了解决线程间冲突的基本方法,很容易把它应用到比较复杂的程序中去。
C#多线程学习(四) 多线程的自动管理(线程池)
作者:钢钢  来源:博客园  发布时间: 2008-09-20 19:26  阅读: 15341 次  原文链接  全屏阅读[收藏]
本系列文章导航
C#多线程学习(一) 多线程的相关概念
C#多线程学习(二) 如何操纵一个线程
C#多线程学习(三) 生产者和消费者
C#多线程学习(四) 多线程的自动管理(线程池)
C#多线程学习(五) 多线程的自动管理(定时器)
C#多线程学习(六) 互斥对象
在多线程的程序中,经常会出现两种情况:
一种情况: 应用程序中,线程把大部分的时间花费在等待状态,等待某个事件发生,然后才能给予响应
这一般使用ThreadPool(线程池)来解决;
另一种情况:线程平时都处于休眠状态,只是周期性地被唤醒
这一般使用Timer(定时器)来解决;
ThreadPool类提供一个由系统维护的线程池(可以看作一个线程的容器),该容器需要 Windows 2000 以上系统支持,因为其中某些方法调用了只有高版本的Windows才有的API函数。
将线程安放在线程池里,需使用ThreadPool.QueueUserWorkItem()方法,该方法的原型如下:
//将一个线程放进线程池,该线程的Start()方法将调用WaitCallback代理对象代表的函数
public static bool QueueUserWorkItem(WaitCallback);
//重载的方法如下,参数object将传递给WaitCallback所代表的方法
public static bool QueueUserWorkItem(WaitCallback, object);
ThreadPool类是一个静态类,你不能也不必要生成它的对象。而且一旦使用该方法在线程池中添加了一个项目,那么该项目将是无法取消的。
在这里你无需自己建立线程,只需把你要做的工作写成函数,然后作为参数传递给ThreadPool.QueueUserWorkItem()方法就行了,传递的方法就是依靠WaitCallback代理对象,而线程的建立、管理、运行等工作都是由系统自动完成的,你无须考虑那些复杂的细节问题。
ThreadPool 的用法:
首先程序创建了一个ManualResetEvent对象,该对象就像一个信号灯,可以利用它的信号来通知其它线程。
本例中,当线程池中所有线程工作都完成以后,ManualResetEvent对象将被设置为有信号,从而通知主线程继续运行。
ManualResetEvent对象有几个重要的方法:
初始化该对象时,用户可以指定其默认的状态(有信号/无信号);
在初始化以后,该对象将保持原来的状态不变,直到它的Reset()或者Set()方法被调用:
Reset()方法:将其设置为无信号状态;
Set()方法:将其设置为有信号状态。
WaitOne()方法:使当前线程挂起,直到ManualResetEvent对象处于有信号状态,此时该线程将被激活。然后,程序将向线程池中添加工作项,这些以函数形式提供的工作项被系统用来初始化自动建立的线程。当所有的线程都运行完了以后,ManualResetEvent.Set()方法被调用,因为调用了ManualResetEvent.WaitOne()方法而处在等待状态的主线程将接收到这个信号,于是它接着往下执行,完成后边的工作。
ThreadPool 的用法示例:
Code
using System;
using System.Collections;
using System.Threading;
namespace ThreadExample
{
//这是用来保存信息的数据结构,将作为参数被传递
public class SomeState
{
public int Cookie;
public SomeState(int iCookie)
{
Cookie = iCookie;
}
}
public class Alpha
{
public Hashtable HashCount;
public ManualResetEvent eventX;
public static int iCount = 0;
public static int iMaxCount = 0;
public Alpha(int MaxCount)
{
HashCount = new Hashtable(MaxCount);
iMaxCount = MaxCount;
}
//线程池里的线程将调用Beta()方法
public void Beta(Object state)
{
//输出当前线程的hash编码值和Cookie的值
Console.WriteLine(" {0} {1} :", Thread.CurrentThread.GetHashCode(),((SomeState)state).Cookie);
Console.WriteLine("HashCount.Count=={0}, Thread.CurrentThread.GetHashCode()=={1}", HashCount.Count, Thread.CurrentThread.GetHashCode());
lock (HashCount)
{
//如果当前的Hash表中没有当前线程的Hash值,则添加之
if (!HashCount.ContainsKey(Thread.CurrentThread.GetHashCode()))
HashCount.Add (Thread.CurrentThread.GetHashCode(), 0);
HashCount[Thread.CurrentThread.GetHashCode()] =
((int)HashCount[Thread.CurrentThread.GetHashCode()])+1;
}
int iX = 2000;
Thread.Sleep(iX);
//Interlocked.Increment()操作是一个原子操作,具体请看下面说明
Interlocked.Increment(ref iCount);
if (iCount == iMaxCount)
{
Console.WriteLine();
Console.WriteLine("Setting eventX ");
eventX.Set();
}
}
}
public class SimplePool
{
public static int Main(string[] args)
{
Console.WriteLine("Thread Pool Sample:");
bool W2K = false;
int MaxCount = 10;//允许线程池中运行最多10个线程
//新建ManualResetEvent对象并且初始化为无信号状态
ManualResetEvent eventX = new ManualResetEvent(false);
Console.WriteLine("Queuing {0} items to Thread Pool", MaxCount);
Alpha oAlpha = new Alpha(MaxCount);
//创建工作项
//注意初始化oAlpha对象的eventX属性
oAlpha.eventX = eventX;
Console.WriteLine("Queue to Thread Pool 0");
try
{
//将工作项装入线程池
//这里要用到Windows 2000以上版本才有的API,所以可能出现NotSupportException异常
ThreadPool.QueueUserWorkItem(new WaitCallback(oAlpha.Beta), new SomeState(0));
W2K = true;
}
catch (NotSupportedException)
{
Console.WriteLine("These API's may fail when called on a non-Windows 2000 system.");
W2K = false;
}
if (W2K)//如果当前系统支持ThreadPool的方法.
{
for (int iItem=1;iItem < MaxCount;iItem++)
{
//插入队列元素
Console.WriteLine("Queue to Thread Pool {0}", iItem);
ThreadPool.QueueUserWorkItem(new WaitCallback(oAlpha.Beta), new SomeState(iItem));
}
Console.WriteLine("Waiting for Thread Pool to drain");
//等待事件的完成,即线程调用ManualResetEvent.Set()方法
eventX.WaitOne(Timeout.Infinite,true);
//WaitOne()方法使调用它的线程等待直到eventX.Set()方法被调用
Console.WriteLine("Thread Pool has been drained (Event fired)");
Console.WriteLine();
Console.WriteLine("Load across threads");
foreach(object o in oAlpha.HashCount.Keys)
Console.WriteLine("{0} {1}", o, oAlpha.HashCount[o]);
}
Console.ReadLine();
return 0;
}
}
}
}
程序中应该引起注意的地方:
SomeState类是一个保存信息的数据结构,它在程序中作为参数被传递给每一个线程,因为你需要把一些有用的信息封装起来提供给线程,而这种方式是非常有效的。
程序出现的InterLocked类也是专为多线程程序而存在的,它提供了一些有用的原子操作。
原子操作:就是在多线程程序中,如果这个线程调用这个操作修改一个变量,那么其他线程就不能修改这个变量了,这跟lock关键字在本质上是一样的。
输出的结果
Thread Pool Sample:
Queuing 10 items to Thread Pool
Queue to Thread Pool 0
Queue to Thread Pool 1
Queue to Thread Pool 2
Queue to Thread Pool 3
Queue to Thread Pool 4
Queue to Thread Pool 5
2 0 :
HashCount.Count==0, Thread.CurrentThread.GetHashCode()==2
Queue to Thread Pool 6
Queue to Thread Pool 7
Queue to Thread Pool 8
Queue to Thread Pool 9
Waiting for Thread Pool to drain
4 1 :
HashCount.Count==1, Thread.CurrentThread.GetHashCode()==4
6 2 :
HashCount.Count==1, Thread.CurrentThread.GetHashCode()==6
7 3 :
HashCount.Count==1, Thread.CurrentThread.GetHashCode()==7
2 4 :
HashCount.Count==1, Thread.CurrentThread.GetHashCode()==2
8 5 :
HashCount.Count==2, Thread.CurrentThread.GetHashCode()==8
9 6 :
HashCount.Count==2, Thread.CurrentThread.GetHashCode()==9
10 7 :
HashCount.Count==2, Thread.CurrentThread.GetHashCode()==10
11 8 :
HashCount.Count==2, Thread.CurrentThread.GetHashCode()==11
4 9 :
HashCount.Count==2, Thread.CurrentThread.GetHashCode()==4
Setting eventX
Thread Pool has been drained (Event fired)
Load across threads
11 1
10 1
9 1
8 1
7 1
6 1
4 2
2 2
我们应该彻底地分析上面的程序,把握住线程池的本质,理解它存在的意义是什么,这样才能得心应手地使用它。
C#多线程学习(五) 多线程的自动管理(定时器)
作者:钢钢  来源:博客园  发布时间: 2008-09-20 19:30  阅读: 10276 次  原文链接  全屏阅读[收藏]
本系列文章导航
C#多线程学习(一) 多线程的相关概念
C#多线程学习(二) 如何操纵一个线程
C#多线程学习(三) 生产者和消费者
C#多线程学习(四) 多线程的自动管理(线程池)
C#多线程学习(五) 多线程的自动管理(定时器)
C#多线程学习(六) 互斥对象
Timer类:设置一个定时器,定时执行用户指定的函数。
定时器启动后,系统将自动建立一个新的线程,执行用户指定的函数。
初始化一个Timer对象:
Timer timer = new Timer(timerDelegate, s,1000, 1000);
// 第一个参数:指定了TimerCallback 委托,表示要执行的方法;
// 第二个参数:一个包含回调方法要使用的信息的对象,或者为空引用;
// 第三个参数:延迟时间——计时开始的时刻距现在的时间,单位是毫秒,指定为“0”表示立即启动计时器;
// 第四个参数:定时器的时间间隔——计时开始以后,每隔这么长的一段时间,TimerCallback所代表的方法将被调用一次,单位也是毫秒。指定 Timeout.Infinite 可以禁用定期终止。
Timer.Change()方法:修改定时器的设置。(这是一个参数类型重载的方法)
使用示例: timer.Change(1000,2000);
Timer类的程序示例(来源:MSDN):
Code
using System;
using System.Threading;
namespace ThreadExample
{
class TimerExampleState
{
public int counter = 0;
public Timer tmr;
}
class App
{
public static void Main()
{
TimerExampleState s = new TimerExampleState();
//创建代理对象TimerCallback,该代理将被定时调用
TimerCallback timerDelegate = new TimerCallback(CheckStatus);
//创建一个时间间隔为1s的定时器
Timer timer = new Timer(timerDelegate, s,1000, 1000);
s.tmr = timer;
//主线程停下来等待Timer对象的终止
while(s.tmr != null)
Thread.Sleep(0);
Console.WriteLine("Timer example done.");
Console.ReadLine();
}
//下面是被定时调用的方法
static void CheckStatus(Object state)
{
TimerExampleState s =(TimerExampleState)state;
s.counter++;
Console.WriteLine("{0} Checking Status {1}.",DateTime.Now.TimeOfDay, s.counter);
if(s.counter == 5)
{
//使用Change方法改变了时间间隔
(s.tmr).Change(10000,2000);
Console.WriteLine("changed");
}
if(s.counter == 10)
{
Console.WriteLine("disposing of timer");
s.tmr.Dispose();
s.tmr = null;
}
}
}
}
程序首先创建了一个定时器,它将在创建1秒之后开始每隔1秒调用一次CheckStatus()方法,当调用5次以后,在CheckStatus()方法中修改了时间间隔为2秒,并且指定在10秒后重新开始。当计数达到10次,调用Timer.Dispose()方法删除了timer对象,主线程于是跳出循环,终止程序。
C#多线程学习(六) 互斥对象
作者:钢钢  来源:博客园  发布时间: 2008-09-20 19:34  阅读: 8717 次  原文链接  全屏阅读[收藏]
本系列文章导航
C#多线程学习(一) 多线程的相关概念
C#多线程学习(二) 如何操纵一个线程
C#多线程学习(三) 生产者和消费者
C#多线程学习(四) 多线程的自动管理(线程池)
C#多线程学习(五) 多线程的自动管理(定时器)
C#多线程学习(六) 互斥对象
如何控制好多个线程相互之间的联系,不产生冲突和重复,这需要用到互斥对象,即:System.Threading 命名空间中的 Mutex 类。
我们可以把Mutex看作一个出租车,乘客看作线程。乘客首先等车,然后上车,最后下车。当一个乘客在车上时,其他乘客就只有等他下车以后才可以上车。而线程与Mutex对象的关系也正是如此,线程使用Mutex.WaitOne()方法等待Mutex对象被释放,如果它等待的Mutex对象被释放了,它就自动拥有这个对象,直到它调用Mutex.ReleaseMutex()方法释放这个对象,而在此期间,其他想要获取这个Mutex对象的线程都只有等待。
下面这个例子使用了Mutex对象来同步四个线程,主线程等待四个线程的结束,而这四个线程的运行又是与两个Mutex对象相关联的。
其中还用到AutoResetEvent类的对象,可以把它理解为一个信号灯。这里用它的有信号状态来表示一个线程的结束。
// AutoResetEvent.Set()方法设置它为有信号状态
// AutoResetEvent.Reset()方法设置它为无信号状态
Mutex 类的程序示例:
Code
using System;
using System.Threading;
namespace ThreadExample
{
public class MutexSample
{
static Mutex gM1;
static Mutex gM2;
const int ITERS = 100;
static AutoResetEvent Event1 = new AutoResetEvent(false);
static AutoResetEvent Event2 = new AutoResetEvent(false);
static AutoResetEvent Event3 = new AutoResetEvent(false);
static AutoResetEvent Event4 = new AutoResetEvent(false);
public static void Main(String[] args)
{
Console.WriteLine("Mutex Sample ");
//创建一个Mutex对象,并且命名为MyMutex
gM1 = new Mutex(true,"MyMutex");
//创建一个未命名的Mutex 对象.
gM2 = new Mutex(true);
Console.WriteLine(" - Main Owns gM1 and gM2");
AutoResetEvent[] evs = new AutoResetEvent[4];
evs[0] = Event1; //为后面的线程t1,t2,t3,t4定义AutoResetEvent对象
evs[1] = Event2;
evs[2] = Event3;
evs[3] = Event4;
MutexSample tm = new MutexSample( );
Thread t1 = new Thread(new ThreadStart(tm.t1Start));
Thread t2 = new Thread(new ThreadStart(tm.t2Start));
Thread t3 = new Thread(new ThreadStart(tm.t3Start));
Thread t4 = new Thread(new ThreadStart(tm.t4Start));
t1.Start( );// 使用Mutex.WaitAll()方法等待一个Mutex数组中的对象全部被释放
t2.Start( );// 使用Mutex.WaitOne()方法等待gM1的释放
t3.Start( );// 使用Mutex.WaitAny()方法等待一个Mutex数组中任意一个对象被释放
t4.Start( );// 使用Mutex.WaitOne()方法等待gM2的释放
Thread.Sleep(2000);
Console.WriteLine(" - Main releases gM1");
gM1.ReleaseMutex( ); //线程t2,t3结束条件满足
Thread.Sleep(1000);
Console.WriteLine(" - Main releases gM2");
gM2.ReleaseMutex( ); //线程t1,t4结束条件满足
//等待所有四个线程结束
WaitHandle.WaitAll(evs);
Console.WriteLine(" Mutex Sample");
Console.ReadLine();
}
public void t1Start( )
{
Console.WriteLine("t1Start started, Mutex.WaitAll(Mutex[])");
Mutex[] gMs = new Mutex[2];
gMs[0] = gM1;//创建一个Mutex数组作为Mutex.WaitAll()方法的参数
gMs[1] = gM2;
Mutex.WaitAll(gMs);//等待gM1和gM2都被释放
Thread.Sleep(2000);
Console.WriteLine("t1Start finished, Mutex.WaitAll(Mutex[]) satisfied");
Event1.Set( ); //线程结束,将Event1设置为有信号状态
}
public void t2Start( )
{
Console.WriteLine("t2Start started, gM1.WaitOne( )");
gM1.WaitOne( );//等待gM1的释放
Console.WriteLine("t2Start finished, gM1.WaitOne( ) satisfied");
Event2.Set( );//线程结束,将Event2设置为有信号状态
}
public void t3Start( )
{
Console.WriteLine("t3Start started, Mutex.WaitAny(Mutex[])");
Mutex[] gMs = new Mutex[2];
gMs[0] = gM1;//创建一个Mutex数组作为Mutex.WaitAny()方法的参数
gMs[1] = gM2;
Mutex.WaitAny(gMs);//等待数组中任意一个Mutex对象被释放
Console.WriteLine("t3Start finished, Mutex.WaitAny(Mutex[])");
Event3.Set( );//线程结束,将Event3设置为有信号状态
}
public void t4Start( )
{
Console.WriteLine("t4Start started, gM2.WaitOne( )");
gM2.WaitOne( );//等待gM2被释放
Console.WriteLine("t4Start finished, gM2.WaitOne( )");
Event4.Set( );//线程结束,将Event4设置为有信号状态
}
}
}
程序的输出结果:
结果
Mutex Sample
- Main Owns gM1 and gM2
t1Start started, Mutex.WaitAll(Mutex[])
t2Start started, gM1.WaitOne( )
t3Start started, Mutex.WaitAny(Mutex[])
t4Start started, gM2.WaitOne( )
- Main releases gM1
t2Start finished, gM1.WaitOne( ) satisfied
t3Start finished, Mutex.WaitAny(Mutex[])
- Main releases gM2
t1Start finished, Mutex.WaitAll(Mutex[]) satisfied
t4Start finished, gM2.WaitOne( )
Mutex Sample
从执行结果可以很清楚地看到,线程t2,t3的运行是以gM1的释放为条件的,而t4在gM2释放后开始执行,t1则在gM1和gM2都被释放了之后才执行。Main()函数最后,使用WaitHandle等待所有的AutoResetEvent对象的信号,这些对象的信号代表相应线程的结束。
Asp.net(c#)实现多线程断点续传
Posted on 2004-08-10 16:50bestcomy 阅读(32759)评论(51)编辑收藏
以前一直错误的认为在ASP.NET中无法通过编程方式实现多线程断点续传,今天终于获得了这样一个解决方案,让我明白要学习的东西还很多
此解决方案基于其它解决方案及相关资料,根据我自己的理解改进.如有错漏,请尽管指出;如有其它更好的解决方案,请推荐一下,感谢先。
System.IO.Stream iStream = null;

            // Buffer to read 10K bytes in chunk:
            byte[] buffer = new Byte[10240];

            // Length of the file:
            int length;

            // Total bytes to read:
            long dataToRead;

            // Identify the file to download including its path.
            string filepath  = @"E:\software\SQL Server 2000 Personal Edition.ISO";

            // Identify the file name.
            string  filename  = System.IO.Path.GetFileName(filepath);

            try
            {
                // Open the file.
                iStream = new System.IO.FileStream(filepath, System.IO.FileMode.Open,
                    System.IO.FileAccess.Read,System.IO.FileShare.Read);
                Response.Clear();

                // Total bytes to read:
                dataToRead = iStream.Length;

                long p = 0;
                if(Request.Headers["Range"]!=null)
                {
                    Response.StatusCode = 206;
                    p = long.Parse( Request.Headers["Range"].Replace("bytes=","").Replace("-",""));
                }
                if(p != 0)
                {
                    Response.AddHeader("Content-Range","bytes " + p.ToString() + "-" + ((long)(dataToRead - 1)).ToString() + "/" + dataToRead.ToString());
                }
                Response.AddHeader("Content-Length",((long)(dataToRead-p)).ToString());
                Response.ContentType = "application/octet-stream";
                Response.AddHeader("Content-Disposition", "attachment; filename=" + System.Web.HttpUtility.UrlEncode(Request.ContentEncoding.GetBytes(filename)));

                iStream.Position = p;
                dataToRead = dataToRead - p;
                // Read the bytes.
                while (dataToRead > 0)
                {
                    // Verify that the client is connected.
                    if (Response.IsClientConnected)
                    {
                        // Read the data in buffer.
                        length = iStream.Read(buffer, 0, 10240);

                        // Write the data to the current output stream.
                        Response.OutputStream.Write(buffer, 0, length);

                        // Flush the data to the HTML output.
                        Response.Flush();

                        buffer= new Byte[10240];
                        dataToRead = dataToRead - length;
                    }
                    else
                    {
                        //prevent infinite loop if user disconnects
                        dataToRead = -1;
                    }
                }
            }
            catch (Exception ex)
            {
                // Trap the error, if any.
                Response.Write("Error : " + ex.Message);
            }
            finally
            {
                if (iStream != null)
                {
                    //Close the file.
                    iStream.Close();
                }
Response.End();
            }
本解决方案所参考的资料链接列表(在此对作者表示感谢):
http://blog.csdn.net/playyuer/archive/2004/08/02/58430.aspx
http://www.httpsniffer.com/http/1416.htm
http://support.microsoft.com/default.aspx?scid=kb;en-us;812406&Product=aspnet
更新:
此解决方案已根据 提出的问题作了更新,另外在输出前调用了Response.Clear();方法。
闲话多线程的创建
2010-07-21 05:27 by 圣殿骑士, 7519 visits,收藏,编辑一,摘要
圣殿骑士首先向大家说声对不起,由于最近身体不适,同时也因为这些天一直在研究微软的云计算平台Windows Azure(公司项目需要),所以暂停了更新WPF 基础到企业应用系列索引,不过经过这几天的调节,尤其是到海边去晒了晒太阳,现在又开始继续发文了,大家有兴趣也可以去看看漂亮的大海图片工作之余的闲暇,今天这篇文章不是专业谈多线程,只是应一些朋友的要求对上篇文章WPF 基础到企业应用系列4——WPF千年轮回进行一些额外的补充,如果有时间,可以单独写一个专题来详细深入多线程的应用,当然由于自己才疏学浅,但渴求对自己知识的纠正和提高,所以发布出来。如有不对的地方,也希望大家多多海涵!
二,提纲
一,摘要
二,提纲
三,基本概念
四,多线程实践
五,总结
三,基本概念
什么是进程?
“进程”是操作系统的最基本的,也是最重要的概念之一。简单来说一个进程就是你正在执行的应用程序,一个进程里面包括一个或多个线程。系统中的一个进程肯定对应着一个应用程序,但同一个应用程序可以有多个进程。所以我们要清楚,进程和程序是相关联的,但并不是同一个概念。即应用程序被加载到内存中后叫进程。
什么是线程?
线程简单来说就是程序中的一个执行流,每个线程都有自己的专有寄存器同时代码区是共享的,即不同的线程可以执行同样的函数和访问同样的变量。 即进程被CPU处理时叫线程。
什么是多线程?
多线程简单的说就是在一个程序中包含多个程序流,可以把一个复杂的操作分成多个细节操作,这些细节操作可以并行的执行,从而节约时间和提高效率。
多线程优点:
线程可以有以下一些好处:可以提高CPU的利用率。在一个多线程程序中,一个线程处于等待的时候,CPU可以运行其它的线程来处理,这样就节约了时间和提高了程序的效率,同时也提高了用户的体验。
多线程缺点:
1,线程越多,内存占用越大;
2,多线程的运行需要互相协调和统一管理,CPU会额外跟踪线程;
3,线程之间对共享资源的访问会相互影响,必须解决竞用共享资源的种种问题;
4,线程太多会导致控制的复杂度增加,会引发不必要的Bug;
5,在32位的操作系统和64位的操作系统执行的线程、版本不同的操作系统之间执行的线程等都有所差异,执行顺序也有差异。
重要概念(本篇不重点讲解)
Start():启动线程;
Sleep(int):暂停当前线程指定的毫秒数;
Abort():通常使用该方法来终止一个线程,但容易出错;
Suspend():挂起线程,需要时可以恢复;
Resume():恢复被Suspend()方法挂起的线程;
程的优先级可以定义为ThreadPriority枚举的值,即Highest、AboveNormal、Normal、BelowNormal和 Lowest;
创建线程可以用如下三种方式:Thread、ThreadPool、Timer;
.NET Framework内置提供了三种Timer:System.Windows.Forms.Timer、System.Timers.Timer和System.Threading.Timer;
线程同步lock,Monitor,同步事件EventWaitHandler,互斥体Mutex、线程池等的使用;
四,多线程实践
在本文中我们会通过11个小Demo来讲解一下多线程的实践,讲得不是很全面,只是希望给大家一个参考。由于比较简单,所以我就不添加累赘的文字介绍,这样大家看起来也比较舒畅。我会在文章后面附上代码,大家可以下载进行查看和调试。
这个11个方法都通过Form1_Load调用,如下面的代码和图片:
private void Form1_Load(object sender, EventArgs e)
{
DoWithEasy();
DoWithParameter();
DoWithTimer();
DoWithThreadPool();
DoWithThreadPoolParameter();
DoWithAnonymous();
DoWithLambda();
DoWithCommon();
DoWithAction();
DoWithFunc();
DoWithPredicate();
}


创建一个简单的线程:
private void DoWithEasy()
{
Thread t = new Thread(new ThreadStart(this.DoSomethingWithEasy));
t.Start();
}
private void DoSomethingWithEasy()
{
MessageBox.Show("Knights Warrior");
}
创建一个带参数的线程:
private void DoWithParameter()
{
Thread t = new Thread(new ParameterizedThreadStart(this.DoSomethingWithParameter));
t.Start("Knights Warrior");
}
private void DoSomethingWithParameter(object x)
{
MessageBox.Show(x.ToString());
}
使用Timer创建线程:
public void DoWithTimer()
{
System.Windows.Forms.Timer timer = new System.Windows.Forms.Timer();
timer.Interval = 1000;
timer.Tick += (x, y) =>
{
MessageBox.Show("Knights Warrior");
};
timer.Start();
}
通过ThreadPool创建无参线程:
private void DoWithThreadPool()
{
ThreadPool.QueueUserWorkItem(new WaitCallback(this.DoSomethingWithThreadPoolNO));
}
private void DoSomethingWithThreadPoolNO(object x)
{
MessageBox.Show("Knights Warrior");
}
通过ThreadPool创建有参线程:
private void DoWithThreadPoolParameter()
{
ThreadPool.QueueUserWorkItem(new WaitCallback(this.DoSomethingWithThreadPoolParameter), "Knights Warrior");
}
private void DoSomethingWithThreadPoolParameter(object x)
{
MessageBox.Show(x.ToString());
}
通过匿名委托方式创建线程:
private void DoWithAnonymous()
{
ThreadPool.QueueUserWorkItem(new WaitCallback(delegate(object x)
{
MessageBox.Show("Knights Warrior");
}));
}
通过lambda的方式创建线程:
private void DoWithLambda()
{
ThreadPool.QueueUserWorkItem(new WaitCallback(x =>
{
MessageBox.Show("Knights Warrior");
}));
}
线程更新UI(自定义委托的方式):
private void DoWithCommon()
{
WaitCallback waitCallBack = new WaitCallback(this.InvokeMethod);
ThreadPool.QueueUserWorkItem(waitCallBack, "Knights Warrior");
}
private delegate void InvokeMethodDelegate(string name);
private void InvokeMethod(object x)
{
this.Invoke(new InvokeMethodDelegate(this.ChangeUIWithCommon), x.ToString());
}
private void ChangeUIWithCommon(string name)
{
this.lblMessage.Text = name;
}

线程更新UI(通过Action委托)
private void DoWithAction()
{
WaitCallback waitCallback = new WaitCallback(this.DoSomethingWithAction);
ThreadPool.QueueUserWorkItem(waitCallback, "Knights Warrior");
}
private void DoSomethingWithAction(object x)
{
this.Invoke(new Action(this.ChangeUI), x.ToString());
}
private void ChangeUI(string message)
{
this.lblMessage.Text = message;
}
线程更新UI(通过Func委托)
private void DoWithFunc()
{
WaitCallback waitCallback = new WaitCallback(this.DoSomethingWithFunc);
ThreadPool.QueueUserWorkItem(waitCallback, "Knights Warrior");
}
private void DoSomethingWithFunc(object x)
{
Func f = new Func(this.GetFuncMessage);
object result = this.Invoke(f, x.ToString());
MessageBox.Show(result.ToString());
}
private int GetFuncMessage(string message)
{
this.lblMessage.Text = message;
if (message == "Knights Warrior")
{
return 1;
}
else
{
return 0;
}
}

线程更新UI(通过Predicate委托)
private void DoWithPredicate()
{
WaitCallback waitCallback = new WaitCallback(this.DoSomethingWithPredicate);
ThreadPool.QueueUserWorkItem(waitCallback, "Knights Warrior");
}
private void DoSomethingWithPredicate(object x)
{
Predicate pd = new Predicate(this.GetPredicateMessage);
object result = this.Invoke(pd, x);
MessageBox.Show(result.ToString());
}
private bool GetPredicateMessage(string message)
{
this.lblMessage.Text = message;
if (message == "Knights Warrior")
{
return true;
}
else
{
return false;
}
}

概念注解:
Predicate 委托
定义:public delegate bool Predicate(T obj);
表示定义一组条件并确定指定对象是否符合这些条件的方法。这个委托经常由 Array 和 List 类的几种方法使用,用于在集合中检索元素。
Func 委托
定义:public delegate TResult Func(T arg);
代码 delegate TResult Func();
delegate TResult Func(T1 arg1);
delegate TResult Func(T1 arg1, T2 arg2);
delegate TResult Func(T1 arg1, T2 arg2, T3 arg3);
delegate TResult FuncT1 arg1, T2 arg2, T3 arg3, T4 arg4);
Func():封装一个不具有参数并返回 TResult 的类型值的方法。
Func 封装一个具有一个参数并返回 TResult 的类型值的方法。
Action 委托
定义:public delegate void Action(T obj);  代码 delegate void Action(T1 arg1);
delegate void Action(T1 arg1, T2 arg2);
delegate void ActionT1 arg1, T2 arg2, T3 arg3);
delegate void ActionT1 arg1, T2 arg2, T3 arg3, T4 arg4);
Action:封装一个带有两个参数并且无返回值的方法。
这三个委托经常会用到,区分也很简单:
Predicate接受一个T的参数,返回一个bool值,可以用Func实现此功能;
Func接受1到4个参数,返回一个值;
Action接受1到4个参数,无返回值;
五,总结
这篇文章并没有什么深度和难度,只是对多线程进行了一下小结,如果大家想了解更多,我会单独详细写一些多线程相关的文章,当然由于本人知识有限,文中错误之处也敬请海涵!下一篇开始我们将继续更新WPF 基础到企业应用系列索引系列文章,如果有感兴趣的同仁,敬请关注!
[原创] Windows Forms 实现安全的多线程详解(附带程序代码示例)
前言
在我们应用程序开发过程中,经常会遇到一些问题,需要使用多线程技术来加以解决。本文就是通过几个示例程序给大家讲解一下多线程相关的一些主要问题。
执行长任务操作
许多种类的应用程序都需要长时间操作,比如:执行一个打印任务,请求一个 Web Service 调用等。用户在这种情况下一般会去转移做其他事情来等待任务的完成,同时还希望随时可以监控任务的执行进度。

下面的代码片断示例了当长任务执行时用户界面是如何被更新的。
// 显示进度条
void ShowProgress( int totalStep, int currentStep )
{
_Progress.Maximum = totalStep;
_Progress.Value = currentStep;
}
// 执行任务
void RunTask( int seconds )
{
// 每 1 / 4 秒 显示进度一次
for( int i = 0; i < seconds * 4; i++ )
{
Thread.Sleep( 250 );
// 显示进度条
ShowProgress( seconds * 4, i + 1 );
}
}
private void _btnRun_Click( object sender, System.EventArgs e )
{
RunTask( Convert.ToInt32( _txtSecond.Value ) );
}
当我们运行上面的程序,在整个长任务的过程中,没有出现任何问题。这样就真的没有问题了吗?当我们切换应用程序去做其他事情后再切换回来,问题就发生了!主窗体就会出现如下情况:

这个问题当然会发生,因为我们现在的应用程序是单线程的,因此,当线程执行长任务时,它同时也就不能重画用户界面了。
为什么在我们切换应用程序后,问题才发生呢?这是因为当你切换当前应用程序到后台再切换回前台时,我们需要重画整个用户界面。但是应用程序正在执行长任务,根本没有时间处理用户界面的重画,问题就会发生。
如何解决问题呢?我们需要将长任务放在后台运行,把用户界面线程解放出来,因此我们需要另外一个线程。
线程异步操作
我们上面程序中执行按钮的Click 处理如下:
private void _btnRun_Click( object sender, System.EventArgs e )
{
RunTask( Convert.ToInt32( _txtSecond.Value ) );
}
回想上面刚才问题发生的原因,直到 RunTask 执行完成后返回,Click 处理函数始终不能够返回,这就意味着用户界面不能处理重画事件或其他任何事件。一个解决方法就是创建另外一个线程,代码片断如下:
using System.Threading;
private int _seconds;
// 执行任务工作线程进入点
void RunTaskThreadStart()
{
RunTask( _seconds );
}
// 通过创建工作线程消除用户界面线程的阻塞问题
private void _btnRun_Click( object sender, System.EventArgs e )
{
_seconds = Convert.ToInt32( _txtSecond.Value );
Thread runTaskThread = new Thread( new ThreadStart( RunTaskThreadStart ) );
runTaskThread.Start();
}
现在,我们不再需要等待 RunTask 执行完成才能够从 Click 事件返回,我们创建了新的工作线程并让它开始工作、运行。

runTaskThread.Start(); 将我们新创建的工作线程调度执行并立即返回,允许我们的用户界面线程重新获得控制权执行它自己的工作。现在如果用户再切换应用程序,因为工作线程在自己的空间执行长任务,用户界面线程被解放出来处理包括用户界面重画的各种事件,我们上面遇到的问题就解决了。
委托异步调用
在上面的代码中,我们注意到,我们没有给工作线程进入点(RunTaskThreadStart)传递任何参数,我们采用声明一个窗体类的字段 _seconds 来给工作线程传递参数。在某种应用场合不能够给工作线程直接传递参数也是一件非常痛苦的事情。
如何改进呢?我们可以使用委托来进行异步调用。委托是支持传递参数的。这样,就消除了我们刚才的问题,使我们能够消除额外的字段声明和额外的工作线程函数。
如果你不熟悉委托,你可以简单的把它理解为安全的函数指针。采用了委托异步调用,代码片断如下:
// 执行任务的委托声明
delegate void RunTaskDelegate( int seconds );
// 通过创建委托解决传递参数问题
private void _btnRun_Click( object sender, System.EventArgs e )
{
RunTaskDelegate runTask = new RunTaskDelegate( RunTask );
// 委托同步调用方式
runTask( Convert.ToInt16( _txtSecond.Value ) );
}
//通过创建委托解决传递参数问题,通过委托的异步调用消除用户界面线程的阻塞问题
private void _btnRun_Click( object sender, System.EventArgs e )
{
RunTaskDelegate runTask = new RunTaskDelegate( RunTask );
// 委托异步调用方式
runTask.BeginInvoke( Convert.ToInt16( _txtSecond.Value ), null, null );
}
多线程安全
到这里为止,我们已经解决了长任务的难题和传递参数的困扰。但是我们真的解决了全部问题吗?回答是否定的。
我们知道 Windows 编程中有一个必须遵守的原则,那就是在一个窗体创建线程之外的任何线程中都不允许操作窗体。
我们上面的程序就是存在这样的问题:工作线程是在 ShowProgress 方法中修改了用户界面的进度条的属性。那为什么程序运行没有出现问题,运行正常呢?
没有发生问题是因为是现在的Windows XP操作系统对这类问题有非常健壮的解决方法,让我们避免了问题的发生。但是我们现在的程序不能保证在其他的操作系统能够运行正常!
真正的解决方法是我们能够认识到问题所在,并在程序中加以避免。

如何避免多线程的窗体资源访问的安全问题呢?其实非常简单,有两种方法:
一种方法就是不管线程是否是用户界面线程,对用户界面资源的访问统一由委托完成;
另一种方法是在每个 Windows Forms 用户界面类中都有一个 InvokeRequired 属性,它用来标识当前线程是否能够直接访问窗体资源。我们只需要检查这个属性的值,只有当允许直接访问窗体资源时才直接访问相应的资源,否则,就需要通过委托进行访问了。
采用第一种安全的方法的代码片断如下:
// 显示进度条的委托声明
delegate void ShowProgressDelegate( int totalStep, int currentStep );
// 显示进度条
void ShowProgress( int totalStep, int currentStep )
{
_Progress.Maximum = totalStep;
_Progress.Value = currentStep;
}
// 执行任务的委托声明
delegate void RunTaskDelegate( int seconds );
// 执行任务
void RunTask( int seconds )
{
ShowProgressDelegate showProgress = new ShowProgressDelegate( ShowProgress );
// 每 1 / 4 秒 显示进度一次
for( int i = 0; i < seconds * 4; i++ )
{
Thread.Sleep( 250 );
// 显示进度条
this.Invoke( showProgress, new object[] { seconds * 4, i + 1 } );
}
}
采用第二种安全的方法的代码片断如下:
// 显示进度条的委托声明
delegate void ShowProgressDelegate( int totalStep, int currentStep );
// 显示进度条
void ShowProgress( int totalStep, int currentStep )
{
if( _Progress.InvokeRequired )
{
ShowProgressDelegate showProgress = new ShowProgressDelegate( ShowProgress );
// 为了避免工作线程被阻塞,采用异步调用委托
this.BeginInvoke( showProgress, new object[] { totalStep, currentStep } );
}
else
{
_Progress.Maximum = totalStep;
_Progress.Value = currentStep;
}
}
// 执行任务的委托声明
delegate void RunTaskDelegate( int seconds );
// 执行任务
void RunTask( int seconds )
{
// 每 1 / 4 秒 显示进度一次
for( int i = 0; i < seconds * 4; i++ )
{
Thread.Sleep( 250 );
// 显示进度条
ShowProgress( seconds * 4, i + 1 );
}
}
至此,我们用了几个示例说明了如何执行长任务、如何通过多线程异步处理任务进度的显示并解决了多线程的安全性等问题。希望能够给大家对理解多线程编程、委托的使用、异步调用等方面提供一些帮助,也希望能和大家进行进一步的沟通和交流。
示例程序包下载地址:
---安全多线程示例程序包
.Net多线程总结(一)
.Net提供了许多多线程编程工具,可能是因为太多了,所以掌握起来总是有一些头疼,我在这里讲讲我总结的一些多线程编程的经验,希望对大家有帮助
不需要传递参数,也不需要返回参数
我们知道启动一个线程最直观的办法是使用Thread类,具体步骤如下
ThreadStart threadStart=new ThreadStart(Calculate);
Thread thread=new Thread(threadStart);
thread.Start();
public void Calculate(){
double Diameter=0.5;
Console.Write("The perimeter Of Circle with a Diameter of {0} is {1}"Diameter,Diameter*Math.PI);
}
例1
上面我们用定义了一个ThreadStart类型的委托,这个委托制定了线程需要执行的方法:Calculate,在这个方法里计算了一个直径为0.5的圆的周长,并输出.这就构成了最简单的多线程的例子,在很多情况下这就够用了,然后ThreadStart这个委托定义为void ThreadStart(),也就是说,所执行的方法不能有参数,这显然是个很大的不足,为了弥补这个缺陷,聪明的程序员想出了许多好的方法,我们将在需要传递多个参数一节中进行介绍,这里我们先介绍.Net为了解决这个问题而设定的另外一个委托:就是ParameterizedThreadStart ,我会在下面详细讲述
需要传递单个参数
ParameterThreadStart的定义为void ParameterizedThreadStart(object state)使用这个这个委托定义的线程的启动函数可以接受一个输入参数,具体例子如下
ParameterizedThreadStart threadStart=new ParameterizedThreadStart(Calculate)
Thread thread=new Thread()
thread.Start(0.9);
public void Calculate(object arg){
double Diameter=double(arg);
Console.Write("The perimeter Of Circle with a Diameter of {0} is {1}"Diameter,Diameter*Math.PI);
}
例2
Calculate方法有一个为object类型的参数,虽然只有一个参数,而且还是object类型的,使用的时候尚需要类型转换,但是好在可以有参数了,并且通过把多个参数组合到一个类中,然后把这个类的实例作为参数传递,就可以实现多个参数传递
需要传递多个参数
虽然通过把需要的参数包装到一个类中,委托ParameterizedThreadStart就可以传递多个参数,但是由于这个委托的传入参数是object,所以不可避免的需要进行参数转换,下面还有几个常用的参数传递方法,让我们来一一看来
使用专门的线程类
这是许多程序员爱使用的经典模式,简单来说,就是把需要另起线程执行的方法,和他需要的参数放到一个类中,参数作为了类的属性,调用时声明此类的实例,然后初始化属性,方法执行时直接使用类里初始化好的属性来执行,这样方法本身就可以不需要参数,而又起到了多参数传递的效果,于是使用本文最开始提到的不带参数的ThreadStart委托就可以了,并且由于需要执行的方法和参数都放在一个类中,充分体现了面向对象的特点.具体方法如下
还是计算面积的方法的例子,我们把这个方法用一个类包装起来,输入参数Diameter(直径)是这个类的一个字段
public class MyThread
{
public double Diameter=10;
public double Result=0;
public MyThread(int Diameter)
{
this.Diameter = Diameter;
}
public void Calculate()
{
Console.WriteLine( "Calculate Start");
Thread.Sleep(2000);
Result = Diameter*Math.PI;;
Console.WriteLine("Calculate End, Diameter is {0},Result is {1}" ,this.Diameter, Result);
}
}
MyThread t=new MyThread(5.0);
ThreadStart threadStart=new ThreadStart(t.Calculate)
Thread thread=new Thread(threadStart);
thread.Start();
例3
这种方法把参数传递变成了属性共享,想传递多少个变量都可以,从封装上讲,把逻辑和逻辑涉及的数据封装在一起,也很不错,这个方法还有一个聪明的变体,利用了匿名方法,这种变体连独立的类都省掉了,我现在给出这个方法
double Diameter = 6;
double Result=0;
Thread ta = new Thread(new ThreadStart(delegate()
{
Thread.Sleep(2000);
Result=Diameter * Math.PI;
Console.WriteLine("匿名 Calculate End, Diameter is {0},Result is {1}", Diameter, Result); ;
}));
ta.Start();
例4
这个方法和上例道理相同,都是把参数传递变成了对变量的调用,从而取消了参数传递,但是,后者充分利用了匿名方法的一个性质,就是可以直接使用当前上下文的局部变量,比如委托中的Diameter,和Result.当然,这样做的缺点是如果匿名方法太长,程序的可读性会降低,所以一般很少有人这样做,这里给出这个方法供大家参考,关于匿名委托的资料可以参见
聪明的读者肯定想,既然可以用字段来传入变量,当然也可以用字段传出变量,比如在上面两个例子里我们看到计算结果都写进了一个叫Result(加亮的地方)的变量里,我们直接访问这个变量不就可以得到计算结果了吗?
这样做有一个致命的问题:既然是异步执行,主线程怎么知道分线程什么时候完成了计算呢?比如上两个例子中,我们的线程都睡眠了2000毫秒,然后才进行计算,那么如果主线程在没有完成计算前访问Result,只能得到一个0值.于是我们就有了下面的一系列解决方法.
需要传递参数且需要返回参数
刚才说到主线程需要知道子线程什么时候执行完成,可以使用Thread.ThreadState枚举来判断
当线程的ThreadState==ThreadState.Stop时,一般就说明线程完成了工作,这时结果就可用了,如果不是这个状态,就继续执行别的工作,或者等待一会,然后再尝试.倘若需要等有多个子线程需的返回,并且需要用他们的结果来进行进异步计算,那就叫做线程同步了,下面我们介绍另外一种我比较推荐的方法,能够自定义参数个数,并且返回数据,而且使用起来也相对方便
使用委托的异步调用方法和回调
首先我们要把需要异步调用的方法定义为一个委托,然后利用BeginInvoke来异步调用,BeginInvoke的第一个参数就是直径,第二个是当线程执行完毕后的调用的方法
delegate double CalculateMethod(double Diameter);
static CalculateMethod calcMethod;
double result = 0;
static void Main(string[] args)
{
calcMethod = new CalculateMethod(Calculate);
calcMethod.BeginInvoke(5, new AsyncCallback(TaskFinished), null);
}
/// ///线程调用的函数
/// public static double Calculate(double Diameter)
{
return Diameter * Math.PI;
}
/// ///线程完成之后回调的函数
/// public static void TaskFinished(IAsyncResult result)
{
result=calcMethod.EndInvoke(result);
}
例5
注意,再线程执行完毕后执行的方法TaskFinished中,我们使用了EndInvoke来取得这个函数的返回值
线程池
线程虽然是个好东西,但是也是个资源消耗大户,许多时候,我们需要用多线程,但是又不希望线程的数量过多,这就是线程池的作用,.Net为我们提供了现成的线程池ThreadPool,他的使用如下
WaitCallback w = new WaitCallback(Calculate);
ThreadPool.QueueUserWorkItem(w, 1.0);
ThreadPool.QueueUserWorkItem(w, 2.0);
ThreadPool.QueueUserWorkItem(w, 3.0);
ThreadPool.QueueUserWorkItem(w, 4.0);
public static void Calculate(double Diameter)
{
return Diameter * Math.PI;
}
例6
首先定义一个WaitCallback委托,WaitCallback的格式是void WaitCallback(object state),也就是说你的方法必须符合这个格式,接着调用QueueUserWorkItem,将这个任务加入线程池,当县城池有空闲线时,将会调度并运行你的代码
每一个进程都有一个线程池,线程池的默认大小是25,我们可以通过SetMaxThreads方法来设置其最大值.
[注]由于每个进程只有一个线程池,所以如果是在iis进程,或者sqlserver的进程中使用线程池,并且需要设置线程池的最大容量的话,会影响到iis进程或sql进程,所以这两种情况下要特别小心
控制权
在和大家交谈的时候我发现凡是习惯了面向对象思维的同事,总是对多线程情况下的执行上下文很困扰,比如例5中,主程序启动了子线程执行Calculate方法,执行完毕后回调TaskFinished,假如主线程id是1,子线程id是2,那么Calculate肯定是在id=2的线程中执行,那么他的回调函数TaskFinished呢? 同样也是在id=2的线程上下文中执行,不信你输出线程id试试,这通常不是什么问题,但是当我们需要在Winform编程中使用子线程时,就有可能会引起问题了,我们将在下面讲这个问题
窗体程序多线程编程的特殊性
当我们把例5的回调代码稍加修改,搬到winform里面,就可以看到问题所在了
public static void TaskFinished(IAsyncResult result)
{
result=calcMethod.EndInvoke(result);
this.TextBox1.Text=result;
}
程序的原意是在线程执行完毕后讲结果写入一个TextBox,然而当程序执行到this.TextBox1.Text=result这里的时候就抱错了.原来WinForm对线程有很严格的要求,除了创建这些控件的线程,其他线程想跨线程访问WinForm上的控件的属性和方法是不允许(除了几个特殊属性),在有的版本系统上,比如XP,对这个问题进行了处理,跨线程控件访问可以被执行,但是大多数windows系统都是不可以的,那么如果我们确实需要跨线程修改控件属性,或者调用控件的方法,就必须用到控件的一个方法Invoke,这个方法可以把执行上下文切换回创建这些控件的线程,具体操作如下
delegate void changeText(string result);
public static void TaskFinished(IAsyncResult result)
{
result=calcMethod.EndInvoke(result);
this.BeginInvoke(new changeText(this.textBox1.AppendText),t.Result.ToString())
}
由于委托中必须使用方法,所以我用AppendTex方法t,而不是直接设置Text属性,你如果想设置text属性,就必须自己包装一个方法,然后连接到委托了
c#中使用多线程访问winform中控件的若干问题
我们在做winform应用的时候,大部分情况下都会碰到使用多线程控制界面上控件信息的问题。然而我们并不能用传统方法来做这个问题,下面我将详细的介绍。
首先来看传统方法:
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
Thread thread = new Thread(ThreadFuntion);
thread.IsBackground = true;
thread.Start();
}
private void ThreadFuntion()
{
while (true)
{
this.textBox1.Text = DateTime.Now.ToString();
Thread.Sleep(1000);
}
}
}
运行这段代码,我们会看到系统抛出一个异常:Cross-thread operation not valid:Control 'textBox1' accessed from a thread other than the thread it was created on . 这是因为.net 2.0以后加强了安全机制,不允许在winform中直接跨线程访问控件的属性。那么怎么解决这个问题呢,下面提供几种方案。
第一种方案,我们在Form1_Load()方法中加一句代码:
private void Form1_Load(object sender, EventArgs e)
{
Control.CheckForIllegalCrossThreadCalls = false;
Thread thread = new Thread(ThreadFuntion);
thread.IsBackground = true;
thread.Start();
}
加入这句代码以后发现程序可以正常运行了。这句代码就是说在这个类中我们不检查跨线程的调用是否合法(如果没有加这句话运行也没有异常,那么说明系统以及默认的采用了不检查的方式)。然而,这种方法不可取。我们查看CheckForIllegalCrossThreadCalls 这个属性的定义,就会发现它是一个static的,也就是说无论我们在项目的什么地方修改了这个值,他就会在全局起作用。而且像这种跨线程访问是否存在异常,我们通常都会去检查。如果项目中其他人修改了这个属性,那么我们的方案就失败了,我们要采取另外的方案。
下面来看第二种方案,就是使用delegate和invoke来从其他线程中控制控件信息。网上有很多人写了这种控制方式,然而我看了很多这种帖子,表明上看来是没有什么问题的,但是实际上并没有解决这个问题,首先来看网络上的那种不完善的方式:
public partial class Form1 : Form
{
private delegate void FlushClient();//代理
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
Thread thread = new Thread(CrossThreadFlush);
thread.IsBackground=true;
thread.Start();
}
private void CrossThreadFlush()
{
//将代理绑定到方法
FlushClient fc = new FlushClient(ThreadFuntion);
this.BeginInvoke(fc);//调用代理
}
private void ThreadFuntion()
{
while (true)
{
this.textBox1.Text = DateTime.Now.ToString();
Thread.Sleep(1000);
}
}
}
使用这种方式我们可以看到跨线程访问的异常没有了。但是新问题出现了,界面没有响应了。为什么会出现这个问题,我们只是让新开的线程无限循环刷新,理论上应该不会对主线程产生影响的。其实不然,这种方式其实相当于把这个新开的线程“注入”到了主控制线程中,它取得了主线程的控制。只要这个线程不返回,那么主线程将永远都无法响应。就算新开的线程中不使用无限循环,使可以返回了。这种方式的使用多线程也失去了它本来的意义。
现在来让我们看看推荐的解决方案:
public partial class Form1 : Form
{
private delegate void FlushClient();//代理
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
Thread thread = new Thread(CrossThreadFlush);
thread.IsBackground = true;
thread.Start();
}
private void CrossThreadFlush()
{
while (true)
{
//将sleep和无限循环放在等待异步的外面
Thread.Sleep(1000);
ThreadFunction();
}
}
private void ThreadFunction()
{
if (this.textBox1.InvokeRequired)//等待异步
{
FlushClient fc = new FlushClient(ThreadFunction);
this.Invoke(fc);//通过代理调用刷新方法
}
else
{
this.textBox1.Text = DateTime.Now.ToString();
}
}
}
运行上述代码,我们可以看到问题已经被解决了,通过等待异步,我们就不会总是持有主线程的控制,这样就可以在不发生跨线程调用异常的情况下完成多线程对winform多线程控件的控制了。
对于深山老林提出的问题,我最近找到了更优的解决方案,利用了delegate的异步调用,大家可以看看:
public partial class Form1 : Form
{
private delegate void FlushClient();//代理
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
Thread thread = new Thread(CrossThreadFlush);
thread.IsBackground = true;
thread.Start();
}
private void CrossThreadFlush()
{
FlushClient fc=new FlushClient(ThreadFunction);
fc.BeginInvoke(null,null);
}
private void ThreadFunction()
{
while (true)
{
this.textBox1.Text = DateTime.Now.ToString();
Thread.Sleep(1000);
}
}
}
这种方法也可以直接简化为(因为delegate的异步就是开了一个异步线程):
public partial class Form1 : Form
{
private delegate void FlushClient();//代理
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
FlushClient fc=new FlushClient(ThreadFunction);
fc.BeginInvoke(null,null);
}
private void ThreadFunction()
{
while (true)
{
this.textBox1.Text = DateTime.Now.ToString();
Thread.Sleep(1000);
}
}
}
使用匿名委托,Lambda简化多线程代码
.net中的线程也接触不少了。在多线程中最常见的应用莫过于有一个耗时的操作需要放到线程中去操作,而在这个线程中我们需要更新UI,这个时候就要创建一个委托了来更新UI了,不然会报错的。下面我们就来设计一个简单的场景:窗体上有一个按钮和进度条,按钮按下后启动一个线程让进度条滚动。需要说明一下的是,我们这里不讨论使用匿名委托,lambda的好坏,我们只有一个目标就是使得我们的程序:短点,短点,再短点。
最“朴素”写法
我刚开始没有掌握匿名方法这些就是按照下面这些写的,说实话很痛苦。后来接触了匿名表达式,lambda后几乎都不想再想写这样的东西了,除非特殊的一些情况,比如需要自己定义委托。如果您现在还在按照下面这样写,那么这篇文章对你或许有些帮助!
//声明一个委托 delegate void UpdateProgressDelegate(); //声明一个UpdateProgressDelegate的委托实例 private UpdateProgressDelegate UpdateProgressHandle; public Form1() { InitializeComponent(); progressBar1.Maximum = 100; progressBar1.Minimum = 0; progressBar1.Value = 0; //将该委托实例和UpdateProgressValue方法绑定起来 UpdateProgressHandle = new UpdateProgressDelegate(UpdateProgressValue); } private void button1_Click(object sender, EventArgs e) { Thread t = new Thread(new ThreadStart(D)); t.Start(); } private void D() { //其他事情 //.......... progressBar1.Invoke(UpdateProgressHandle); //调用Invoke更新进度条,参数是我们新建的委托 } //更新进度条的方法 private void UpdateProgressValue() { for (int i = 0; i < 50; i++) { progressBar1.Value = i; } } 去掉委托创建
这个写法基本就是.net 1.x里面的委托写法了。您也看出来,很繁琐。像一个多线程的界面中,要和多线程打交道的控件何止一个两个。几个一来就会感觉很烦了。维护起来也不方便。下面我们使用.net 2.0中的一个新特性:支持省略委托的创建,直接将方法名字赋给需要的参数。即我们可以将
Thread t = new Thread(new ThreadStart(D));
改为:
Thread t = new Thread(D);
虽然只是少了一点,不过好歹也是个进步对吧。
去掉自定义方法
现在我们引入.net 2.0中的匿名委托来改善下上面这个程序,使其看起来更加简洁点。怎样使用匿名委托?教你个简单的方法,程序中参数是方法名字的地方您都可以通过delegate(){//操作}的形式来代换。比如下面我们就像D方法名那里给替换掉。
//声明一个委托 delegate void UpdateProgressDelegate(); //声明一个UpdateProgressDelegate的委托实例 private UpdateProgressDelegate UpdateProgressHandle; public Form1() { InitializeComponent(); progressBar1.Maximum = 100; progressBar1.Minimum = 0; progressBar1.Value = 0; //将该委托实例和UpdateProgressValue方法绑定起来 UpdateProgressHandle = new UpdateProgressDelegate(UpdateProgressValue); } private void button1_Click(object sender, EventArgs e) { Thread t = new Thread(delegate() { progressBar1.Invoke(UpdateProgressHandle); }); t.Start(); } //更新进度条的方法 private void UpdateProgressValue() { for (int i = 0; i < 50; i++) { progressBar1.Value = i; } }
我们将D方法删除,将Thread线程声明的时候直接改为使用匿名委托来定义需要执行的操作。怎么样?整整少了一个方法的定义,同时操作更加接近使用的地方了。不过我还是觉得太多了,还能去掉一点吗?当然能。
去掉自定义委托
我们在上面我们定义的UpdateProgressDelegate委托上下功夫。能不能直接不用声明就可以使用呢?这时我们就需要使用Action,Func委托了。这两个是系统自带的委托,利用这两个现成的委托我们可以省去自定义简单委托的步骤。这两个委托的区别在于Action只有参数没有返回值,而Func既有参数也有返回值。里面的T代表了你要执行的方法的参数类型。另外需要注意的是,在.net framework 2.0中只有Action一种形式,在3.5中增加了Action(无参数形式)以及Action等最多四个参数,Func,Func最多四个参数。所以如果您的.net版本是2.0那么意味着您只有Action可以使用。我们现在首先在.net 3.5下用Action委托来简化上面的代码。 形式如下:
public partial class Form1 : Form { public Form1() { InitializeComponent(); progressBar1.Maximum = 100; progressBar1.Minimum = 0; progressBar1.Value = 0; } private void button1_Click(object sender, EventArgs e) { Thread t = new Thread(delegate() { progressBar1.Invoke(new Action(UpdateProgressValue)); }); t.Start(); } //更新进度条的方法 private void UpdateProgressValue() { for (int i = 0; i < 50; i++) { progressBar1.Value = i; } } }
可以看到我们之前声明的那一长段的委托都去掉了,清爽了不少。那么在.net 2中该如何使用呢?两个办法:
1.强制在UpdateProgressValue中加个参数,但我们不使用。代码如下:
代码 public Form1() { InitializeComponent(); progressBar1.Maximum = 100; progressBar1.Minimum = 0; progressBar1.Value = 0; } private void button1_Click(object sender, EventArgs e) { Thread t = new Thread(delegate() { progressBar1.Invoke(new Action(UpdateProgressValue),0); }); t.Start(); } //更新进度条的方法 private void UpdateProgressValue(int x) { for (int i = 0; i < 50; i++) { progressBar1.Value = i; } }
2.不使用Action委托,还记得我们最朴素写法中的ThreadStart这个委托吗?这个就是一个现成的无参数委托,不用白不用!代码如下:
代码 public Form1() { InitializeComponent(); progressBar1.Maximum = 100; progressBar1.Minimum = 0; progressBar1.Value = 0; } private void button1_Click(object sender, EventArgs e) { Thread t = new Thread(delegate() { progressBar1.Invoke(new ThreadStart(UpdateProgressValue)); }); t.Start(); } //更新进度条的方法 private void UpdateProgressValue() { for (int i = 0; i < 50; i++) { progressBar1.Value = i; } }
上面我们已经说过了,有方法名为参数的地方可以使用匿名方法替代,那么上面的那个new Action(UpdateProgressValue)中的UpdateProgressValue我们同样可以再给替换掉了。代码如下:
代码 public Form1() { InitializeComponent(); progressBar1.Maximum = 100; progressBar1.Minimum = 0; progressBar1.Value = 0; } private void button1_Click(object sender, EventArgs e) { Thread t = new Thread(delegate() { progressBar1.Invoke(new Action(delegate() { for (int i = 0; i < 50; i++) { progressBar1.Value = i; } })); }); t.Start(); }
怎么样,这下子就在一个方法里面搞定所有的事情了,这样写起来是不是比你定义许多委托啊什么的爽多了。不过我们的旅程还没有结束,还有减少的空间。
终极简化---使用Lambda
我们最后引入lambda来简化我们的代码。Lambda表达式在C#中的写法是“arg-list => expr-body”,“=>”符号左边为表达式的参数列表,右边则是表达式体(body)。参数列表可以包含0到多个参数,参数之间使用逗号分割。当然因为我们这里没有参数所有可以直接写成()=>{}的形式了啦。lambda用在哪里呢?它可以替换匿名表达式使其更加简单,在LINQ等等查询语句中也有使用,不过不是我们今天讨论的范围。如何替换匿名表达式呢?代码如下:
private void button1_Click(object sender, EventArgs e) { Thread t = new Thread(() => progressBar1.Invoke(new Action(()=> { for (int i = 0; i < 50; i++) { progressBar1.Value = i; } }))); t.Start(); }
可以看到使用了lambda后匿名方法中的一些不需要的{}被省略了,自然看起来也就爽多啦。如果非要总结一个怎么替换的过程的话那就简单的认为将delegate(){}替换为了()=>{},如果有参数类似。
最后需要说的是别看我们上面的代码样子好像变化了不少,其实在编译后编译器会为我们上面省略的一系列代码再加上去的。有兴趣的可以看看简化后的IL和没有简化的IL,其实都是差不多的。好了,这就是我目前能达到的最短代码了。如果您还能再短些欢迎提出!
多线程开发
前不久写了一个项目,其中需要处理大量的数据,这些数据都是记录型的,对这些数据进行处理是一个非常耗时的事情,.net在处理这些使用的过程中,就好像死机一样一动不动。这样给用户的感觉非常的不好。例如如下的例子,我们在一个窗体上放置一个ProgressBar,然后对ProgressBar进行循环添加,例如:
1         private void btnProcess_Click(object sender, EventArgs e)
2         {
3             int nStart = Convert.ToInt32(txtStart.Text);
4             int nEnd = Convert.ToInt32(txtEnd.Text);
5
6             prgProcess.Minimum = nStart;
7             prgProcess.Maximum = nEnd;
8             for (int nValue = nStart; nValue <= nEnd; nValue++)
9             {
10                 lblStatus.Text = "Processing item: " + Convert.ToString(nValue);
11                 prgProcess.Value = nValue;
12             } }
这段代码运行时正常的,但是对于数据量很大的时候,比如txtEnd.Text设置成1000000,这个时候界面上的进度条也许就是个固定值,一动不动,而且lblStatus.Text的现实也是为空,这是很正常的。程序就如同死机一样。当然,我们可以在循环中添加Application.DoEvents();使系统在空闲的时间处理其他的事情,这样系统的效果会好些。例如:
1 private void btnProcess_Click(object sender, EventArgs e)
2         {
3             int nStart = Convert.ToInt32(txtStart.Text);
4             int nEnd = Convert.ToInt32(txtEnd.Text);
5
6             prgProcess.Minimum = nStart;
7             prgProcess.Maximum = nEnd;
8             for (int nValue = nStart; nValue <= nEnd; nValue++)
9             {
10                 lblStatus.Text = "Processing item: " + Convert.ToString(nValue);
11                 prgProcess.Value = nValue;
12
13                 Application.DoEvents();
14             }
15         }
但这些不是本文的重点,本文的重点是使用多线程的的方式去处理这些内容,我们将在此窗体中添加Tread去处理这些复杂的操作。这样的效果会比Application.DoEvents();更好。
还是在原来的窗体上,我们使用线程的方式去处理。
1         delegate void DelegateType(int x);
2         DelegateType TheDelegate;
3
4         int StartFrom, EndTo;
5
6         private void btnStartThreaded_Click(object sender, EventArgs e)
7         {
8             TheDelegate = MessageHandler;
9
10             StartFrom = Convert.ToInt32(txtStart.Text);
11             EndTo = Convert.ToInt32(txtEnd.Text);
12
13             prgProcess.Minimum = StartFrom;
14             prgProcess.Maximum = EndTo;
15
16             btnStartThreaded.Enabled = false;
17
18             Thread MyThread = new Thread(ProcessRoutine);
19             MyThread.Start();
20         }
21
22         void MessageHandler(int nProgress)
23         {
24             lblStatus.Text = "Processing item: " + Convert.ToString(nProgress);
25             prgProcess.Value = nProgress;
26         }
27
28         void ProcessRoutine()
29         {
30             for (int nValue = StartFrom; nValue <= EndTo; nValue++)
31             {
32                 this.Invoke(this.TheDelegate, nValue);
33             }
34         }
这个是使用线程的方法去处理,但是问题还是有的,虽然线程处理了用户界面效果的问题,但是对于大数据量处理过程中,很耗时的,用户可能会去取消自己的操作,所以我们要添加方法去对自己的线程取消。微软这个方面做的很好,它提供了ManualResetEvent的委托类,可以轻松的对线程进行取消。代码如下:
首先添加ManualResetEvent的委托:
ManualResetEvent CancelEvent = new ManualResetEvent(false);
private void btnCancelButton_Click(object sender, EventArgs e)
{
btnCancelButton.Enabled = false;
CancelEvent.Set();
MyThread.Join();
lblStatus.Text = "Cancelled!";
}
修改ProcessRoutine的代码如下:
void ProcessRoutine()
{
for (int nValue = StartFrom; nValue <= EndTo; nValue++)
{
if (CancelEvent.WaitOne(0, false) == true)
{
return;
}
this.BeginInvoke(this.TheDelegate, nValue);
Thread.Sleep(200);
}
}
另外一点,我们还可以对线程进行扩充,就是将线程的暂停和继续,这个就很简单了,就是对线程的Suspend和Resume,只要再添加按钮设置线程的Suspend和Resume就可以了。代码如下:
bool IsThreadPaused;
private void btnPause_Click(object sender, EventArgs e)
{
if (!IsThreadPaused)
{
IsThreadPaused = true;
MyThread.Suspend();
btnPause.Text = "Resume";
lblStatus.Text = "Paused!";
btnCancelButton.Enabled = false;
}
else
{
IsThreadPaused = false;
MyThread.Resume();
btnPause.Text = "Pause";
btnCancelButton.Enabled = true;
}
}
多线程服务器的常用编程模型
多线程服务器的常用编程模型
陈硕 (giantchen_AT_gmail)
Blog.csdn.net/Solstice
2010 Feb 12
本文 PDF 版下载:http://files.cppblog.com/Solstice/multithreaded_server.pdf
本文主要讲我个人在多线程开发方面的一些粗浅经验。总结了一两种常用的线程模型,归纳了进程间通讯与线程同步的最佳实践,以期用简单规范的方式开发多线程程序。
文中的“多线程服务器”是指运行在 Linux 操作系统上的独占式网络应用程序。硬件平台为 Intel x64 系列的多核 CPU,单路或双路 SMP 服务器(每台机器一共拥有四个核或八个核,十几 GB 内存),机器之间用百兆或千兆以太网连接。这大概是目前民用 PC 服务器的主流配置。
本文不涉及 Windows 系统,不涉及人机交互界面(无论命令行或图形);不考虑文件读写(往磁盘写 log 除外),不考虑数据库操作,不考虑 Web 应用;不考虑低端的单核主机或嵌入式系统,不考虑手持式设备,不考虑专门的网络设备,不考虑高端的 >=32 核 Unix 主机;只考虑 TCP,不考虑 UDP,也不考虑除了局域网络之外的其他数据收发方式(例如串并口、USB口、数据采集板卡、实时控制等)。
有了以上这么多限制,那么我将要谈的“网络应用程序”的基本功能可以归纳为“收到数据,算一算,再发出去”。在这个简化了的模型里,似乎看不出用多线程的必要,单线程应该也能做得很好。“为什么需要写多线程程序”这个问题容易引发口水战,我放到另一篇博客里讨论。请允许我先假定“多线程编程”这一背景。
“服务器”这个词有时指程序,有时指进程,有时指硬件(无论虚拟的或真实的),请注意按上下文区分。另外,本文不考虑虚拟化的场景,当我说“两个进程不在同一台机器上”,指的是逻辑上不在同一个操作系统里运行,虽然物理上可能位于同一机器虚拟出来的两台“虚拟机”上。
本文假定读者已经有多线程编程的知识与经验,这不是一篇入门教程。
本文承蒙 Milo Yip 先生审读,在此深表谢意。当然,文中任何错误责任均在我。
目  录
1 进程与线程 2
2 典型的单线程服务器编程模型 3
3 典型的多线程服务器的线程模型 3
One loop per thread 4
线程池 4
归纳 5
4 进程间通信与线程间通信 5
5 进程间通信 6
6 线程间同步 7
互斥器 (mutex) 7
跑题:非递归的 mutex 8
条件变量 10
读写锁与其他 11
封装 MutexLock、MutexLockGuard 和 Condition 11
线程安全的 Singleton 实现 14
归纳 15
7 总结 15
后文预览:Sleep 反模式 16
1 进程与线程
“进程/process”是操作里最重要的两个概念之一(另一个是文件),粗略地讲,一个进程是“内存中正在运行的程序”。本文的进程指的是 Linux 操作系统通过 fork() 系统调用产生的那个东西,或者 Windows 下 CreateProcess() 的产物,不是 Erlang 里的那种轻量级进程。
每个进程有自己独立的地址空间 (address space),“在同一个进程”还是“不在同一个进程”是系统功能划分的重要决策点。Erlang 书把“进程”比喻为“人”,我觉得十分精当,为我们提供了一个思考的框架。
每个人有自己的记忆 (memory),人与人通过谈话(消息传递)来交流,谈话既可以是面谈(同一台服务器),也可以在电话里谈(不同的服务器,有网络通信)。面谈和电话谈的区别在于,面谈可以立即知道对方死否死了(crash, SIGCHLD),而电话谈只能通过周期性的心跳来判断对方是否还活着。
有了这些比喻,设计分布式系统时可以采取“角色扮演”,团队里的几个人各自扮演一个进程,人的角色由进程的代码决定(管登陆的、管消息分发的、管买卖的等等)。每个人有自己的记忆,但不知道别人的记忆,要想知道别人的看法,只能通过交谈。(暂不考虑共享内存这种 IPC。)然后就可以思考容错(万一有人突然死了)、扩容(新人中途加进来)、负载均衡(把 a 的活儿挪給 b 做)、退休(a 要修复 bug,先别给他派新活儿,等他做完手上的事情就把他重启)等等各种场景,十分便利。
“线程”这个概念大概是在 1993 年以后才慢慢流行起来的,距今不过十余年,比不得有 40 年光辉历史的 Unix 操作系统。线程的出现给 Unix 添了不少乱,很多 C 库函数(strtok(), ctime())不是线程安全的,需要重新定义;signal 的语意也大为复杂化。据我所知,最早支持多线程编程的(民用)操作系统是 Solaris 2.2 和 Windows NT 3.1,它们均发布于 1993 年。随后在 1995 年,POSIX threads 标准确立。
线程的特点是共享地址空间,从而可以高效地共享数据。一台机器上的多个进程能高效地共享代码段(操作系统可以映射为同样的物理内存),但不能共享数据。如果多个进程大量共享内存,等于是把多进程程序当成多线程来写,掩耳盗铃。
“多线程”的价值,我认为是为了更好地发挥对称多路处理 (SMP) 的效能。在 SMP 之前,多线程没有多大价值。Alan Cox 说过 A computer is a state machine. Threads are for people who can't program state machines. (计算机是一台状态机。线程是给那些不能编写状态机程序的人准备的。)如果只有一个执行单元,一个 CPU,那么确实如 Alan Cox 所说,按状态机的思路去写程序是最高效的,这正好也是下一节展示的编程模型。
2 典型的单线程服务器编程模型
UNP3e 对此有很好的总结(第 6 章:IO 模型,第 30 章:客户端/服务器设计范式),这里不再赘述。据我了解,在高性能的网络程序中,使用得最为广泛的恐怕要数“non-blocking IO + IO multiplexing”这种模型,即 Reactor 模式,我知道的有:
lighttpd,单线程服务器。(nginx 估计与之类似,待查) libevent/libev ACE,Poco C++ libraries(QT 待查) Java NIO (Selector/SelectableChannel), Apache Mina, Netty (Java) POE (Perl) Twisted (Python)
相反,boost::asio 和 Windows I/O Completion Ports 实现了 Proactor 模式,应用面似乎要窄一些。当然,ACE 也实现了 Proactor 模式,不表。
在“non-blocking IO + IO multiplexing”这种模型下,程序的基本结构是一个事件循环 (event loop):(代码仅为示意,没有完整考虑各种情况)
while (!done) { int timeout_ms = max(1000, getNextTimedCallback()); int retval = ::poll(fds, nfds, timeout_ms); if (retval < 0) { 处理错误 } else { 处理到期的 timers if (retval > 0) { 处理 IO 事件 } } }
当然,select(2)/poll(2) 有很多不足,Linux 下可替换为 epoll,其他操作系统也有对应的高性能替代品(搜 c10k problem)。
Reactor 模型的优点很明显,编程简单,效率也不错。不仅网络读写可以用,连接的建立(connect/accept)甚至 DNS 解析都可以用非阻塞方式进行,以提高并发度和吞吐量 (throughput)。对于 IO 密集的应用是个不错的选择,Lighttpd 即是这样,它内部的 fdevent 结构十分精妙,值得学习。(这里且不考虑用阻塞 IO 这种次优的方案。)
当然,实现一个优质的 Reactor 不是那么容易,我也没有用过坊间开源的库,这里就不推荐了。
3 典型的多线程服务器的线程模型
这方面我能找到的文献不多,大概有这么几种:
1. 每个请求创建一个线程,使用阻塞式 IO 操作。在 Java 1.4 引入 NIO 之前,这是 Java 网络编程的推荐做法。可惜伸缩性不佳。
2. 使用线程池,同样使用阻塞式 IO 操作。与 1 相比,这是提高性能的措施。
3. 使用 non-blocking IO + IO multiplexing。即 Java NIO 的方式。
4. Leader/Follower 等高级模式
在默认情况下,我会使用第 3 种,即 non-blocking IO + one loop per thread 模式。
http://pod.tst.eu/http://cvs.schmorp.de/libev/ev.pod#THREADS_AND_COROUTINES
One loop per thread
此种模型下,程序里的每个 IO 线程有一个 event loop (或者叫 Reactor),用于处理读写和定时事件(无论周期性的还是单次的),代码框架跟第 2 节一样。
这种方式的好处是:
线程数目基本固定,可以在程序启动的时候设置,不会频繁创建与销毁。 可以很方便地在线程间调配负载。
event loop 代表了线程的主循环,需要让哪个线程干活,就把 timer 或 IO channel (TCP connection) 注册到那个线程的 loop 里即可。对实时性有要求的 connection 可以单独用一个线程;数据量大的 connection 可以独占一个线程,并把数据处理任务分摊到另几个线程中;其他次要的辅助性 connections 可以共享一个线程。
对于 non-trivial 的服务端程序,一般会采用 non-blocking IO + IO multiplexing,每个 connection/acceptor 都会注册到某个 Reactor 上,程序里有多个 Reactor,每个线程至多有一个 Reactor。
多线程程序对 Reactor 提出了更高的要求,那就是“线程安全”。要允许一个线程往别的线程的 loop 里塞东西,这个 loop 必须得是线程安全的。
线程池
不过,对于没有 IO 光有计算任务的线程,使用 event loop 有点浪费,我会用有一种补充方案,即用 blocking queue 实现的任务队列(TaskQueue):
blocking_queue > taskQueue; // 线程安全的阻塞队列 void worker_thread() { while (!quit) { boost::function task = taskQueue.take(); // this blocks task(); // 在产品代码中需要考虑异常处理 } }
用这种方式实现线程池特别容易:
// 启动容量为 N 的线程池: int N = num_of_computing_threads; for (int i = 0; i < N; ++i) { create_thread(&worker_thread); // 伪代码:启动线程 }
使用起来也很简单:
boost::function task = boost::bind(&Foo::calc, this); taskQueue.post(task);
上面十几行代码就实现了一个简单的固定数目的线程池,功能大概相当于 Java 5 的 ThreadPoolExecutor 的某种“配置”。当然,在真实的项目中,这些代码都应该封装到一个 class 中,而不是使用全局对象。另外需要注意一点:Foo 对象的生命期,我的另一篇博客《当析构函数遇到多线程——C++ 中线程安全的对象回调》详细讨论了这个问题
http://blog.csdn.net/Solstice/archive/2010/01/22/5238671.aspx
除了任务队列,还可以用 blocking_queue 实现数据的消费者-生产者队列,即 T 的是数据类型而非函数对象,queue 的消费者(s)从中拿到数据进行处理。这样做比 task queue 更加 specific 一些。
blocking_queue 是多线程编程的利器,它的实现可参照 Java 5 util.concurrent 里的 (Array|Linked)BlockingQueue,通常 C++ 可以用 deque 来做底层的容器。Java 5 里的代码可读性很高,代码的基本结构和教科书一致(1 个 mutex,2 个 condition variables),健壮性要高得多。如果不想自己实现,用现成的库更好。(我没有用过免费的库,这里就不乱推荐了,有兴趣的同学可以试试 Intel Threading Building Blocks 里的 concurrent_queue。)
归纳
总结起来,我推荐的多线程服务端编程模式为:event loop per thread + thread pool。
event loop 用作 non-blocking IO 和定时器。 thread pool 用来做计算,具体可以是任务队列或消费者-生产者队列。
以这种方式写服务器程序,需要一个优质的基于 Reactor 模式的网络库来支撑,我只用过 in-house 的产品,无从比较并推荐市面上常见的 C++ 网络库,抱歉。
程序里具体用几个 loop、线程池的大小等参数需要根据应用来设定,基本的原则是“阻抗匹配”,使得 CPU 和 IO 都能高效地运作,具体的考虑点容我以后再谈。
这里没有谈线程的退出,留待下一篇 blog“多线程编程反模式”探讨。
此外,程序里或许还有个别执行特殊任务的线程,比如 logging,这对应用程序来说基本是不可见的,但是在分配资源(CPU 和 IO)的时候要算进去,以免高估了系统的容量。
4 进程间通信与线程间通信
Linux 下进程间通信 (IPC) 的方式数不胜数,光 UNPv2 列出的就有:pipe、FIFO、POSIX 消息队列、共享内存、信号 (signals) 等等,更不必说 Sockets 了。同步原语 (synchronization primitives) 也很多,互斥器 (mutex)、条件变量 (condition variable)、读写锁 (reader-writer lock)、文件锁 (Record locking)、信号量 (Semaphore) 等等。
如何选择呢?根据我的个人经验,贵精不贵多,认真挑选三四样东西就能完全满足我的工作需要,而且每样我都能用得很熟,,不容易犯错。
5 进程间通信
进程间通信我首选 Sockets(主要指 TCP,我没有用过 UDP,也不考虑  Unix domain 协议),其最大的好处在于:可以跨主机,具有伸缩性。反正都是多进程了,如果一台机器处理能力不够,很自然地就能用多台机器来处理。把进程分散到同一局域网的多台机器上,程序改改 host:port 配置就能继续用。相反,前面列出的其他 IPC 都不能跨机器(比如共享内存效率最高,但再怎么着也不能高效地共享两台机器的内存),限制了 scalability。
在编程上,TCP sockets 和 pipe 都是一个文件描述符,用来收发字节流,都可以 read/write/fcntl/select/poll 等。不同的是,TCP 是双向的,pipe 是单向的 (Linux),进程间双向通讯还得开两个文件描述符,不方便;而且进程要有父子关系才能用 pipe,这些都限制了 pipe 的使用。在收发字节流这一通讯模型下,没有比 sockets/TCP 更自然的 IPC 了。当然,pipe 也有一个经典应用场景,那就是写 Reactor/Selector 时用来异步唤醒 select (或等价的 poll/epoll) 调用(Sun JVM 在 Linux 就是这么做的)。
TCP port 是由一个进程独占,且操作系统会自动回收(listening port 和已建立连接的 TCP socket 都是文件描述符,在进程结束时操作系统会关闭所有文件描述符)。这说明,即使程序意外退出,也不会给系统留下垃圾,程序重启之后能比较容易地恢复,而不需要重启操作系统(用跨进程的 mutex 就有这个风险)。还有一个好处,既然 port 是独占的,那么可以防止程序重复启动(后面那个进程抢不到 port,自然就没法工作了),造成意料之外的结果。
两个进程通过 TCP 通信,如果一个崩溃了,操作系统会关闭连接,这样另一个进程几乎立刻就能感知,可以快速 failover。当然,应用层的心跳也是必不可少的,我以后在讲服务端的日期与时间处理的时候还会谈到心跳协议的设计。
与其他 IPC 相比,TCP 协议的一个自然好处是“可记录可重现”,tcpdump/Wireshark 是解决两个进程间协议/状态争端的好帮手。
另外,如果网络库带“连接重试”功能的话,我们可以不要求系统里的进程以特定的顺序启动,任何一个进程都能单独重启,这对开发牢靠的分布式系统意义重大。
使用 TCP 这种字节流 (byte stream) 方式通信,会有 marshal/unmarshal 的开销,这要求我们选用合适的消息格式,准确地说是 wire format。这将是我下一篇 blog 的主题,目前我推荐 Google Protocol Buffers。
有人或许会说,具体问题具体分析,如果两个进程在同一台机器,就用共享内存,否则就用 TCP,比如 MS SQL Server 就同时支持这两种通信方式。我问,是否值得为那么一点性能提升而让代码的复杂度大大增加呢?TCP 是字节流协议,只能顺序读取,有写缓冲;共享内存是消息协议,a 进程填好一块内存让 b 进程来读,基本是“停等”方式。要把这两种方式揉到一个程序里,需要建一个抽象层,封装两种 IPC。这会带来不透明性,并且增加测试的复杂度,而且万一通信的某一方崩溃,状态 reconcile 也会比 sockets 麻烦。为我所不取。再说了,你舍得让几万块买来的 SQL Server 和你的程序分享机器资源吗?产品里的数据库服务器往往是独立的高配置服务器,一般不会同时运行其他占资源的程序。
TCP 本身是个数据流协议,除了直接使用它来通信,还可以在此之上构建 RPC/REST/SOAP 之类的上层通信协议,这超过了本文的范围。另外,除了点对点的通信之外,应用级的广播协议也是非常有用的,可以方便地构建可观可控的分布式系统。
本文不具体讲 Reactor 方式下的网络编程,其实这里边有很多值得注意的地方,比如带 back off 的 retry connecting,用优先队列来组织 timer 等等,留作以后分析吧。
6 线程间同步
线程同步的四项原则,按重要性排列:
1. 首要原则是尽量最低限度地共享对象,减少需要同步的场合。一个对象能不暴露给别的线程就不要暴露;如果要暴露,优先考虑 immutable 对象;实在不行才暴露可修改的对象,并用同步措施来充分保护它。
2. 其次是使用高级的并发编程构件,如 TaskQueue、Producer-Consumer Queue、CountDownLatch 等等;
3. 最后不得已必须使用底层同步原语 (primitives) 时,只用非递归的互斥器和条件变量,偶尔用一用读写锁;
4. 不自己编写 lock-free 代码,不去凭空猜测“哪种做法性能会更好”,比如 spin lock vs. mutex。
前面两条很容易理解,这里着重讲一下第 3 条:底层同步原语的使用。
互斥器 (mutex)
互斥器 (mutex) 恐怕是使用得最多的同步原语,粗略地说,它保护了临界区,一个时刻最多只能有一个线程在临界区内活动。(请注意,我谈的是 pthreads 里的 mutex,不是 Windows 里的重量级跨进程 Mutex。)单独使用 mutex 时,我们主要为了保护共享数据。我个人的原则是:
用 RAII 手法封装 mutex 的创建、销毁、加锁、解锁这四个操作。 只用非递归的 mutex(即不可重入的 mutex)。 不手工调用 lock() 和 unlock() 函数,一切交给栈上的 Guard 对象的构造和析构函数负责,Guard 对象的生命期正好等于临界区(分析对象在什么时候析构是 C++ 程序员的基本功)。这样我们保证在同一个函数里加锁和解锁,避免在 foo() 里加锁,然后跑到 bar() 里解锁。 在每次构造 Guard 对象的时候,思考一路上(调用栈上)已经持有的锁,防止因加锁顺序不同而导致死锁 (deadlock)。由于 Guard 对象是栈上对象,看函数调用栈就能分析用锁的情况,非常便利。
次要原则有:
不使用跨进程的 mutex,进程间通信只用 TCP sockets。 加锁解锁在同一个线程,线程 a 不能去 unlock 线程 b 已经锁住的 mutex。(RAII 自动保证) 别忘了解锁。(RAII 自动保证) 不重复解锁。(RAII 自动保证) 必要的时候可以考虑用 PTHREAD_MUTEX_ERRORCHECK 来排错
用 RAII 封装这几个操作是通行的做法,这几乎是 C++ 的标准实践,后面我会给出具体的代码示例,相信大家都已经写过或用过类似的代码了。Java 里的 synchronized 语句和 C# 的 using 语句也有类似的效果,即保证锁的生效期间等于一个作用域,不会因异常而忘记解锁。
Mutex 恐怕是最简单的同步原语,安装上面的几条原则,几乎不可能用错。我自己从来没有违背过这些原则,编码时出现问题都很快能招到并修复。
跑题:非递归的 mutex
谈谈我坚持使用非递归的互斥器的个人想法。
Mutex 分为递归 (recursive) 和非递归(non-recursive)两种,这是 POSIX 的叫法,另外的名字是可重入 (Reentrant) 与非可重入。这两种 mutex 作为线程间 (inter-thread) 的同步工具时没有区别,它们的惟一区别在于:同一个线程可以重复对 recursive mutex 加锁,但是不能重复对 non-recursive mutex 加锁。
首选非递归 mutex,绝对不是为了性能,而是为了体现设计意图。non-recursive 和 recursive 的性能差别其实不大,因为少用一个计数器,前者略快一点点而已。在同一个线程里多次对 non-recursive mutex 加锁会立刻导致死锁,我认为这是它的优点,能帮助我们思考代码对锁的期求,并且及早(在编码阶段)发现问题。
毫无疑问 recursive mutex 使用起来要方便一些,因为不用考虑一个线程会自己把自己给锁死了,我猜这也是 Java 和 Windows 默认提供 recursive mutex 的原因。(Java 语言自带的 intrinsic lock 是可重入的,它的 concurrent 库里提供 ReentrantLock,Windows 的 CRITICAL_SECTION 也是可重入的。似乎它们都不提供轻量级的 non-recursive mutex。)
正因为它方便,recursive mutex 可能会隐藏代码里的一些问题。典型情况是你以为拿到一个锁就能修改对象了,没想到外层代码已经拿到了锁,正在修改(或读取)同一个对象呢。具体的例子:
std::vector foos; MutexLock mutex; void post(const Foo& f) { MutexLockGuard lock(mutex); foos.push_back(f); } void traverse() { MutexLockGuard lock(mutex); for (auto it = foos.begin(); it != foos.end(); ++it) { // 用了 0x 新写法 it->doit(); } }
post() 加锁,然后修改 foos 对象; traverse() 加锁,然后遍历 foos 数组。将来有一天,Foo::doit() 间接调用了 post() (这在逻辑上是错误的),那么会很有戏剧性的:
1. Mutex 是非递归的,于是死锁了。
2. Mutex 是递归的,由于 push_back 可能(但不总是)导致 vector 迭代器失效,程序偶尔会 crash。
这时候就能体现 non-recursive 的优越性:把程序的逻辑错误暴露出来。死锁比较容易 debug,把各个线程的调用栈打出来((gdb) thread apply all bt),只要每个函数不是特别长,很容易看出来是怎么死的。(另一方面支持了函数不要写过长。)或者可以用 PTHREAD_MUTEX_ERRORCHECK 一下子就能找到错误(前提是 MutexLock 带 debug 选项。)
程序反正要死,不如死得有意义一点,让验尸官的日子好过些。
如果一个函数既可能在已加锁的情况下调用,又可能在未加锁的情况下调用,那么就拆成两个函数:
1. 跟原来的函数同名,函数加锁,转而调用第 2 个函数。
2. 给函数名加上后缀 WithLockHold,不加锁,把原来的函数体搬过来。
就像这样:
void post(const Foo& f) { MutexLockGuard lock(mutex); postWithLockHold(f); // 不用担心开销,编译器会自动内联的 } // 引入这个函数是为了体现代码作者的意图,尽管 push_back 通常可以手动内联 void postWithLockHold(const Foo& f) { foos.push_back(f); }
这有可能出现两个问题(感谢水木网友 ilovecpp 提出):a) 误用了加锁版本,死锁了。b) 误用了不加锁版本,数据损坏了。
对于 a),仿造前面的办法能比较容易地排错。对于 b),如果 pthreads 提供 isLocked() 就好办,可以写成:
void postWithLockHold(const Foo& f) { assert(mutex.isLocked()); // 目前只是一个愿望 // ... }
另外,WithLockHold 这个显眼的后缀也让程序中的误用容易暴露出来。
C++ 没有 annotation,不能像 Java 那样给 method 或 field 标上 @GuardedBy 注解,需要程序员自己小心在意。虽然这里的办法不能一劳永逸地解决全部多线程错误,但能帮上一点是一点了。
我还没有遇到过需要使用 recursive mutex 的情况,我想将来遇到了都可以借助 wrapper 改用 non-recursive mutex,代码只会更清晰。
=== 回到正题 ===
本文这里只谈了 mutex 本身的正确使用,在 C++ 里多线程编程还会遇到其他很多 race condition,请参考拙作《当析构函数遇到多线程——C++ 中线程安全的对象回调》
http://blog.csdn.net/Solstice/archive/2010/01/22/5238671.aspx 。请注意这里的 class 命名与那篇文章有所不同。我现在认为 MutexLock 和 MutexLockGuard 是更好的名称。
性能注脚:Linux 的 pthreads mutex 采用 futex 实现,不必每次加锁解锁都陷入系统调用,效率不错。Windows 的 CRITICAL_SECTION 也是类似。
条件变量
条件变量 (condition variable) 顾名思义是一个或多个线程等待某个布尔表达式为真,即等待别的线程“唤醒”它。条件变量的学名叫管程 (monitor)。Java Object 内置的 wait(), notify(), notifyAll() 即是条件变量(它们以容易用错著称)。条件变量只有一种正确使用的方式,对于 wait() 端:
1. 必须与 mutex 一起使用,该布尔表达式的读写需受此 mutex 保护
2. 在 mutex 已上锁的时候才能调用 wait()
3. 把判断布尔条件和 wait() 放到 while 循环中
写成代码是:
MutexLock mutex; Condition cond(mutex); std::deque queue; int dequeue() { MutexLockGuard lock(mutex); while (queue.empty()) { // 必须用循环;必须在判断之后再 wait() cond.wait(); // 这一步会原子地 unlock mutex 并进入 blocking,不会与 enqueue 死锁 } assert(!queue.empty()); int top = queue.front(); queue.pop_front(); return top; }
对于 signal/broadcast 端:
1. 不一定要在 mutex 已上锁的情况下调用 signal (理论上)
2. 在 signal 之前一般要修改布尔表达式
3. 修改布尔表达式通常要用 mutex 保护(至少用作 full memory barrier)
写成代码是:
void enqueue(int x) { MutexLockGuard lock(mutex); queue.push_back(x); cond.notify(); }
上面的 dequeue/enqueue 实际上实现了一个简单的 unbounded BlockingQueue。
条件变量是非常底层的同步原语,很少直接使用,一般都是用它来实现高层的同步措施,如 BlockingQueue 或 CountDownLatch。
读写锁与其他
读写锁 (Reader-Writer lock),读写锁是个优秀的抽象,它明确区分了 read 和 write 两种行为。需要注意的是,reader lock 是可重入的,writer lock 是不可重入(包括不可提升 reader lock)的。这正是我说它“优秀”的主要原因。
遇到并发读写,如果条件合适,我会用《借 shared_ptr 实现线程安全的 copy-on-write》http://blog.csdn.net/Solstice/archive/2008/11/22/3351751.aspx 介绍的办法,而不用读写锁。当然这不是绝对的。
信号量 (Semaphore),我没有遇到过需要使用信号量的情况,无从谈及个人经验。
说一句大逆不道的话,如果程序里需要解决如“哲学家就餐”之类的复杂 IPC 问题,我认为应该首先考察几个设计,为什么线程之间会有如此复杂的资源争抢(一个线程要同时抢到两个资源,一个资源可以被两个线程争夺)?能不能把“想吃饭”这个事情专门交给一个为各位哲学家分派餐具的线程来做,然后每个哲学家等在一个简单的 condition variable 上,到时间了有人通知他去吃饭?从哲学上说,教科书上的解决方案是平权,每个哲学家有自己的线程,自己去拿筷子;我宁愿用集权的方式,用一个线程专门管餐具的分配,让其他哲学家线程拿个号等在食堂门口好了。这样不损失多少效率,却让程序简单很多。虽然 Windows 的 WaitForMultipleObjects 让这个问题 trivial 化,在 Linux 下正确模拟 WaitForMultipleObjects 不是普通程序员该干的。
封装 MutexLock、MutexLockGuard 和 Condition
本节把前面用到的 MutexLock、MutexLockGuard、Condition classes 的代码列出来,前面两个 classes 没多大难度,后面那个有点意思。
MutexLock 封装临界区(Critical secion),这是一个简单的资源类,用 RAII 手法 [CCS:13]封装互斥器的创建与销毁。临界区在 Windows 上是 CRITICAL_SECTION,是可重入的;在 Linux 下是 pthread_mutex_t,默认是不可重入的。MutexLock 一般是别的 class 的数据成员。
MutexLockGuard 封装临界区的进入和退出,即加锁和解锁。MutexLockGuard 一般是个栈上对象,它的作用域刚好等于临界区域。
这两个 classes 应该能在纸上默写出来,没有太多需要解释的:
#include #include class MutexLock : boost::noncopyable { public: MutexLock() // 为了节省版面,单行函数都没有正确缩进 { pthread_mutex_init(&mutex_, NULL); } ~MutexLock() { pthread_mutex_destroy(&mutex_); } void lock() // 程序一般不主动调用 { pthread_mutex_lock(&mutex_); } void unlock() // 程序一般不主动调用 { pthread_mutex_unlock(&mutex_); } pthread_mutex_t* getPthreadMutex() // 仅供 Condition 调用,严禁自己调用 { return &mutex_; } private: pthread_mutex_t mutex_; }; class MutexLockGuard : boost::noncopyable { public: explicit MutexLockGuard(MutexLock& mutex) : mutex_(mutex) { mutex_.lock(); } ~MutexLockGuard() { mutex_.unlock(); } private: MutexLock& mutex_; }; #define MutexLockGuard(x) static_assert(false, "missing mutex guard var name")
注意代码的最后一行定义了一个宏,这个宏的作用是防止程序里出现如下错误:
void doit() { MutexLockGuard(mutex); // 没有变量名,产生一个临时对象又马上销毁了,没有锁住临界区 // 正确写法是 MutexLockGuard lock(mutex); // 临界区 }
这里 MutexLock 没有提供 trylock() 函数,因为我没有用过它,我想不出什么时候程序需要“试着去锁一锁”,或许我写过的代码太简单了。
我见过有人把 MutexLockGuard 写成 template,我没有这么做是因为它的模板类型参数只有 MutexLock 一种可能,没有必要随意增加灵活性,于是我人肉把模板具现化 (instantiate) 了。此外一种更激进的写法是,把 lock/unlock 放到 private 区,然后把 Guard 设为 MutexLock 的 friend,我认为在注释里告知程序员即可,另外 check-in 之前的 code review 也很容易发现误用的情况 (grep getPthreadMutex)。
这段代码没有达到工业强度:a) Mutex 创建为 PTHREAD_MUTEX_DEFAULT 类型,而不是我们预想的 PTHREAD_MUTEX_NORMAL 类型(实际上这二者很可能是等同的),严格的做法是用 mutexattr 来显示指定 mutex 的类型。b) 没有检查返回值。这里不能用 assert 检查返回值,因为 assert 在 release build 里是空语句。我们检查返回值的意义在于防止 ENOMEM 之类的资源不足情况,这一般只可能在负载很重的产品程序中出现。一旦出现这种错误,程序必须立刻清理现场并主动退出,否则会莫名其妙地崩溃,给事后调查造成困难。这里我们需要 non-debug 的 assert,或许 google-glog 的 CHECK() 是个不错的思路。
以上两点改进留作练习。
Condition class 的实现有点意思。
Pthreads condition variable 允许在 wait() 的时候指定 mutex,但是我想不出什么理由一个 condition variable 会和不同的 mutex 配合使用。Java 的 intrinsic condition 和 Conditon class 都不支持这么做,因此我觉得可以放弃这一灵活性,老老实实一对一好了。相反 boost::thread 的 condition_varianle 是在 wait 的时候指定 mutex,请参观其同步原语的庞杂设计:
Concept 有四种 Lockable, TimedLockable, SharedLockable, UpgradeLockable. Lock 有五六种: lock_guard, unique_lock, shared_lock, upgrade_lock, upgrade_to_unique_lock, scoped_try_lock. Mutex 有七种:mutex, try_mutex, timed_mutex, recursive_mutex, recursive_try_mutex, recursive_timed_mutex, shared_mutex.
恕我愚钝,见到 boost::thread 这样如 Rube Goldberg Machine 一样“灵活”的库我只得三揖绕道而行。这些 class 名字也很无厘头,为什么不老老实实用 reader_writer_lock 这样的通俗名字呢?非得增加精神负担,自己发明新名字。我不愿为这样的灵活性付出代价,宁愿自己做几个简简单单的一看就明白的 classes 来用,这种简单的几行代码的轮子造造也无妨。提供灵活性固然是本事,然而在不需要灵活性的地方把代码写死,更需要大智慧。
下面这个 Condition 简单地封装了 pthread cond var,用起来也容易,见本节前面的例子。这里我用 notify/notifyAll 作为函数名,因为 signal 有别的含义,C++ 里的 signal/slot,C 里的 signal handler 等等。就别 overload 这个术语了。
class Condition : boost::noncopyable { public: Condition(MutexLock& mutex) : mutex_(mutex) { pthread_cond_init(&pcond_, NULL); } ~Condition() { pthread_cond_destroy(&pcond_); } void wait() { pthread_cond_wait(&pcond_, mutex_.getPthreadMutex()); } void notify() { pthread_cond_signal(&pcond_); } void notifyAll() { pthread_cond_broadcast(&pcond_); } private: MutexLock& mutex_; pthread_cond_t pcond_; };
如果一个 class 要包含 MutexLock 和 Condition,请注意它们的声明顺序和初始化顺序,mutex_ 应先于 condition_ 构造,并作为后者的构造参数:
class CountDownLatch { public: CountDownLatch(int count) : count_(count), mutex_(), condition_(mutex_) { } private: int count_; MutexLock mutex_; // 顺序很重要 Condition condition_; };
请允许我再次强调,虽然本节花了大量篇幅介绍如何正确使用 mutex 和 condition variable,但并不代表我鼓励到处使用它们。这两者都是非常底层的同步原语,主要用来实现更高级的并发编程工具,一个多线程程序里如果大量使用 mutex 和 condition variable 来同步,基本跟用铅笔刀锯大树(孟岩语)没啥区别。
在程序里使用 pthreads 库有一个额外的好处:分析工具认得它们,懂得其语意。线程分析工具如 Intel Thread Checker 和 Valgrind-Helgrind 等能识别 pthreads 调用,并依据 happens-before 关系 [Lamport 1978] 分析程序有无 data race。
线程安全的 Singleton 实现
研究 Signleton 的线程安全实现的历史你会发现很多有意思的事情,一度人们认为 Double checked locking 是王道,兼顾了效率与正确性。后来有神牛指出由于乱序执行的影响,DCL 是靠不住的。(这个又让我想起了 SQL 注入,十年前用字符串拼接出 SQL 语句是 Web 开发的通行做法,直到有一天有人利用这个漏洞越权获得并修改网站数据,人们才幡然醒悟,赶紧修补。)Java 开发者还算幸运,可以借助内部静态类的装载来实现。C++ 就比较惨,要么次次锁,要么 eager initialize、或者动用 memory barrier 这样的大杀器( http://www.aristeia.com/Papers/DDJ_Jul_Aug_2004_revised.pdf )。接下来 Java 5 修订了内存模型,并增强了 volatile 的语义,这下 DCL (with volatile) 又是安全的了。然而 C++ 的内存模型还在修订中,C++ 的 volatile 目前还不能(将来也难说)保证 DCL 的正确性(只在 VS2005+ 上有效)。
其实没那么麻烦,在实践中用 pthread once 就行:
#include template class Singleton : boost::noncopyable { public: static T& instance() { pthread_once(&ponce_, &Singleton::init); return *value_; } static void init() { value_ = new T(); } private: static pthread_once_t ponce_; static T* value_; }; template pthread_once_t Singleton::ponce_ = PTHREAD_ONCE_INIT; template T* Singleton::value_ = NULL;
上面这个 Singleton 没有任何花哨的技巧,用 pthread_once_t 来保证 lazy-initialization 的线程安全。使用方法也很简单:
Foo& foo = Singleton::instance();
当然,这个 Singleton 没有考虑对象的销毁,在服务器程序里,这不是一个问题,因为当程序退出的时候自然就释放所有资源了(前提是程序里不使用不能由操作系统自动关闭的资源,比如跨进程的 Mutex)。另外,这个 Singleton 只能调用默认构造函数,如果用户想要指定 T 的构造方式,我们可以用模板特化 (template specialization) 技术来提供一个定制点,这需要引入另一层间接。
归纳
进程间通信首选 TCP sockets 线程同步的四项原则 使用互斥器的条件变量的惯用手法 (idiom),关键是 RAII
用好这几样东西,基本上能应付多线程服务端开发的各种场合,只是或许有人会觉得性能没有发挥到极致。我认为,先把程序写正确了,再考虑性能优化,这在多线程下任然成立。让一个正确的程序变快,远比“让一个快的程序变正确”容易得多。
7 总结
在现代的多核计算背景下,线程是不可避免的。尽管一定程度上可以通过 framework 来屏蔽,让你感觉像是在写单线程程序,比如 Java Servlet。了解 under the hood 发生了什么对于编写这种程序也会有帮助。
多线程编程是一项重要的个人技能,不能因为它难就本能地排斥,现在的软件开发比起 10 年 20 年前已经难了不知道多少倍。掌握多线程编程,才能更理智地选择用还是不用多线程,因为你能预估多线程实现的难度与收益,在一开始做出正确的选择。要知道把一个单线程程序改成多线程的,往往比重头实现一个多线程的程序更难。
掌握同步原语和它们的适用场合时多线程编程的基本功。以我的经验,熟练使用文中提到的同步原语,就能比较容易地编写线程安全的程序。本文没有考虑 signal 对多线程编程的影响,Unix 的 signal 在多线程下的行为比较复杂,一般要靠底层的网络库 (如 Reactor) 加以屏蔽,避免干扰上层应用程序的开发。
通篇来看,“效率”并不是我的主要考虑点,a) TCP 不是效率最高的 IPC,b) 我提倡正确加锁而不是自己编写 lock-free 算法(使用原子操作除外)。在程序的复杂度和性能之前取得平衡,并经考虑未来两三年扩容的可能(无论是 CPU 变快、核数变多,还是机器数量增加,网络升级)。下一篇“多线程编程的反模式”会考察伸缩性方面的常见错误,我认为在分布式系统中,伸缩性 (scalability) 比单机的性能优化更值得投入精力。
这篇文章记录了我目前对多线程编程的理解,用文中介绍的手法,我能解决自己面临的全部多线程编程任务。如果文章的观点与您不合,比如您使用了我没有推荐使用的技术或手法(共享内存、信号量等等),只要您理由充分,但行无妨。
这篇文章本来还有两节“多线程编程的反模式”与“多线程的应用场景”,考虑到字数已经超过一万了,且听下回分解吧 :-)
后文预览:Sleep 反模式
我认为 sleep 只能出现在测试代码中,比如写单元测试的时候。(涉及时间的单元测试不那么好写,短的如一两秒钟可以用 sleep,长的如一小时一天得想其他办法,比如把算法提出来并把时间注入进去。)产品代码中线程的等待可分为两种:一种是无所事事的时候(要么等在 select/poll/epoll 上。要么等在 condition variable 上,等待 BlockingQueue /CountDownLatch 亦可归入此类),一种是等着进入临界区(等在 mutex 上)以便继续处理。在程序的正常执行中,如果需要等待一段时间,应该往 event loop 里注册一个 timer,然后在 timer 的回调函数里接着干活,因为线程是个珍贵的共享资源,不能轻易浪费。如果多线程的安全性和效率要靠代码主动调用 sleep 来保证,这是设计出了问题。等待一个事件发生,正确的做法是用 select 或 condition variable 或(更理想地)高层同步工具。当然,在 GUI 编程中会有主动让出 CPU 的做法,比如调用 sleep(0) 来实现 yield。
谈谈多线程的思维方式
Posted on 2009-04-24 08:53Kevin-moon 阅读(4246)评论(26)编辑收藏
前段时间仔细看过些关于多线程方面的资料,项目中用到线程的地方也不少,可是,当看了Jeffrey的一篇关于锁的文章后,发现自己虽然一直都在使用多线程,但是缺少了做多线程编程需要的思维!所以想从Jeffrey的Optex(锁)入手,来谈谈我从其中体会的东西。
在NET中,我们用的最多的锁机制就是lock,用起来很简单,短短几行程序就可以实现,例如:
Lock 's Code
public class TestThreading
{
private System.Object lockThis = new System.Object();
public void Function()
{
lock (lockThis)
{
// Access thread-sensitive resources.
}
}
}
其实我们也明白,lock并不是锁,而是MS提供的一个简便式的写法,真正实现的是Monitor类中的Enter和Exit方法,既然提到了Monitor类也就说下有个需要注意的地方:
Pulse和PulseAll方法,这两个方法就是把锁状态将要改变的消息通知给等待队列中的线程,不过这时如果等待队列中没有线程,那么该方法就会一直等待下去,直到有等待的线程进入队列,也就是说该方法可能造成类试死锁的情况出现。
上面的lock + 线程(Thread和ThreadPool) = 多线程编程(N%)!?
对于该公式我曾经的N是80,现在是20。其中有很多东西影响我,让我从80->20,下面的Optex就是一个入口点。
Optex 's Code
public sealed class Optex : IDisposable {
private Int32 m_Waiters = 0;
private Semaphore m_WaiterLock = new Semaphore(0, Int32.MaxValue);
public Optex() { }
public void Dispose() {
if (m_WaiterLock != null)
{
m_WaiterLock.Close();
m_WaiterLock = null;
}
}
public void Enter() {
Thread.BeginCriticalRegion();
// Add ourself to the set of threads interested in the Optex
if (Interlocked.Increment(ref m_Waiters) == 1) {
// If we were the first thread to show interest, we got it.
return;
}
// Another thread has the Optex, we need to wait for it
m_WaiterLock.WaitOne();
// When WaitOne returns, this thread now has the Optex
}
public void Exit() {
// Subtract ourself from the set of threads interested in the Optex
if (Interlocked.Decrement(ref m_Waiters) > 0) {
// Other threads are waiting, wake 1 of them
m_WaiterLock.Release(1);
}
Thread.EndCriticalRegion();
}
}
看完上面的代码,让我增加了两点认识:
1、Thread.BeginCriticalRegion()和Thread.EndCriticalRegion();
因为这段时间正好看了一本多线程编程的书,既然将上面方法认为是进入临界区和退出临界区,对于临界区而言,进入该区的数据,在没有退出之前,如果临界区外的程序需要使用它,那么就必须出于等待。所以觉得已经使用临界区,为什么还要使用Semaphore?!
可是,MS只是取了个相同的名字,做的事情完全不同,上面两个方法完全没有临界区的概念,它只是设置一个区域(Begin到End之间),表示该区域内发生线程中断或未处理的异常会影响整个应用程序域。 2、m_Waiters的作用
一开始以为在Enter的时候,直接写上:
m_WaiterLock.WaitOne();
Exit的时候,写上:
m_WaiterLock.Release(1);
这样就可以了。m_Waiters有什么意义?!
优化性能,Semaphore是内核对象,我们都知道,要尽量少的进入内核模式,因为这是很消耗性能,所以尽量少的使用内核对象。m_Waiters的意义就在这里,如果只有一个线程使用该锁对象的时候,是不需要去获取和释放的。 OK,上述的东西都是铺垫,铺完了也就进入主题了!
多线程的思维优化的Optex
namespace ThreadConcurrent.Lock
{
public sealed class Optex : IDisposable
{
/// 
/// 琐的状态
/// 

private Int32 m_LockState = c_lsFree;
/// 
/// 自由状态
/// 

private const Int32 c_lsFree = 0x00000000;
/// 
/// 被拥有状态
/// 

private const Int32 c_lsOwned = 0x00000001;
/// 
/// 等待的线程数
/// 

private const Int32 c_1Waiter = 0x00000002;
private Semaphore m_WaiterLock = new Semaphore(0, Int32.MaxValue);
#region 构造函数
/// 
///
/// 

public Optex() { }
#endregion
/// 
/// 请求锁
/// 

public void Enter()
{
Thread.BeginCriticalRegion();
while (true)
{
Int32 ls = InterlockedOr(ref m_LockState, c_lsOwned);
//自由状态
if ((ls & c_lsOwned) == c_lsFree) return;
// 增加等待的线程数
if (IfThen(ref m_LockState, ls, ls + c_1Waiter))
{
m_WaiterLock.WaitOne();
}
}
}
public void Exit()
{
// 释放琐
Int32 ls = InterlockedAnd(ref m_LockState, ~c_lsOwned);
//无等待的线程
if (ls == c_lsOwned)
{
}
else
{
ls &= ~c_lsOwned;
if (IfThen(ref m_LockState, ls & ~c_lsOwned, ls - c_1Waiter))
{
m_WaiterLock.Release(1);
}
else
{
}
}
Thread.EndCriticalRegion();
}
#region 原子化操作
/// 
/// 与操作
/// 

/// 
/// 
/// 
private static Int32 InterlockedAnd(ref Int32 target, Int32 with)
{
Int32 i, j = target;
do
{
i = j;
j = Interlocked.CompareExchange(ref target, i & with, i);
} while (i != j);
return j;
}
/// 
/// 或操作
/// 

/// 
/// 
/// 
private static Int32 InterlockedOr(ref Int32 target, Int32 with)
{
Int32 i, j = target;
do
{
i = j;
j = Interlocked.CompareExchange(ref target, i | with, i);
} while (i != j);
return j;
}
#endregion
private static Boolean IfThen(ref Int32 val, Int32 @if, Int32 then)
{
return (Interlocked.CompareExchange(ref val, @then, @if) == @if);
}
private static Boolean IfThen(ref Int32 val, Int32 @if, Int32 then, out Int32 prevVal)
{
prevVal = Interlocked.CompareExchange(ref val, @then, @if);
return (prevVal == @if);
}
/// 
/// 释放资源
/// 

public void Dispose()
{
if (m_WaiterLock != null)
{
m_WaiterLock.Close();
m_WaiterLock = null;
}
}
}
}
对于上面的这个代码,我晕眩了好一段时间,不过当我真正理解的时候,从晕眩中学到了做多线程编程应该具备的思维方式。
首先从简单的理解开始谈,
1、原子化操作
对于InterLocked类,曾经也知道,但是却用的很少,不过从该代码中知道,在多线程的编程中对共享数据的写入操作,一定要达到原子性。至于如何做到这点,InterlockedAnd和InterlockedOr做了很好的诠释:
While循环的目的就是保证target值以最新的值做与操作,如果传入的值在执行的过程被其他线程改变的话,那么是不会退出该循环的,并会利用改变后的值重新做次与操作。
2、理解Enter和Exit
这两个方法很难写出来解释,用图是最清晰的。

曾经的晕眩:
1、Enter方法中为什么存在循环,为什么不是执行完waitone就结束,必须m_lockState等于c_IsFree的时候才结束?
线程的执行并不完全按照先前排好的顺序去执行,有时会发生一些特殊的情况来使改变线程的调度顺序,所以就可能会出现上图灰色部分的情况,则为了解决该可能发生的问题(概率很小)循环机制就出现了。
2、为什么在WaitOne和Release之前,除了增加和减少等待者外,还需要判断m_lockstate是否改变(进入Enter到执行Waitone前的这段时间)?
一般性的思维:

该程序的思维:

这样做的好处就是尽量少的操作内核对象,提高性能!
多线程编程虽然复杂,但是我觉得很有意思和挑战性,而且随着硬件的发展,多线程编程会更加重要,既然已经上路就让我们走到尽头!
衔接UI线程和管理后台工作线程的类(多线程、异步调用)
一、引言
在编写Windows form时,如果直接在UI线程要运行一个费时方法的话(如从数据库查询大量数据时),会引起程序“假死”,从而导致用户不满。这个时候就需要通过多线程技术来解决,提高界面交互性能,方便用户使用。
一般通过三种方式解决:
1.通过System.Threading.Thread类,创建新的线程,Thread.Start运行费时方法。
2.通过System.Threading.ThreadPool类,将费时任务提交到线程池中,等待运行。
以上两种方法,基本思路是在UI界面中控制线程的启动和中止,在线程中回调用UI界面方法,更新界面。在线程中回调UI界面方法时,特别是涉及更新控件属性时,如果不注意,存在很大的隐患。这两种办法,编码和控制结构较为复杂,需要启动和管理额外的线程占用资源。
3.通过异步委托调用,将该方法排队到系统线程池的线程中运行,而在费时方法中也通过Control.BeginInvoke异步回调,达到"启动后不管"的目的。
这种方法,编码简单,程序结构较为清晰,充分利用.NET框架的异步委托功能,但要对异步调用知识较熟悉。
相关知识点参见
现利用.NET异步委托调用功能,编写Task抽象类,以方便管理后台工作线程,衔接后台线程与UI线程的联系。该抽象类提供了调用和管理的框架,没有方法的实现细节,通过继承类、重写方法,可以实现想要的功能。主要功能如下:
1.利用异步委托调用,实际多线程,不需要单独后台线程。
2.通过委托、事件驱动,实际后台与前台UI线程的联系,实现事件广播。
3.支持正常取消后台工作方法(费时方法)运行,也可以强制中止线程。
4.能够捕获取消、强制中止和方法出错三种情况,并突发相关事件,以便进行释放资源等操作。
5.通过异步调用,在工作方法中安全调用涉及UI控件的方法。
6.自行管理工作进程状态,提供状态变化事件。
7.只要工作方法调用签名,符合定义的TaskDelegate委托接口,可通过StartTask(TaskDelegate worker ,params object[] args )方便调用。在实际使用时,可在继承类中定义多个相同调用接口的方法,避免重复编码,较为方便。
给大家作个参考,而大牛呢,多点指正。当是扔个砖头,想砸块玉吧。
二、代码
1using System;
2using System.Windows.Forms;
3
4namespace Net66.AsynchThread
5{
6    /**//// 
7    /// 任务工作状态
8    /// 

9    public enum TaskStatus
10    {
11        /**//// 
12        /// 任务没有运行,可能是工作进程没有开始、工作进程正常结束或正常取消工作进程
13        /// 

14        Stopped,
15        /**//// 
16        /// 任务没有运行,被调用者强行中止
17        /// 

18        Aborted,
19        /**//// 
20        /// 任务没有运行,在工作进程中触发错误而中止
21        /// 

22        ThrowErrorStoped,
23        /**//// 
24        /// 任务运行中
25        /// 

26        Running,
27        /**//// 
28        /// 尝试取消工作进程中
29        /// 

30        CancelPending,
31        /**//// 
32        /// 强行中止工作进程中
33        /// 

34        AbortPending
35
36    }
37
38    /**//// 
39    /// 任务状态消息
40    /// 

41    public class TaskEventArgs : EventArgs
42    {
43        /**//// 
44        /// 任务运行结果
45        /// 

46        public Object     Result;
47        /**//// 
48        /// 任务进度(0-100)
49        /// 

50        public int        Progress;
51        /**//// 
52        /// 任务工作状态
53        /// 

54        public TaskStatus Status;
55        /**//// 
56        /// 任务消息文本
57        /// 

58        public String Message;
59        /**//// 
60        /// 创建任务状态消息
61        /// 

62        /// 任务进度(0-100)
63        public TaskEventArgs( int progress )
64        {
65            this.Progress = progress;
66            this.Status   = TaskStatus.Running;
67        }
68        /**//// 
69        /// 创建任务状态消息
70        /// 

71        /// 任务线程状态
72        public TaskEventArgs( TaskStatus status )
73        {
74            this.Status = status;
75        }
76        /**//// 
77        /// 创建任务状态消息
78        /// 

79        /// 任务进度(0-100)
80        /// 任务运行中间结果
81        public TaskEventArgs( int progress,object result )
82        {
83            this.Progress = progress;
84            this.Status   = TaskStatus.Running;
85            this.Result   = result;
86        }
87        /**//// 
88        /// 创建任务状态消息
89        /// 

90        /// 任务线程状态
91        /// 任务运行结果
92        public TaskEventArgs( TaskStatus status,object result )
93        {
94            this.Status   = status;
95            this.Result   = result;
96        }
97        /**//// 
98        /// 创建任务状态消息
99        /// 

100        /// 任务线程状态
101        /// 消息文本
102        /// 任务运行结果
103        public TaskEventArgs( TaskStatus status,string message ,object result )
104        {
105            this.Status   = status;
106            this.Message = message;
107            this.Result   = result;
108        }
109        /**//// 
110        /// 创建任务状态消息
111        /// 

112        /// 任务进度(0-100)
113        /// 消息文本
114        /// 任务运行中间结果
115        public TaskEventArgs( int progress,string message ,object result )
116        {
117            this.Progress = progress;
118            this.Status   = TaskStatus.Running;
119            this.Message = message;
120            this.Result   = result;
121        }
122        /**//// 
123        /// 创建任务状态消息
124        /// 

125        /// 任务线程状态
126        /// 任务进度(0-100)
127        /// 消息文本
128        /// 任务运行中间结果
129        public TaskEventArgs( TaskStatus status,int progress,string message ,object result )
130        {
131            this.Status = status;
132            this.Progress = progress;
133            this.Message = message;
134            this.Result   = result;
135        }
136    }
137
138    /**//// 
139    /// 任务的工作方法(Work)的委托接口
140    /// 传入值:对象数组(object[])
141    /// 返回值:对象(object)
142    /// 

143    public delegate object TaskDelegate( params object[] args );
144
145    /**//// 
146    /// 任务事件的委托接口
147    /// 

148    public delegate void TaskEventHandler( object sender, TaskEventArgs e );
149
150    abstract public class Task
151    {
152        内部属性#region 内部属性
153        /**//// 
154        /// 任务调用线程(前台或UI线程)
155        /// 

156        protected System.Threading.Thread _callThread = null;
157        /**//// 
158        /// 任务工作线程(后台)
159        /// 

160        protected System.Threading.Thread _workThread = null;
161        /**//// 
162        /// 任务工作状态
163        /// 

164        protected TaskStatus _taskState = TaskStatus.Stopped;
165        /**//// 
166        /// 任务进度(0-100)
167        /// 

168        protected int _progress = -1;
169        /**//// 
170        /// 任务工作结果
171        /// 

172        protected object _result = null;
173        /**//// 
174        /// 任务工作进程出错时,捕获的异常对象
175        /// 

176        protected Exception _exception = null;
177        #endregion
178
179        事件#region 事件
180        /**//// 
181        /// 任务工作状态变化事件
182        /// 

183        public event TaskEventHandler TaskStatusChanged;
184        /**//// 
185        /// 任务进度变化事件
186        /// 

187        public event TaskEventHandler TaskProgressChanged;
188        /**//// 
189        /// 任务被调用者强行中止事件
190        /// 

191        public event TaskEventHandler TaskAbort;
192        /**//// 
193        /// 任务工作方法执行中触发错误事件
194        /// 

195        public event TaskEventHandler TaskThrowError;
196        /**//// 
197        /// 任务被调用者取消事件
198        /// 

199        public event TaskEventHandler TaskCancel;
200        #endregion
201
202        属性#region 属性
203
204        /**//// 
205        /// 任务工作进程出错时,捕获的异常对象
206        /// 

207        public Exception Exception
208        {
209            get { return _exception;}
210        }
211        /**//// 
212        /// 任务调用线程(前台或UI线程)
213        /// 

214        public System.Threading.Thread CallThread
215        {
216            get { return _callThread;}
217        }
218        /**//// 
219        /// 任务工作线程(后台)
220        /// 

221        public System.Threading.Thread WordThread
222        {
223            get {return _workThread;}
224        }
225
226        /**//// 
227        /// 任务进度(0-100)
228        /// 

229        public int Progress
230        {
231            get {return _progress;}
232        }
233        /**//// 
234        /// 任务工作状态
235        /// 

236        public TaskStatus TaskState
237        {
238            get {return _taskState;}
239        }
240        /**//// 
241        /// 任务工作结果
242        /// 

243        public object Result
244        {
245            get {return _result;}
246        }
247
248        protected bool IsStop
249        {
250            get
251            {
252                bool result = false;
253                switch (_taskState)
254                {
255                    case TaskStatus.Stopped:
256                    case TaskStatus.Aborted:
257                    case TaskStatus.ThrowErrorStoped:
258                        result = true;
259                        break;
260                    default:
261                        break;
262                }
263                return result;
264            }
265360docimg_501_        }
266360docimg_502_        #endregion
267360docimg_503_
268360docimg_504_360docimg_505_        触发事件#region 触发事件
269360docimg_506_360docimg_507_        /**//// 
270360docimg_508_        /// 触发任务工作状态变化事件
271360docimg_509_        /// 

272360docimg_510_        /// 任务工作状态
273360docimg_511_        /// 任务工作结果对象
274360docimg_512_        protected void FireStatusChangedEvent(TaskStatus status, object result)
275360docimg_513_360docimg_514_        360docimg_515_{
276360docimg_516_            if( TaskStatusChanged != null )
277360docimg_517_360docimg_518_            360docimg_519_{
278360docimg_520_                TaskEventArgs args = new TaskEventArgs( status,result);
279360docimg_521_                AsyncInvoke(TaskStatusChanged,args);
280360docimg_522_            }
281360docimg_523_        }
282360docimg_524_
283360docimg_525_360docimg_526_        /**//// 
284360docimg_527_        /// 触发任务进度变化事件
285360docimg_528_        /// 

286360docimg_529_        /// 任务进度(0-100)
287360docimg_530_        /// 任务工作中间结果对象
288360docimg_531_        protected void FireProgressChangedEvent(int progress, object result)
289360docimg_532_360docimg_533_        360docimg_534_{
290360docimg_535_            if( TaskProgressChanged != null )
291360docimg_536_360docimg_537_            360docimg_538_{
292360docimg_539_                TaskEventArgs args = new TaskEventArgs( progress,result);
293360docimg_540_                AsyncInvoke(TaskProgressChanged,args);
294360docimg_541_            }
295360docimg_542_        }
296360docimg_543_360docimg_544_        /**//// 
297360docimg_545_        /// 触发工作方法执行中发现错误事件
298360docimg_546_        /// 

299360docimg_547_        /// 任务进度(0-100)
300360docimg_548_        /// 任务工作中间结果对象
301360docimg_549_        protected void FireThrowErrorEvent(int progress, object result)
302360docimg_550_360docimg_551_        360docimg_552_{
303360docimg_553_            if( TaskThrowError != null )
304360docimg_554_360docimg_555_            360docimg_556_{
305360docimg_557_                TaskEventArgs args = new TaskEventArgs( progress,result);
306360docimg_558_                AsyncInvoke(TaskThrowError,args);
307360docimg_559_            }
308360docimg_560_        }
309360docimg_561_360docimg_562_        /**//// 
310360docimg_563_        /// 触发被调用者取消事件
311360docimg_564_        /// 

312360docimg_565_        /// 任务进度(0-100)
313360docimg_566_        /// 任务工作中间结果对象
314360docimg_567_        protected void FireCancelEvent(int progress, object result)
315360docimg_568_360docimg_569_        360docimg_570_{
316360docimg_571_            if( TaskCancel != null )
317360docimg_572_360docimg_573_            360docimg_574_{
318360docimg_575_                TaskEventArgs args = new TaskEventArgs( progress,result);
319360docimg_576_                AsyncInvoke(TaskCancel,args);
320360docimg_577_            }
321360docimg_578_        }
322360docimg_579_360docimg_580_        /**//// 
323360docimg_581_        /// 触发被调用者强行中止事件
324360docimg_582_        /// 

325360docimg_583_        /// 任务进度(0-100)
326360docimg_584_        /// 任务工作中间结果对象
327360docimg_585_        protected void FireAbortEvent(int progress, object result)
328360docimg_586_360docimg_587_        360docimg_588_{
329360docimg_589_            if( TaskAbort != null )
330360docimg_590_360docimg_591_            360docimg_592_{
331360docimg_593_                TaskEventArgs args = new TaskEventArgs( progress,result);
332360docimg_594_                AsyncInvoke(TaskAbort,args);
333360docimg_595_            }
334360docimg_596_        }
335360docimg_597_360docimg_598_        /**//// 
336360docimg_599_        /// 异步调用挂接事件委托
337360docimg_600_        /// 

338360docimg_601_        /// 事件处理方法句柄
339360docimg_602_        /// 事件消息
340360docimg_603_        protected void AsyncInvoke(TaskEventHandler eventhandler,TaskEventArgs args)
341360docimg_604_360docimg_605_        360docimg_606_{
342360docimg_607_//            TaskEventHandler[] tpcs = (TaskEventHandler[])eventhandler.GetInvocationList();
343360docimg_608_            Delegate[] tpcs = eventhandler.GetInvocationList();
344360docimg_609_            foreach(TaskEventHandler tpc in tpcs)
345360docimg_610_360docimg_611_            360docimg_612_{
346360docimg_613_                if ( tpc.Target is System.Windows.Forms.Control )
347360docimg_614_360docimg_615_                360docimg_616_{
348360docimg_617_                    Control targetForm = tpc.Target as System.Windows.Forms.Control;
349360docimg_618_360docimg_619_                    targetForm.BeginInvoke( tpc,new object[] 360docimg_620_{ this, args } );
350360docimg_621_                }
351360docimg_622_                else
352360docimg_623_360docimg_624_                360docimg_625_{
353360docimg_626_                    tpc.BeginInvoke(this, args ,null,null); //异步调用,启动后不管
354360docimg_627_                }
355360docimg_628_            }
356360docimg_629_        }
357360docimg_630_        #endregion
358360docimg_631_
359360docimg_632_360docimg_633_        工作进程管理#region 工作进程管理
360docimg_634_360docimg_635_        /**//// 
361360docimg_636_        /// 开启任务默认的工作进程
362360docimg_637_        /// [public object Work(params  object[] args )]
363360docimg_638_        /// 

364360docimg_639_        /// 传入的参数数组
365360docimg_640_        public bool StartTask( params object[] args )
366360docimg_641_360docimg_642_        360docimg_643_{
367360docimg_644_            return StartTask(new TaskDelegate( Work ),args);
368360docimg_645_        }
369360docimg_646_360docimg_647_        /**//// 
370360docimg_648_        /// 开启任务的工作进程
371360docimg_649_        /// 将开启符合TaskDelegate委托接口的worker工作方法
372360docimg_650_        /// 

373360docimg_651_        /// 工作方法
374360docimg_652_        /// 传入的参数数组
375360docimg_653_        public bool StartTask(TaskDelegate worker ,params object[] args )
376360docimg_654_360docimg_655_        360docimg_656_{
377360docimg_657_            bool result =false;
378360docimg_658_            lock( this )
379360docimg_659_360docimg_660_            360docimg_661_{
380360docimg_662_                if( IsStop && worker != null )
381360docimg_663_360docimg_664_                360docimg_665_{
382360docimg_666_                    _result = null;
383360docimg_667_                    _callThread = System.Threading.Thread.CurrentThread;
384360docimg_668_                    // 开始工作方法进程,异步开启,传送回调方法
385360docimg_669_                    worker.BeginInvoke( args ,new AsyncCallback( EndWorkBack ), worker );
386360docimg_670_                    // 更新任务工作状态
387360docimg_671_                    _taskState = TaskStatus.Running;
388360docimg_672_                    // 触发任务工作状态变化事件
389360docimg_673_                    FireStatusChangedEvent( _taskState, null);
390360docimg_674_                    result = true;
391360docimg_675_                }
392360docimg_676_            }
393360docimg_677_            return result;
394360docimg_678_        }
395360docimg_679_360docimg_680_        /**//// 
396360docimg_681_        /// 请求停止任务进程
397360docimg_682_        /// 是否停止成功,应看任务工作状态属性TaskState是否为TaskStatus.Stop
398360docimg_683_        /// 

399360docimg_684_        public bool StopTask()
400360docimg_685_360docimg_686_        360docimg_687_{
401360docimg_688_            bool result =false;
402360docimg_689_            lock( this )
403360docimg_690_360docimg_691_            360docimg_692_{
404360docimg_693_                if( _taskState == TaskStatus.Running )
405360docimg_694_360docimg_695_                360docimg_696_{
406360docimg_697_                    // 更新任务工作状态
407360docimg_698_                    _taskState = TaskStatus.CancelPending;
408360docimg_699_                    // 触发任务工作状态变化事件
409360docimg_700_                    FireStatusChangedEvent( _taskState, _result);
410360docimg_701_                    result = true;
411360docimg_702_                }
412360docimg_703_            }
413360docimg_704_            return result;
414360docimg_705_        }
415360docimg_706_360docimg_707_        /**//// 
416360docimg_708_        /// 强行中止任务的工作线程
417360docimg_709_        ///
418360docimg_710_        /// 

419360docimg_711_        public bool AbortTask()
420360docimg_712_360docimg_713_        360docimg_714_{
421360docimg_715_            bool result = false;
422360docimg_716_            lock( this )
423360docimg_717_360docimg_718_            360docimg_719_{
424360docimg_720_                if( _taskState == TaskStatus.Running && _workThread != null )
425360docimg_721_360docimg_722_                360docimg_723_{
426360docimg_724_                    if (_workThread.ThreadState != System.Threading.ThreadState.Stopped)
427360docimg_725_360docimg_726_                    360docimg_727_{
428360docimg_728_                        _workThread.Abort();
429360docimg_729_                    }
430360docimg_730_                    System.Threading.Thread.Sleep(2);
431360docimg_731_                    if (_workThread.ThreadState == System.Threading.ThreadState.Stopped)
432360docimg_732_360docimg_733_                    360docimg_734_{
433360docimg_735_                        // 更新任务工作状态
434360docimg_736_                        _taskState = TaskStatus.Aborted;
435360docimg_737_                        result = true;
436360docimg_738_                    }
437360docimg_739_                    else
438360docimg_740_360docimg_741_                    360docimg_742_{
439360docimg_743_                        // 更新任务工作状态
440360docimg_744_                        _taskState = TaskStatus.AbortPending;
441360docimg_745_                        result = false;
442360docimg_746_                    }
443360docimg_747_                    // 触发任务工作状态变化事件
444360docimg_748_                    FireStatusChangedEvent( _taskState, _result);
445360docimg_749_                }
446360docimg_750_            }
447360docimg_751_            return result;
448360docimg_752_        }
449360docimg_753_
450360docimg_754_360docimg_755_        /**//// 
451360docimg_756_        /// 工作方法完成后的回调方法
452360docimg_757_        /// 将检查是否出错,并获取、更新返回结果值
453360docimg_758_        /// 

454360docimg_759_        /// 异步调用信号对象
455360docimg_760_        protected void EndWorkBack( IAsyncResult ar )
456360docimg_761_360docimg_762_        360docimg_763_{
457360docimg_764_            bool error = false;
458360docimg_765_            bool abort = false;
459360docimg_766_            try                                                //检查是否错误
460360docimg_767_360docimg_768_            360docimg_769_{
461360docimg_770_                TaskDelegate del = (TaskDelegate)ar.AsyncState;
462360docimg_771_                _result = del.EndInvoke( ar );
463360docimg_772_            }
464360docimg_773_            catch(Exception e)                                //如果错误,则保存错误对象
465360docimg_774_360docimg_775_            360docimg_776_{
466360docimg_777_                error = true;
467360docimg_778_                _exception = e;
468360docimg_779_                if (e.GetType() == typeof(System.Threading.ThreadAbortException))
469360docimg_780_360docimg_781_                360docimg_782_{
470360docimg_783_                    abort = true;
471360docimg_784_                    FireAbortEvent(_progress,_exception);
472360docimg_785_                }
473360docimg_786_                else
474360docimg_787_360docimg_788_                360docimg_789_{
475360docimg_790_                    FireThrowErrorEvent(_progress,_exception);
476360docimg_791_                }
477360docimg_792_            }
478360docimg_793_            lock( this )
479360docimg_794_360docimg_795_            360docimg_796_{
480360docimg_797_                if (error)
481360docimg_798_360docimg_799_                360docimg_800_{
482360docimg_801_                    if ( abort)
483360docimg_802_360docimg_803_                    360docimg_804_{
484360docimg_805_                        _taskState = TaskStatus.Aborted;        //调用者强行中止
485360docimg_806_                    }
486360docimg_807_                    else
487360docimg_808_360docimg_809_                    360docimg_810_{
488360docimg_811_                        _taskState = TaskStatus.ThrowErrorStoped;//出现错误而中止
489360docimg_812_                    }
490360docimg_813_                }
491360docimg_814_                else
492360docimg_815_360docimg_816_                360docimg_817_{    _taskState = TaskStatus.Stopped;}          //正常结束
493360docimg_818_                FireStatusChangedEvent( _taskState, _result);
494360docimg_819_            }
495360docimg_820_        }
496360docimg_821_        #endregion
497360docimg_822_
498360docimg_823_360docimg_824_        工作方法的基础#region 工作方法的基础
499360docimg_825_360docimg_826_        /**//// 
500360docimg_827_        /// 工作方法
501360docimg_828_        /// 在继承类中应重写(override)此方法,以实现具体的工作内容,注意以几点:
502360docimg_829_        /// 1.须在继承类是引用base.Work,在基类(base)的Work方法中,执行线程设为IsBackground=true,并保存工作线程对象
503360docimg_830_        /// 2.在继承类中,应及时更新_progress与_result对象,以使Progress和Result属性值正确
504360docimg_831_        /// 3.在执行过程中应检查_taskState,以使任务中被请求停止后(_taskState为TaskStatus.CancelPending),工作线程能最快终止.
505360docimg_832_        /// 4.如在继承类中新定义了事件,应在此方法中引用触发
506360docimg_833_        /// 5.工作线程状态不由工作方法管理,所以在工作方法中不应改变_taskState变量值
507360docimg_834_        /// 6.工作方法中应对args参数进行有效检查
508360docimg_835_        /// 

509360docimg_836_        /// 传入的参数数组
510360docimg_837_        /// 返回null
511360docimg_838_        virtual public object Work(params  object[] args )
512360docimg_839_360docimg_840_        360docimg_841_{
513360docimg_842_            System.Threading.Thread.CurrentThread.IsBackground = true;
514360docimg_843_            _workThread = System.Threading.Thread.CurrentThread;
515360docimg_844_            _result = null;
516360docimg_845_            return null;
517360docimg_846_        }
518360docimg_847_
519360docimg_848_        #endregion
520360docimg_849_    }
521360docimg_850_}
522360docimg_851_
523360docimg_852_360docimg_853_使用Task类#region 使用Task类
524360docimg_854_360docimg_855_/**//*
525360docimg_856_
526360docimg_857_使用 Task 类
527360docimg_858_
528360docimg_859_一.在UI线程中创建Task类
529360docimg_860_
530360docimg_861_Task 类负责管理后台线程。要使用 Task 类,必须做的事情就是创建一个 Task 对象,注册它激发的事件,并且实现这些事件的处理。因为事件是在 UI 线程上激发的,所以您根本不必担心代码中的线程处理问题。
531360docimg_862_
532360docimg_863_下面的示例展示了如何创建 Task 对象。现假设UI 有两个按钮,一个用于启动运算,一个用于停止运算,还有一个进度栏显示当前的计算进度。
533360docimg_864_
534360docimg_865_// 创建任务管理对象
535360docimg_866__Task = new Task();
536360docimg_867_// 挂接任务管理对象工作状态变化事件
537360docimg_868__Task.TaskStatusChanged += new TaskEventHandler( OnTaskStatusChanged );
538360docimg_869_// 挂接任务管理对象工作进度变化事件
539360docimg_870__Task.TaskProgressChanged += new TaskEventHandler( OnTaskProgressChanged );
540360docimg_871_
541360docimg_872_(1)
542360docimg_873_用于计算状态和计算进度事件的事件处理程序相应地更新 UI,例如通过更新状态栏控件。
543360docimg_874_
544360docimg_875_private void OnTaskProgressChanged( object sender,TaskEventArgs e )
545360docimg_876_{
546360docimg_877_    _progressBar.Value = e.Progress;
547360docimg_878_}
548360docimg_879_(2)
549360docimg_880_下面的代码展示的 TaskStatusChanged 事件处理程序更新进度栏的值以反映当前的计算进度。假定进度栏的最小值和最大值已经初始化。
550360docimg_881_
551360docimg_882_private void OnTaskStatusChanged( object sender, TaskEventArgs e )
552360docimg_883_{
553360docimg_884_    switch ( e.Status )
554360docimg_885_    {
555360docimg_886_        case TaskStatus.Running:
556360docimg_887_            button1.Enabled = false;
557360docimg_888_            button2.Enabled = true;
558360docimg_889_            break;
559360docimg_890_        case TaskStatus.Stop:
560360docimg_891_            button1.Enabled = true;
561360docimg_892_            button2.Enabled = false;
562360docimg_893_            break;
563360docimg_894_        case TaskStatus.CancelPending:
564360docimg_895_            button1.Enabled = false;
565360docimg_896_            button2.Enabled = false;
566360docimg_897_            break;
567360docimg_898_    }
568360docimg_899_}
569360docimg_900_
570360docimg_901_在这个示例中,TaskStatusChanged 事件处理程序根据计算状态启用和禁用启动和停止按钮。这可以防止用户尝试启动一个已经在进行的计算,并且向用户提供有关计算状态的反馈。
571360docimg_902_
572360docimg_903_通过使用 Task 对象中的公共方法,UI 为每个按钮单击实现了窗体事件处理程序,以便启动和停止计算。例如,启动按钮事件处理程序调用 StartTask 方法,如下所示。
573360docimg_904_
574360docimg_905_private void startButton_Click( object sender, System.EventArgs e )
575360docimg_906_{
576360docimg_907_    _Task.StartTask( new object[] {} );
577360docimg_908_}
578360docimg_909_
579360docimg_910_类似地,停止计算按钮通过调用 StopTask 方法来停止计算,如下所示。
580360docimg_911_
581360docimg_912_private void stopButton_Click( object sender, System.EventArgs e )
582360docimg_913_{
583360docimg_914_    _Task.StopTask();
584360docimg_915_}
585360docimg_916_
586360docimg_917_二.可能在非UI线程中使用Task类时
587360docimg_918_(1)和(2)应作如下改变
588360docimg_919_
589360docimg_920_(1)
590360docimg_921_用于计算状态和计算进度事件的事件处理程序相应地更新 UI,例如通过更新状态栏控件。
591360docimg_922_
592360docimg_923_private void OnTaskProgressChanged( object sender,TaskEventArgs e )
593360docimg_924_{
594360docimg_925_    if (InvokeRequired )        //不在UI线程上,异步调用
595360docimg_926_    {
596360docimg_927_        TaskEventHandler TPChanged = new TaskEventHandler( OnTaskProgressChanged );
597360docimg_928_        this.BeginInvoke(TPChanged,new object[] {sender,e});
598360docimg_929_    }
599360docimg_930_    else                        //更新
600360docimg_931_    {
601360docimg_932_        _progressBar.Value = e.Progress;
602360docimg_933_    }
603360docimg_934_}
604360docimg_935_(2)
605360docimg_936_下面的代码展示的 TaskStatusChanged 事件处理程序更新进度栏的值以反映当前的计算进度。假定进度栏的最小值和最大值已经初始化。
606360docimg_937_
607360docimg_938_private void OnTaskStatusChanged( object sender, TaskEventArgs e )
608360docimg_939_{
609360docimg_940_    if (InvokeRequired )        //不在UI线程上,异步调用
610360docimg_941_    {
611360docimg_942_        TaskEventHandler TSChanged = new TaskEventHandler( OnTaskStatusChanged );
612360docimg_943_        this.BeginInvoke(TSChanged,new object[] {sender,e});
613360docimg_944_    }
614360docimg_945_    else                        //更新
615360docimg_946_    {
616360docimg_947_        switch ( e.Status )
617360docimg_948_        {
618360docimg_949_            case TaskStatus.Running:
619360docimg_950_                button1.Enabled = false;
620360docimg_951_                button2.Enabled = true;
621360docimg_952_                break;
622360docimg_953_            case TaskStatus.Stop:
623360docimg_954_                button1.Enabled = true;
624360docimg_955_                button2.Enabled = false;
625360docimg_956_                break;
626360docimg_957_            case TaskStatus.CancelPending:
627360docimg_958_                button1.Enabled = false;
628360docimg_959_                button2.Enabled = false;
629360docimg_960_                break;
630360docimg_961_        }
631360docimg_962_    }
632360docimg_963_}
633360docimg_964_
634360docimg_965_*/
635360docimg_966_#endregion
636360docimg_967_
三、示例
1.启动时的UI界面
360docimg_968_
2.后台工作方法(费用方法)运行后,任务状态为Running
360docimg_969_
3.强制中止工作方法,运行任务状态Aborted
360docimg_970_
4.工作方法突发错误时,任务状态ThrowErrorStoped
360docimg_971_
5.工作方法正常结束或正常取消而结束时,任务状态Stopped
360docimg_972_
示例代码下载
蛙蛙推荐:多进程多线程访问数据库
蛙蛙推荐:多进程多线程访问数据库
如何让多进程多线程访问数据库,而不会选择相同的数据,这在设计分布式程序的时候经常用到,多台机器的多个进程,每个进程都有多个线程,每个线程要从数据库里取数据来处理,要实现不能漏取数据,也不能重复取数据,这里给出答案
创建一个数据表,如下,一个自增列,一个表示rss链接地址
360docimg_973_CREATE TABLE [dbo].[Rss_RssSources](
360docimg_974_ [SourceId] [int] IDENTITY(1,1) NOT NULL,
360docimg_975_ [Link] [varchar](1024) NOT NULL
360docimg_976_) ON [PRIMARY]
先放1w条数据
360docimg_977_declare @i int
360docimg_978_set @i = 1
360docimg_979_while @i <10000
360docimg_980_begin
360docimg_981_ select @i = @i +1
360docimg_982_ insert into [Rss_RssSources] values(newid())
360docimg_983_end
再创建一个锁表,一个字段表示是否已经锁定的资源,另一个表示已经读取的rss源的最大id
360docimg_984_create table Rss_RssSourceLock
360docimg_985_(
360docimg_986_IsLock bit,
360docimg_987_MaxSourceId int
360docimg_988_)
初始化数据
360docimg_989_insert into Rss_RssSourceLock values (0,0)
下面我们要设计一个存储过程,让这个存储过程每次返回10个rss源,知道返回所有的rss源,要求无遗漏,无重复返回。如下
360docimg_990_CREATE PROCEDURE [dbo].[USP_GetRssSources]
360docimg_991_AS
360docimg_992_BEGIN
360docimg_993_if exists(select * from Rss_RssSourceLock with(READPAST) where IsLock = 0)
360docimg_994_begin
360docimg_995_ declare @select_count int
360docimg_996_ begin tran
360docimg_997_  update Rss_RssSourceLock set IsLock = 1
360docimg_998_
360docimg_999_  if object_id('tempdb..#t') is not null
360docimg_1000_   drop table #t
360docimg_1001_
360docimg_1002_  select top 10 a.* into #t from [Rss_RssSources] as a
360docimg_1003_  inner join Rss_RssSourceLock as b
360docimg_1004_  on a.SourceId > b.MaxSourceId
360docimg_1005_  order by a.[SourceId]
360docimg_1006_
360docimg_1007_  select @select_count = count(*) from #t
360docimg_1008_
360docimg_1009_  update Rss_RssSourceLock set IsLock = 0,MaxSourceId = MaxSourceId + @select_count
360docimg_1010_
360docimg_1011_  select * from #t
360docimg_1012_ commit tran
360docimg_1013_end
360docimg_1014_END
360docimg_1015_
360docimg_1016_
1、如果锁表里显示没有进程正在读取rss源(IsLock = 0),那么就返回从最大的rss源id往后的10个rss源,否则返回空。
2、用with(READPAST)表示忽略锁住的行,如果另一个进程正在执行update Rss_RssSourceLock的语句,并且在事务提交前,update语句会锁住这些要更新的行,而Rss_RssSourceLock表就一行数据,这时候select Rss_RssSourceLock表并且忽略被锁的行肯定是没数据的,所以本次存储过程执行会返回空。
3、begin tran和commit tran保证了即使本次存储过程出错,也不会让Rss_RssSourceLock表处于IsLock = 1的脏数据状态,如果处于这种状态,后面的进程执行存储过程就永远也返回不了数据了。
4、因为有时候一次选取的记录可能不够10条,所以这里用了个临时表来暂存记录,再算出来选取的条数,最后更新Rss_RssSourceLock表的MaxSourceId字段。但用临时表肯定会增加数据库的压力,这里不知道用表变量是不是会改善性能,暂时先这样了。
5、应用里调用这个存储过程,如果返回了数据,就进行处理,如果没返回数据,就sleep几秒才执行,直到返回数据。
我测试了一下,应该没问题,俺是数据库菜鸟,有高手的话给指点指点有没有隐患和bug
相关链接:
关于sqlserver锁的一点儿讨论
http://topic.csdn.net/t/20061127/12/5187714.html#
Net多线程总结(二)-BackgroundWorker
上篇文章介绍了多种线程的创建方式,以及winform在多线程编程时的特殊性,这篇我们来介绍一下异步编程的经典模式和微软对其的实现
微软推荐的异步操作模型是事件模型,也即用子线程通过事件来通知调用者自己的工作状态,也就是设计模式中的observer模式,也可以看成是上文中线程类的扩展,最后实现后调用效果类似于
 
MyThread thread=new MyThread()
thread.Work+=new ThreadWork(Calculate)
thread.WorkComplete+=new WorkComplete(DisplayResult)
Calculate(object sender, EventArgs e)){
....
}
DisplayResult(object sender, EventArgs e)){
...
}
<例一>
这个话题已经有许多很好的文章,大家参考http://www.cnblogs.com/net66/archive/2005/08/03/206132.html,其作者在文章后附加有示例项目,项目中的线程类实现了事件发送,线程终止,报告任务进度等一系列必要的功能,大家可以自己去查看代码,我就不赘述了,我主要谈微软对这个模式的实现BackgroundWorker
上篇文章里说到了控制权的问题,上面的模型在winform下使用有个问题就是执行上下文的问题,在回调函数中(比如<例一>中的DisplayResult中),我们不得不使用BeginInvoke,才能调用ui线程创建的控件的属性和方法,
比如在上面net66的例子里
 
//创建线程对象 _Task = new newasynchui();
//挂接进度条修改事件 _Task.TaskProgressChanged += new TaskEventHandler( OnTaskProgressChanged1 );
//在UI线程,负责更新进度条 private void OnTaskProgressChanged1( object sender,TaskEventArgs e )
{
if (InvokeRequired ) //不在UI线程上,异步调用 {
TaskEventHandler TPChanged1 = new TaskEventHandler( OnTaskProgressChanged1 );
this.BeginInvoke(TPChanged1,new object[] {sender,e});
Console.WriteLine("InvokeRequired=true");
}
else { progressBar.Value = e.Progress;
}
}
<例二>
可以看到,在函数里面用到了
if(InvokeRequired)
{...BeginInvoke....}
else
{....}
这个模式来保证方法在多线程和单线程下都可以运行,所以线程逻辑和界面逻辑混合在了一起,以至把以前很简单的只需要一句话的任务:progressBar.Value = e.Progress;搞的很复杂,如果线程类作为公共库来提供,对编写事件的人要求会相对较高,那么有什么更好的办法呢?
其实在.Net2.0中微软自己实现这个模式,制作了Backgroundworker这个类,他可以解决上面这些问题,我们先来看看他的使用方法
 
System.ComponentModel.BackgroundWorker bw = new System.ComponentModel.BackgroundWorker();
//定义需要在子线程中干的事情 bw.DoWork += new System.ComponentModel.DoWorkEventHandler(bw_DoWork);
//定义执行完毕后需要做的事情 bw.RunWorkerCompleted += new System.ComponentModel.RunWorkerCompletedEventHandler(bw_RunWorkerCompleted);
//开始执行 bw.RunWorkerAsync();
static void bw_RunWorkerCompleted(object sender, System.ComponentModel.RunWorkerCompletedEventArgs e)
{
MessageBox.Show("Complete"+Thread.CurrentThread.ManagedThreadId.ToString());
}
static void bw_DoWork(object sender, System.ComponentModel.DoWorkEventArgs e)
{
MessageBox.Show(Thread.CurrentThread.ManagedThreadId);
}
<例三>
注意我在两个函数中输出了当前线程的ID,当我们在WindowsForm程序中执行上述代码时,我们惊奇的发现,bw_RunWorkerCompleted这个回调函数居然是运行在UI线程中的,也就是说在这个方法中我们不用再使用Invoke和BeginInvoke调用winform中的控件了, 更让我奇怪的是,如果是在ConsoleApplication中同样运行这段代码,那么bw_RunWorkerCompleted输出的线程id和主线程id就并不相同.
那么BackgroundWorker到底是怎么实现跨线程封送的呢?
阅读一下这个类的代码,我们发现他借助了AsyncOperation.Post(SendOrPostCallback d, object arg)
在winform下使用这个函数,就可以使得由SendOrPostCallback定义被封送会UI线程,聪明的博友可以用这个方法来实现自己的BackgroundWorker.
继续查看下去,发现关键在于AsyncOperation的syncContext字段,这是一个SynchronizationContext类型的对象,而这个对象的Post方法具体实现了封送,当我继续查看
SynchronizationContext.Post方法时,里面简单的令人难以执行
public virtual void Post(SendOrPostCallback d, object state)
{
ThreadPool.QueueUserWorkItem(new WaitCallback(d.Invoke), state);
}
这是怎么回事情呢,线程池本省并不具备线程封送的能力啊 联想到在Winform程序和Console程序下程序的行为是不同的,而且SynchronizationContext的Post方法是一个virtual方法,我猜测这个方法可能被继承自他的类重写了 查询Msdn,果然发现在这个类有两个子类,其中一个就是WindowsFormsSynchronizationContext,我们来看看这个类的Post方法 public override void Post(SendOrPostCallback d, object state)
{
if (this.controlToSendTo != null)
{
this.controlToSendTo.BeginInvoke(d, new object[] { state });
}
}
哈哈,又是熟悉的beginInvoke,原来控制台程序和Winform程序加载的SynchronizationContext是不同的,所以行为才有所不同,通过简单的测试,我们可以看到控制台程序直接使用基类(SynchronizationContext),而winform程序使用这个WindowsFormsSynchronizationContext的Post方法把方法调用封送到控件的线程.
总结:
同事这个类还提供了进度改变事件,允许用户终止线程,功能全面,内部使用了线程池,能在一定成都上避免了大量线程的资源耗用问题,并通过SynchronizationContext解决了封送的问题,让我们的回调事件代码逻辑简单清晰,推荐大家使用.
多线程编程实战(一)
多线程编程实战(一)
为了快速处理大量的任务,多线程编程必不可少,所以最近也开始研究多线程方面的东西了。网上有不少资料可以参考,自己从实例入手,总结了一些东西,把它记录下来,方便自己日后查阅,也方便后来人。现在的编程语言,入门第一课,都是写一个Hello Word的程序。本文类似,虽说不是hello Word,但也是从最简单的多线程实例入手。
我们最常用到多线程的场景一般是这样的,有一个很大的任务需要处理,而这个大的任务有可以细分为许多的小任务(子任务),这些小任务可以采用多线程机制,并行执行。而一般来说,这个大任务所包含的小任务的个数可能不是太确定。
根据以上场景,我们对其进行归纳:
1. 一个大任务,内部包含了数量不定的子任务
2. 需要对子任务进行多线程处理
OK, 下面我们开始着手用C#实现对此实例的多线程实现。我的解决方案是:
1. 对象化大任务,在初始化大任务的实例(Instance)时,初始化(确定)子任务
2. 采用多线程,调用(Invoke)大任务的实例(Instance)中定义的处理子任务的方法
3. 多线程处理子任务时,定义一个全局的标志,记录但前处理到了哪一个,保证不重复处理子任务;
基于此方案,我们的“大任务”类如下:
360docimg_1017_
外部多线程实现如下:
360docimg_1018_
在Console模式下,其结果显示如下:
360docimg_1019_
由此可见,线程3、4、5、6共4个线程一起处理了这些子任务。代码不是很复杂,只做简单说明:
1. 默认的命名空间为System.Threading, 线程相关的东西都在这个namespace下;
2. new Random((int)DateTime.Now.Ticks).Next(30, 50):初始化一个随机数的实例(instance),然后生产一个在30到50之间的随机数。这里的目的是假设我们在初始化任务实例时,不确定子任务的个数。后面还有new Random((int)DateTime.Now.Ticks).Next(100, 300),目的是架设处理子任务的时间在100到300毫秒之间不定
3. Thread th = new Thread(new ThreadStart(tester.TestOutput));
th.Start(); 这两句可是线程编程的核心,即初始化一个线程实例,然后开始执行!
4. Thread.CurrentThread.GetHashCode():这个可以返回线程的一个哈希值(HashCode),此例中没有实际意义,只用于区分不同的线程
5. private int flag = 0:作为一个标记使用,记录但前执行到那个子任务了。各个子线程均能够访问到。
6. public int MaxOutPut:记录总的任务数。
Console.Write(“Hello Word”) J 一个简单的多线程实例就是这么easy,当然,这只是最简单、最初步的一个实例,还有很多需要注意、优化的东西,也有一些bug,我们下文接着说。
..待续..
多线程并发测试类库
WEB项目中除了单元测试,还经常需要多线程测试一个方法是否存在并发问题,或者是否有性能问题。每次都要写测试代码总是一件很累的事情。于是写了这一个多线程测试的类库,用来进行快速的多线程并发测试。
多线程并发测试时,需要等所有线程测试结束后通知主线程,主线程才能进行下一步动作,这里主要用到了ManualResetEvent。ManualResetEvent 类表示一个本地等待处理事件,在已发事件信号后必须手动重置该事件。通常,此通信涉及一个线程在其他线程进行之前必须完成的任务。当一个线程开始一个活动(此活动必须完成后,其他线程才能开始)时,它调用 Reset 以将 ManualResetEvent 置于非终止状态。此线程可被视为控制 ManualResetEvent。调用 ManualResetEvent 上的 WaitOne 的线程将阻止,并等待信号。当控制线程完成活动时,它调用 Set 以发出等待线程可以继续进行的信号。并释放所有等待线程。一旦它被终止,ManualResetEvent 将保持终止状态,直到它被手动重置。即对 WaitOne 的调用将立即返回。可以通过将布尔值传递给构造函数来控制 ManualResetEvent 的初始状态,如果初始状态处于终止状态,为 true;否则为 false。
多线程并发测试由以下步骤完成:
创建并发测试的线程数,先创建的线程等待最后一个线程创建完成。 所有线程执行待测试的方法,返回测试的结果。 等所有线程执行完成后,进入思考时间等待。 继续进行循环测试。
我们来看这个多线程并发测试的代码。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading;
///
/// 并发测试
///

public class ConcurrentTest : IDisposable
{
#region 私有方法
///
/// 测试方法所在的接口
///

private Func func;
///
/// 主线程控制信号
///

private ManualResetEvent manualResetEvent;
///
/// 测试线程控制信号
///

private ManualResetEvent threadResetEvent;
///
/// 待执行的线程数
///

private List threads;
///
/// 测试结果
///

private List results;
///
/// 执行测试的成功数
///

private int successCount;
///
/// 执行测试的失败数
///

private int failureCount;
///
/// 测试耗时
///

private long elapsedMilliseconds;
///
/// 当前线程
///

private int currentIndex;
///
/// 当前测试的总线程数
///

private int currentCount;
///
/// 思考时间
///

private int thinkTime;
///
/// 重复次数
///

private int repeatCount;
///
/// 测试计时器
///

private Stopwatch stopwatch;
#endregion
#region 构造函数
///
/// 构造函数
///

public ConcurrentTest()
{
manualResetEvent = new ManualResetEvent(true);
threadResetEvent = new ManualResetEvent(true);
stopwatch = new Stopwatch();
}
#endregion
#region 执行测试
///
/// 执行多线程测试
///

/// 需要测试的线程数
/// 待执行方法
///
public List Execute(int threadCount, Func func)
{
return Execute(threadCount, 1, func);
}
///
/// 执行多线程测试
///

/// 需要测试的线程数
/// 重复次数
/// 待执行方法
///
public List Execute(int threadCount, int repeatCount, Func func)
{
return Execute(threadCount, 0, repeatCount, func);
}
///
/// 执行多线程测试
///

/// 需要测试的线程数
/// 思考时间,单位耗秒
/// 重复次数
/// 待执行方法
///
public List Execute(int threadCount, int thinkTime, int repeatCount, Func func)
{
return Execute(new List() { threadCount }, thinkTime, repeatCount, func);
}
///
/// 执行多线程测试
///

/// 分别需要测试的线程数
/// 思考时间,单位耗秒
/// 重复次数
/// 待执行方法
///
public List Execute(List threads, int thinkTime, int repeatCount, Func func)
{
this.func = func;
this.threads = threads;
this.thinkTime = thinkTime;
this.repeatCount = repeatCount;
CheckParameters();
CreateMultiThread();
return this.results;
}
#endregion
#region 验证参数
///
/// 验证参数
///

private void CheckParameters()
{
if (func == null) throw new ArgumentNullException("func不能为空");
if (threads == null || threads.Count == 0) throw new ArgumentNullException("threads不能为空或者长度不能为0");
if (thinkTime < 0) throw new Exception("thinkTime不能小于0");
if (repeatCount <= 0) throw new Exception("repeatCount不能小于等于0");
}
#endregion
#region 创建多线程并执行测试
///
/// 创建多线程进行测试
///

private void CreateMultiThread()
{
results = new List(threads.Count);
foreach (int threadCount in threads)
{
for (int repeat = 0; repeat < repeatCount; repeat++)
{
//主线程进入阻止状态
manualResetEvent.Reset();
//测试线程进入阻止状态
threadResetEvent.Reset();
stopwatch.Reset();
currentCount = threadCount;
currentIndex = 0;
successCount = 0;
failureCount = 0;
elapsedMilliseconds = 0;
for (int i = 0; i < currentCount; i++)
{
Thread t = new Thread(new ThreadStart(DoWork));
t.Start();
}
//阻止主线程,等待测试线程完成测试
manualResetEvent.WaitOne();
results.Add(new ConcurrentTestResult()
{
FailureCount = failureCount,
SuccessCount = successCount,
ElapsedMilliseconds = elapsedMilliseconds
});
Thread.Sleep(thinkTime);
}
}
}
///
/// 执行测试方法
///

private void DoWork()
{
bool executeResult;
Interlocked.Increment(ref currentIndex);
if (currentIndex < currentCount)
{
//等待所有线程创建完毕后同时执行测试
threadResetEvent.WaitOne();
}
else
{
//最后一个线程创建完成,通知所有线程,开始执行测试
threadResetEvent.Set();
//开始计时
stopwatch.Start();
}
//执行测试
executeResult = func();
Interlocked.Decrement(ref currentIndex);
if (currentIndex == 0)
{
//最后一个线程执行的测试结束,结束计时
stopwatch.Stop();
elapsedMilliseconds = stopwatch.ElapsedMilliseconds;
//保存测试结果
if (executeResult)
Interlocked.Increment(ref successCount);
else
Interlocked.Increment(ref failureCount);
//通知主线程继续
manualResetEvent.Set();
}
else
{
//保存测试结果
if (executeResult)
Interlocked.Increment(ref successCount);
else
Interlocked.Increment(ref failureCount);
}
}
#endregion
#region 释放资源
///
/// 释放资源
///

public void Dispose()
{
manualResetEvent.Close();
threadResetEvent.Close();
}
#endregion
}
///
/// 并发测试结果
///

public class ConcurrentTestResult
{
///
/// 当前执行线程总数
///

public int ThreadCount
{
get { return SuccessCount + FailureCount; }
}
///
/// 测试成功数
///

public int SuccessCount { get; set; }
///
/// 测试失败数
///

public int FailureCount { get; set; }
///
/// 总耗时
///

public long ElapsedMilliseconds { get; set; }
}
使用起来就非常简单了,我们看测试代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
class Program
{
static void Main(string[] args)
{
using (ConcurrentTest concurrentTest = new ConcurrentTest())
{
var result = concurrentTest.Execute(5, -1, 10, new TestClass().Execute);
foreach (var item in result)
{
Console.WriteLine("线程数:{0}\t成功:{1}\t失败:{2}\t耗时:{3}",
item.ThreadCount, item.SuccessCount, item.FailureCount, item.ElapsedMilliseconds);
}
}
Console.ReadKey(true);
}
}
public class TestClass
{
public bool Execute()
{
int tempValue = GetRandom();
System.Threading.Thread.Sleep(tempValue);
return tempValue % 2 == 0;
}
private int GetRandom()
{
return new Random().Next(990, 1000);
}
}
测试类库提供了4个Execute方法的重载,一般情况下能满足我们的多线程并发测试场景了。
Web中使用多线程来增强用户体验
前几天遇到了一个问题,我在页面逻辑里需要调用一个webservice,处理一个比较耗时的操作,但是我不需要知道其返回值。于是我希望asp.net能像winform一样使用自动生成的webservice异步方法
你是不是想说:在页面调用webservice的时候,直接调用其异步实现不就完了吗?
这其实是行不通的,为了实现异步调用,我们需要对页面进行小小的改动,在Page元素里加上Async=true
我们很快就会发现这样做的问题:
让我们测试一下吧,现在我们在一个webservice的Helloworld方法中放入一个Thread。Sleep(10000),然后调用他的异步实现。通过调试,我们可以发现虽然程序运行至HelloworldAsync时,非常快速的返回并往下运行,但是当所有逻辑处理完成后,页面并不Response,而是硬生生等待我们的线程睡醒了才返回。
可是如果我希望真正做到调了不管怎么办呢?
360docimg_1020_
你可以使用Thread,或者ThreadPool,自己来启动一个线程,我推荐使用ThreadPool,这样的话,这些线程都会被iis的线程池管理起来,不会造成崩溃
我们来分析一下这两种模式的运用有什么特点
WebService自带的异步模式为下图的模式
360docimg_1021_
主线程调用子线程执行一个耗时操作(work1),同时执行一系列同步操作(w2...w5),然后交给w1返回
这种模式适合于work1有返回的情况,并且为了让work1得到充分的工作时间,异步调用的过程开始的越早越好,对web程序设计者而言,这里有一个很重要的问题:线程占用。。
刚才我们谈过,asp.net中每个请求都会有有一个线程来处理,而可以使用的线程是有限的,服务器会使用一个线程池来管理线程,当线程耗尽,ok,新来的请求只能蹲着排队,所以对web开发者而言,线程是个宝贵的资源,所以这个方案在并行处理的同时也增加了耗尽线程池的风险,毕竟一个请求造成了多个线程
用线程池来实现的模式属于下图
360docimg_1022_
这种模式适合无返回的情况,这种情况下,对子线程的调用应该越晚越好,我们可以看到,主、子线程共存的时间越短,我们的稀缺资源线程就越安全,请注意的是,也许总的执行时间不会比同步的情况更少,但是我们很快就返回了用户界面,所以用户体验能够得到提高
使用web多线程的缺点 :
看了上面的叙述,你也许会说,那干脆把我所有的调用都改成异步调用吧,你尽管去做吧,绝对是一场灾难,因为在异步的同时,一定一会产生一个新的线程等待调用的返回,即使你调用函数的返回值为void,所以异步调用的负面效果将是会产生许多子线程,所以注意当你的调用非常耗时,这个子线程也将长期占用你的线程池,如果这样的调用大量出现,照样会消耗掉所有的可用线程
那么什么情况下适合在web上使用哪种多线程模式呢
我们来看看这段伪代码,他的用途是提交一个报告,方法传入一个报告,并从一个WebService中获得一些报告的内容,接着插入数据库,然后在文件服务器上生成一个报告文件,最后发出一个通知,让我们逐条命令的过一下这个方法,看看什么地方适合改为异步调用?(记得我们的讨论都是基于web的,关于桌面运用的多线程请参考 多线程总结一)
public void CreateReport(Report report){
//从webservice上取得报告的一些信息,不取得这些信息报告,报告是不完整的,是不能提交的
Report fullreport=CallWebService(report);
//插入数据库,很重要的工作
InsertIntoDataBase(fullreport)
try{
//生成报告文件,这里是一个耗时而且容易出错的操作
WriteStaticFile(fullreport)
}
catch{//记录错误日志。。。。}
//这个只是通知邮件
CallMailService2(fullreport)
}
第一条语句CallWebService()从一个webservice里加载一些报告的内容,这个是业务逻辑相关的,因为如果不加载的话报告内容是不完整的,不能提交,显然不能改为异步调了不管的模式,在这里你可以尝试模式一,但是这个改动是没有作用的,因为其他所有的过程,包括插入数据库,生成报告都依赖于这个方法的返回,所以如果我们在这里使用异步的话,其他的所有操作都必须等待他的返回,所以采用异步除了多增加了线程以外,一点时间也不能节省
再来看插入数据库,和上面一样也没有必要使用异步调用
生成报告这里比较有趣,确实他是一个和逻辑息息相关的操作,但是通过分析代码,我们可以看出,虽然报告生成是一个重要业务步骤,但是并没有严格到说"如果不能生成报告,就必须回滚上面的操作",并且如果操作失败,在catch中也仅仅是记录了日志,并没有需要尝试重写的逻辑,(很有可能另外的某个程序或者某人,会定时查看日志,发现有错误就重新生成文件)也就是说,就这段代码而言,生成也可以算一个额外逻辑,那么自然也可以去异步操作.可是:千万注意!!
由于生成报告需要的时间较长,那么生成报告的子线程会长时间运行,长期无法返回线程池,如果请求量太大,频率太快,那就会耗尽线程资源了.
平心而论,这个问题其实不是异步造成的,即使时同步调用,执行此操作也需要化肥很长时间,调用量太大,频率太快,也会造成排队.而且由于返回时间太长,用户体验也不会好,所以我们的这个改造应该是有益的
(注:关于报告生成,我在与一个同事讨论这种思想的时候,他就认为这个地方应该有一个写入队列,因为显然生成文件的速度和其他处理速度是不匹配的,这确实是一个比较合理的做法)
C#多线程与UI响应
一.            概述
在使用C#进行应用程序设计时,经常会采用多线程的方式进行一些后台任务的工作。对于不同的应用场景,使用的策略也不尽相同。
1.      后台循环任务,少量UI更新:例如批量上传文件,并提供进度。这种情况使用BackgroundWorker组件是非常好的选择。
2.      耗时的后台任务:这里的耗时任务是指一个时间较长的任务,并且不能精确获取进度,如:调用一个远程WebService接口。这种情况可以开两个线程,一个工作,一个更新UI(不能提供进度,只能显示动画表示系统在运行中)。
3.      耗时的UI任务:当工作压力集中在UI响应上时,可以在工作者线程中增加延时,从而让UI线程获得响应时间。整个工作的总体时间会增加,但用户响应效果会好很多。
二.            后台的循环任务,少量UI更新
这种情况使用BackgroundWorker组件是最好的选择。(详见附一)
三.            后台耗时任务
在后台执行一个不可分解的耗时任务,需要进行界面更新,以便让客户看上去程序有所响应。这种情况下,UI线程一般也不知道工作线程何时结束,所以一般执行循环任务,当工作线程结束后,关闭UI线程就可以了。
Thread uithread = null;
private void btnStart_Click(object sender, EventArgs e)
{
uithread = new Thread(new ThreadStart(this.UpdateProgressThread));
uithread.Start();
Thread workthread = new Thread(new ThreadStart(this.DoSomething));
workthread.Start();
}
private void DoSomething()
{
Thread.Sleep(5000);
uithread.Abort();
MessageBox.Show("work end");
}
private void UpdateProgressThread()
{
for (int i = 0; i < 10000; i++)
{
Thread.Sleep(100);
this.Invoke(new Action(this.UpdateProgress), i);
}
}
private void UpdateProgress(int v)
{
this.progressBar1.Value = v;
}
这里只要注意一点:线程调用的方法都不能访问用户控件,必须通过委托调用Form的方法来实现界面更新。
四.            耗时的UI任务
当整个工作压力集中在UI响应上时,可以在工作者线程中增加延时,从而让UI线程获得响应时间。整个工作的总体时间会增加,但用户响应效果会好很多。
private void FormInitForm_Load(object sender, EventArgs e)
{
this.listView1.Items.Clear();
Thread workthread = new Thread(new ThreadStart(this.DoSomething));
workthread.Start();
}
private void DoSomething()
{
for (int i = 0; i < 30; i++)
{
this.Invoke(new Action(this.LoadPicture), i);
Thread.Sleep(100);
}
}
private void LoadPicture(int i)
{
string text = string.Format("Item{0}", i);
ListViewItem lvi = new ListViewItem(text, 0);
this.listView1.Items.Add(lvi);
Thread.Sleep(200);//模拟耗时UI任务,非循环,不可分解
}
五.            补充
1.        Invoke 和 BeginInvoke
在多线程编程中,我们经常要在工作线程中去更新界面显示,而在多线程中直接调用界面控件的方法是错误的做法,正确的做法是将工作线程中涉及更新界面的代码封装为一个方法,通过 Invoke 或者 BeginInvoke 去调用,两者的区别就是一个导致工作线程等待,而另外一个则不会。
而所谓的“一面响应操作,一面添加节点”永远只能是相对的,使 UI 线程的负担不至于太大而以,因为界面的正确更新始终要通过 UI 线程去做,我们要做的事情是在工作线程中包揽大部分的运算,而将对纯粹的界面更新放到 UI 线程中去做,这样也就达到了减轻 UI 线程负担的目的了。
2.        Application.DoEvent
在耗时的循环的UI更新的方法中,插入Application.DoEvent,会使界面获得响应,Application.DoEvent会调用消息处理程序。
private void button2_Click(object sender, EventArgs e)
{
for (int i = 0; i < 30; i++)
{
string text = string.Format("Item{0}", i);
ListViewItem lvi = new ListViewItem(text, 0);
this.listView1.Items.Add(lvi);
Thread.Sleep(200);
for (int j = 0; j < 10; j++)
{
Thread.Sleep(10);
Application.DoEvents();
}
}
}
3.        Lock
lock(object)
{
}
等价与
try
{
Monitor.Enter(object);
}
finally
{
Monitor.Exit(object)
}
附一:
BackgroundWorker组件使用说明
一.            概述
BackgroundWorker是·NET 2.0提供的一个多线程组件,在应用程序中使用,可以非常简单方便地实现UI控件通信,并自动处理多线程冲突问题。
二.            基本属性
1.      WorkerReportsProgress ,bool:是否允许报告进度;
2.      WorkerSupportsCancellation,bool:是否允许取消线程。
3.      CancellationPending,bool,get:读取用户是否取消该线程。
三.            基本事件
1.      DoWork:工作者线程
2.      RunWorkerCompleted :线程进度报告
3.      ProgressChanged:线程结束报告
四.            基本方法
1.      RunWorkerAsync() :启动工作者线程;
2.      CancelAsync():取消工作者线程;
3.      ReportProgress(int);   报告进度
五.            代码
//启动
private void btnStart_Click(object sender, EventArgs e)
{
this.btnStart.Enabled = false;
this.btnStop.Enabled = true;
this.backgroundWorker.RunWorkerAsync();
}
//通知线程停止
private void btnStop_Click(object sender, EventArgs e)
{
this.backgroundWorker.CancelAsync();
}
//工作者线程
private void backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
for (int i = 0; i < 150; i++)
{
if (backgroundWorker.CancellationPending)   //查看用户是否取消该线程
{
break;
}
System.Threading.Thread.Sleep(50);          //干点实际的事
backgroundWorker.ReportProgress(i);         //报告进度
}
}
//线程进度报告
private void backgroundWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
this.progressBar1.Value = e.ProgressPercentage * 100 / 150;
}
//线程结束报告
private void backgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
this.btnStart.Enabled = true;
this.btnStop.Enabled = false;
}

.NET多线程的探讨
本文开始总结.NET下的多种多线程机制,不断更新中,往各位补充。
Invoke机制
最近在实验一个webservice时候,想到了要用异步机制,于是好好研究了一下多线程和Invoke机制,这里写点小小的心得,如有不妥,请各位指教。
我们往往会遇到这样的需求:有一个十分耗时间的工作(比如一个WebSerive的请求),我们不希望它阻塞现有的UI线程(因为这样会导致界面假死),而是希望它在另外一个线程里面执行,并在执行完毕之后将结果“通知”UI线程。这个需求需要通过Invoke和委托机制实现。
参考资料:
http://www.cnblogs.com/c2303191/articles/826571.html
http://www.cnblogs.com/yuxuanji/archive/2009/07/09/1519605.html
Invoke
Invoke总是和委托同时使用,假设有如下代码片段:
Control.Invoke(myDelegate);
为了解释Invoke的真正意义,首先要说明几个关于这段代码的假设:
1.假设这段代码是在一个非UI线程中调用的,设为线程2;
2.假设Control是个控件,并且是在UI线程中创建的,设为线程1,我们在这个线程2中已设法获得了一个Control的引用;
3.假设myDelegate是一个委托实例,无论它所指向的函数(设为函数1)在哪个类中定义;
于是这段代码的意义是:在线程2中,将函数1放到线程1中执行!
Invoke就这么简单!
控件操作规则:
在.NET中有一个规定:任何对控件的操作,都必须在创建这个控件的线程中执行,否则无效!这条规定正是Control.Invoke出现的原因,Control.Invoke相当于强制将某个函数过程放到控件所在线程中执行。还有一点十分重要:对象的方法在哪个线程中执行跟这个对象在哪个线程中创建无关。简单例子就是你在窗体类里面写的函数不一定在UI线程中执行(这一点也是我一直以来的困惑),假设,我在另外一个线程中调用了这个方法(即使是通过委托调用),这个方法仍然在另一个线程中执行。
一个具体的例子:
1.创建一个WinForm应该程序,在界面上放一个按钮,我的目的是在按下按钮后创建一个耗时间的线程,并执行,同时防止界面假死;
2.创建一个类,这个类负责开启一个新的线程并执行一个长时间的操作:
public class SecondThread
{
//这个函数在UI线程中执行
public void DoProcess()
{
Thread thread = new Thread(new ThreadStart(DoTrueProcess));
thread.Start();
}
//这个函数在新的线程中执行
private void DoTrueProcess()
{
Thread.Sleep(5000);
}
}
3.在按钮事件处理函数中启动新线程:
private void button1_Click(object sender, EventArgs e)
{
SecondThread st = new SecondThread();
//启动新操作
st.DoProcess();
}
到这里只是实现了一个普通的多线程编程,还没有涉及如何更新UI界面的问题,我们继续:
4.在界面中添加一个列表框,用来显示数据;
5.由于我们需要将数据通过委托的方式在线程之间传递,于是,定义一个委托,这个委托传入一个list对象:
public delegate void NotifyUI(List data);
6.这个委托通知是SecondThread发出的,所以在SecondThread类中定义一个共有的委托对象,并调用这个对象:
public NotifyUI myDelegate;
//这个函数在新的线程中执行
private void DoTrueProcess()
{
Thread.Sleep(5000);
List rdata = new List() { "string1", "string2", "string3" };
if (myDelegate != null)
{
myDelegate(rdata);
}
}
7.在form中添加一个方法适应这个委托签名,并将SecondThread实例的委托对象指向这个方法:
private void button1_Click(object sender, EventArgs e)
{
SecondThread st = new SecondThread();
st.myDelegate += new NotifyUI(NotifyReceiver);
//启动新操作
st.DoProcess();
}
private void NotifyReceiver(List data)
{
listBox1.DataSource = data;
}
如果到现在你觉得listbox能够显示data的数据,那么再次考虑:对象的方法在哪个线程中执行跟这个对象在哪个线程中创建无关。myDelegate(rdata);这个调用是在新的线程中执行的,尽管指向的方法是在Form中定义的方法,但是这两者没有任何关系,此时的NotifyReceiver方法是在新线程中执行的,而这个线程不是创建listbox的线程,因此,这里对listbox的数据绑定不能成功实施。那么如何将这个NotifyReceiver封送到UI线程中执行呢?答案便是使用Invoke。
8.把DoTrueProcess修改为如下代码:
private void DoTrueProcess()
{
Thread.Sleep(5000);
List rdata = new List() { "string1", "string2", "string3" };
if (myDelegate != null)
{
//获取myDelegate的目标对象,这里将是Form1的实例
Control control = myDelegate.Target as Control;
//如果目标对象是个Control的话
if (control != null)
{
//通过调用form的invoke,把委托指向的函数NotifyReceiver送到UI线程上执行
control.Invoke(myDelegate, rdata);
}
//如果目标对象不是Control,则直接执行委托
else
{
myDelegate(rdata);
}
}
}
再次测试会发现,5秒后列表会更新,并且在这个5秒内,界面没有假死。在这个例子中我们把创建第二个线程和委托封锁都放到了SecondThread类里面,对于消费者(界面类),可以简单的通过类似事件的机制异步的处理,而不阻塞UI线程。
BeginInvoke
接下来,我们来看看BeginInvoke。BeginInvoke跟Invoke的唯一差别是:对于调用Invoke的线程,在Invoke的方法返回前,这个线程会阻塞;对于调用BeginInvoke的线程,在BeginInvoke的方法返回前,这个线程不会阻塞!
BackgroundWorker组件
本节参考资料:BackgroundWorker类
BackgroundWorker类允许你在单独的专用线程上运行操作。耗时的操作可以利用这个组件方便的调用。这个组件还提供了进程报告的机制。可以在Toolbox中选择该组件拖入设计器,也可以在代码中自行创建。使用这个组件相比使用Invoke要方便的多。
这个类不复杂,下面这个图很好的说明了如何使用:
360docimg_1023_
本图转载,原图出处
SynchronizationContext
有同仁提到可以用SynchronizationContext在线程之间封送调用,于是我查阅了相关的文章,下面这部分内容将讨论SynchronizationContext。
参考资料:http://blog.csdn.net/soarheaven/archive/2009/01/13/3765468.aspx
基本使用
SynchronizationContext基本使用方法很简单,先上一段代码:
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
//获取当前UI线程ID号,并输出
int uiThreadId = Thread.CurrentThread.ManagedThreadId;
Trace.WriteLine(string.Format("UI thread ID:{0}",uiThreadId));
//获取当前线程上下文
SynchronizationContext uiContext = SynchronizationContext.Current;
//运行第二个线程,并传入上下文
Thread newThread = new Thread(RunInSecondThread);
newThread.Start(uiContext);
}
private void RunInSecondThread(object context)
{
//获取第二线程ID号,并输出
int sencondThreadID = Thread.CurrentThread.ManagedThreadId;
Trace.WriteLine(string.Format("Second thread ID:{0}",sencondThreadID));
Thread.Sleep(2000);
//得到UI线程上下文
SynchronizationContext uiContext = context as SynchronizationContext;
if (uiContext != null)
{
//将UpdateUI封送到uiContext所在线程
uiContext.Post(UpdateUI,new object());
}
}
private void UpdateUI(object obj)
{
//获取UpdateUI所在线程ID号,并输出,可以发现这个线程号是UI线程号,可见UpdateUI已被封送到UI线程执行
int UnknowThreadID = Thread.CurrentThread.ManagedThreadId;
Trace.WriteLine(string.Format("Second thread ID:{0}",UnknowThreadID));
button1.Text = "Change?";
}
}
下面是输出内容:
UI thread ID:10
Second thread ID:11
The thread '' (0xa38) has exited with code 0 (0x0).
Second thread ID:10
SynchronizationContext.Current可以获得当前调用线程的上下文对象。将这个对象作为启动线程的函数参数传入第二个线程,就可以获得封送的便捷途径。
可以看到 SynchronizationContext工作的相当好,完全达到了预期的效果,界面也能正常更新。
什么时候创建了SynchronizationContext
每个线程的SynchronizationContext不是有应用程序域维护的,而是每一个线程自己存储的。那么究竟在什么时候创建的SynchronizationContext呢?
看下面的代码:
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
// let's check the context here
var context = SynchronizationContext.Current;
if (context == null)
MessageBox.Show("No context for this thread");
else
MessageBox.Show("We got a context");
// create a form
Form1 form = new Form1();
// let's check it again after creating a form
context = SynchronizationContext.Current;
if (context == null)
MessageBox.Show("No context for this thread");
else
MessageBox.Show("We got a context");
if (context == null)
MessageBox.Show("No context for this thread");
Application.Run(new Form1());
}
这段代码测试了何时SynchronizationContext被创建,,可以看到在form1创建之前,SynchronizationContext还没有创建,而form1创建的时候创建了SynchronizationContext,form会检查SynchronizationContext是否已经存在,如果不存在将创建一个新的SynchronizationContext。
出错处理
我们修改一下第一段代码:
private void UpdateUI(object obj)
{
//这个函数被封送到UI线程上执行,这里抛出异常,那么这个异常由哪个线程捕获呢?
throw new Exception("Boom");
}
...
if (uiContext != null)
{
try
{
//将UpdateUI封送到uiContext所在线程,这里使用Send而不是Post
uiContext.Send(UpdateUI, new object());
}
catch (Exception ex)
{
//如果输出Boom,那么表示UI线程上的异常被这个非UI线程捕获了
Trace.WriteLine(ex.Message);
}
}
结果输出了“Boom”,那么很明显,使用Send封送,我们将在非UI线程捕获UI线程的异常!
Send和Post
在上面的例子中我们先后使用了Send和Post两种封送方法,那么他们的区别又是什么呢?
如果我们在上面的出错处理时用Post封送,将发现UI崩溃,在debug时接收到一个为处理异常。说明什么呢?说明Post是异步的,Send是同步的。Post就像BeginInvoke一样在封送委托后继续执行当天线程不会等待,因此是异步的,如果委托发生异常,则由UI线程受到委托;而Send就像Invoke一样,封送委托后会同步地等待委托的执行,一旦委托出现异常在当前线程中也能捕获这个异常。
其他议题
说到这里你也许觉得SynchronizationContext是如此方便,的确如此。然而我们在例子中使用的SynchronizationContext实际上是WindowsFormsSynchronizationContext。我们也可以自己定制SynchronizationContext。但是这超过了本篇的讨论范围,想了解更多SynchronizationContext可以参考如下两篇文章:
http://blog.csdn.net/soarheaven/archive/2009/01/13/3765468.aspx
http://blog.csdn.net/soarheaven/archive/2009/01/13/3765501.aspx
http://blog.csdn.net/soarheaven/archive/2009/01/13/3765510.aspx
告别单线程,看看多线程能做什么
作为开发者,我觉我们的职责得就是把复杂的东西以一种简单的方式展示给用户或是普通使用者。
任何用户都不喜欢反应慢的程序。在计算机性能大幅度提高的今天,用户体验也就显得格外重要,没有人会有耐心去容忍你那糟糕的程序
接着上篇所讲,当程序中出现耗时较长的操作时,单线程往往就会力不从心,由于它是连续执行的,所以它没有办法跳过耗时较长的操作。
此时,使用多线程无疑是明智的选择,也是简单有效的解决方法。你可以将那些耗时的操作从UI线程中拿掉,放到另外一个非UI线程中,使得程序界面不用停下了等待耗时操作的完成,从而提高程序UI的执行速度。
下面我们就上篇文章中的代码稍作修改,,如下:
public partial class SingleThread : Form
{
public SingleThread()
{
InitializeComponent();
}
private int ab = 0;
private void button1_Click(object sender, EventArgs e)
{
Thread t = new Thread(new ThreadStart(add_AB));
t.Start();
}
private int cd = 0;
private void button2_Click(object sender, EventArgs e)
{
add_CD();
tb_CD.Text = cd.ToString();
}
/// 
/// 运算A+B
/// 

private void add_AB()
{
Thread.Sleep(10000);//运算时间:10秒
ab= int.Parse(tb_A.Text) + int.Parse(tb_B.Text);
tb_AB.BeginInvoke(new add(addA_B), ab);
}
private delegate void add(int a);
private void addA_B(int result)
{
tb_AB.Text = result.ToString();
}
/// 
/// 运算C+D
/// 

private void add_CD()
{
cd = int.Parse(tb_C.Text) + int.Parse(tb_D.Text);
}
}
修改后的程序运行起来,用户体验就会好了很多,尽管A+B会消耗很长时间,但丝毫不会影响到C+D的操作。
在此,需要格外说明的是,Windows 窗体体系结构对线程有个格外严格的规定:除了极少数的例外情况,否则都不要在它的创建线程以外的线程中使用控件的任何成员。也就是说,我们界面中的所有控件有都是创建在UI线程中的,除UI线程外的其他线程都不能随意使用或修改控件中的任何属性,包括t_AB的值。而我们的“复杂计算”A+B的结果是需要显示到t_AB中的,所以不可避免地要修改它的值。
不过,在Winform的Control类中,有两个方法Invoke和BeginInvoke始终允许任何一个线程对Control进行invoke调用,进而修改控件的属性(接触WinForm的应该都知道,几乎任何控件都是继承自Control类,textbox也不例外)。所以与单线程相比,这里我们多定义了一个
private delegate void add(int a);
并且把t_AB的赋值语句改成了tb_AB.BeginInvoke(new add(addA_B), ab);
此外,在利用多线程编程的过程中,细心的人有时候可能会遇到这样一个情况:就是运行的项目命名已经关闭了,为什么打开资源管理器的时候在进程列表中还能看到程序在运行着呢?
我想这可能是你在程序中使用了异步委托调用,该调用以异步委托的方式运行在系统的线程池中,当你关闭程序时如果这个异步调用还没有结束,那么就会出现上述的情况。
断点续传、多线程上载
Posted on 2008-09-12 16:11随心所欲 阅读(3849)评论(15)编辑收藏360docimg_1024_
现在已经有很多断点续传、多线程下载的软件了,比如网际快车等等。下面设计的程序是“断点续传、多线程上载”。
缘起:客户每天都有大量文件上传服务器。这些文件很多,并且体积挺大,FTP有时候会出一些问题,导致传递失败,要重新上传。
基本解决方案:
1:把文件分割成块,每次只是传递一个文件块。
2:一个文件可以起多个发送任务(线程),同时发送。
3:记录文件发送状态,在网络出现问题时(或者客户端意外终止),知道上次发送文件大小和位置指针。再重新链接以后,继续发送。
对象和线程
这里面涉及到一个显示窗体form1,有timer可以随时更新发送状态;一个上传类Uploader(对应于一个文件);Task对象(也就是一个文件);FileThunk对象(每一个任务,对应于一个线程);WebService接受文件类。
发送状态需要记录在数据库。测试状态下,数据记录在xml文件。基本格式如下:
360docimg_1025_360docimg_1026_Code





数据表结构也基本类似这样。
核心代码:
1:form1. 这个主要是显示。主要函数是 添加任务(Task);更新任务状态
360docimg_1027_360docimg_1028_浏览文件,创建任务
1   private void btn_browse_Click(object sender, EventArgs e)
2         {
3             //browse to select files
4             OpenFileDialog ofd = new OpenFileDialog();
5             ofd.Multiselect = true;
6             DialogResult dr= ofd.ShowDialog();
7
8             _taskList = new ArrayList();
9             for (int i = 0; i < ofd.FileNames.Length; i++)
10             {
11                 Task t = new Task();
12                 t.FileChunkCount = Convert.ToInt32(this.txt_threadsPerFile.Text);
13                 t.LocalFile = ofd.FileNames[i];
14                 t.RemoteFile = Path.GetFileName(t.LocalFile);
15                 t.Name = Path.GetFileName(t.LocalFile);
16                 //t.Percentage=0;
17                 t.TotalSeconds=0;
18
19                 t.Init(); //init, split the file to upload
20
21                 _taskList.Add(t);
22
23             }
24             //show in UI
25             this.dataGridView1.DataSource = GetUpdatedStatusAsDT(_taskList,0);
26         }
360docimg_1029_360docimg_1030_显示和更新状态,返回DataTable
1   private DataTable GetUpdatedStatusAsDT(ArrayList list, int seconds)
2         {
3             DataTable dt = new DataTable();
4             dt.Columns.Add("Name");
5             dt.Columns.Add("FileSize");
6             dt.Columns.Add("Percentage");
7             dt.Columns.Add("TotalSeconds");
8             dt.Columns.Add("LocalFile");
9             dt.Columns.Add("RemoteFile");
10
11             for (int i = 0; i < list.Count; i++)
12             {
13                 //update task status to db
14                 Task t = (list[i] as Task);
15                 t.TotalSeconds += seconds;//running time
16                 t.Save();//save the status to db, so next time can load the status to continue.
17
18                 DataRow dr = dt.NewRow();
19                 dr["Name"] = t.Name;
20                 dr["FileSize"] = t.FileSize;
21                 dr["Percentage"] = t.Percentage;
22                 dr["TotalSeconds"] = t.TotalSeconds;
23                 dr["LocalFile"] = t.LocalFile;
24                 dr["RemoteFile"] = t.RemoteFile;
25
26                 dt.Rows.Add(dr);
27             }
28             return dt;
29         }
360docimg_1031_360docimg_1032_执行任务
1    private void btn_run_Click(object sender, EventArgs e)
2         {
3             //timmer to show the status
4             Timer timer = new Timer();
5             timer.Tick += new EventHandler(timer_Tick);
6             timer.Interval = 2 * 1000; //every 2 seconds
7             timer.Enabled = true;
8             timer.Start();
9             //
10             //continue
11             //find task from datasource (DB or xml) and continue
12             //ArrayList tasks = Task.LoadTasks();
13             //
14             ArrayList tasks = _taskList;  // get the task list.
15             //
16             for (int i = 0; i < tasks.Count; i++)
17             {
18                 Uploader uld = new Uploader(tasks[i] as Task);
19                 uld.Strat();
20             }
21         }
22
2:Uploader的核心代码:主要是启动线程。
360docimg_1033_360docimg_1034_Code
1   public void Strat()
2         {
3             //_dataReceiver.Open(this._task.RemoteFile);//init webservice
4
5             #region test
6             //if (!File.Exists(_task.RemoteFile))
7             //{
8             //    FileInfo fi = new FileInfo(_task.LocalFile);
9
10             //    FileStream fst = new FileStream(_task.RemoteFile, FileMode.CreateNew);
11
12             //    BinaryWriter w = new BinaryWriter(fst);
13             //    byte[] ab = new byte[fi.Length];
14             //    w.Write(ab);
15             //    w.Close();
16             //    fst.Close();
17             //}
18             //
19             #endregion
20
21             //main code. find each fileChunk, running in seperate thread
22             for (int i = 0; i < _task.FileChunks.Count; i++)
23             {
24                 FileChunk fc = (_task.FileChunks[i] as FileChunk);
25                 Thread thread = new Thread(new ThreadStart(fc.Upload));
26                 fc.RunningThread = thread;//asign thread to the fileChucks, to stop the task
27                 thread.Name = _task.Name + "_" + i.ToString();
28                 thread.Start();
29             }
30         }
3:Task类的主要代码:主要是初始化任务,其他诸如和对数据的保存/提取
360docimg_1035_360docimg_1036_Code
1    public void Init()
2         {
3             //create new
4            this.FileChunks = new ArrayList();
5
6             FileInfo fi = new FileInfo(LocalFile);
7             FileSize = fi.Length;
8
9             long block = FileSize / this.FileChunkCount;
10             long index = 0;
11             for (int i = 0; i < this.FileChunkCount; i++)
12             {
13                 FileChunk fc = new FileChunk(this);
14                 fc.Begin = index;
15                 if (i==this.FileChunkCount-1)
16                     block = FileSize - index;
17                 index += block;
18                 fc.End = index;
19                 fc.LastTime = DateTime.Now;
20
21                 this.FileChunks.Add(fc);
22             }
23             //
24
25         }
4:FileChunk的核心代码:主要是如何上传文件
360docimg_1037_360docimg_1038_Code
1   /// 
2         /// main function
3         /// 

4         public void Upload()
5         {
6             Stream fs = File.OpenRead(_task.LocalFile);
7
8             long len = 2 * 64 * 1024;// 2 * 64 * 1024;
9
10             while (Begin < End)
11             {
12                 if (End - Begin < len)
13                     len = End - Begin;
14                 byte[] data = new byte[len];
15                 fs.Position = Begin;
16                 fs.Read(data, 0, (int)len);
17
18                 #region local test
19                 //Stream fs2 = File.OpenWrite(_task.RemoteFile);
20                 //fs2.Position = Begin;
21                 //fs2.Write(data, 0, (int)len);
22                 //fs2.Flush();
23                 //fs2.Close();
24                 //
25                 #endregion
26
27                 //call webservice to receive the data. this can be a socket also.
28                 string res = this._task.DataReceiver.Receive(_task.RemoteFile, Begin, len, data);
29                 if (res == "ok")
30                 {
31                     //
32                     Begin += len;
33                     _task.Save();
34                 }
35                 else
36                 {
37                     //wait for next while,
38                 }
39
40             }
41         }
5:WebService的核心代码
360docimg_1039_360docimg_1040_Code
[WebMethod]
public string Receive(string file, long begin, long len, byte[] data)
{
_fileName = AppDomain.CurrentDomain.BaseDirectory + "\\tmp\\" + file;
try
{
_fs = File.OpenWrite(_fileName);
_fs.Position = begin;
_fs.Write(data, 0, (int)len);
_fs.Flush();
_fs.Close();
_fs = null;
return "ok";
}
catch (Exception ex)
{
string s = ex.Message;
return "retry";
}
}
小结:
技术难度不大;现实实用;上传速度尚可。
程序运行截图一个
360docimg_1041_
分类:通讯/WebServer,VS2005
深入浅出多线程系列之七:4种定时器
在Framework中存在着4种定时器:其中分为两类,
多线程计时器
1:System.Threading.Timer
2:System.Timers.Timer
特殊目的的单线程计时器:
1:System.Windows.Forms.Timer(Windows Forms Timer)
2:System.Windows.Threading.DispatcherTimer(WPF timer);
多线程计时器比较强大,精确,而且可扩展性强;
单线程计时器比较安全,对于更新 Windows Forms controls或者WPF这种简单任务来说更方便。
System.Threading.Timer是最简单的多线程计时器。在下面的例子中,定时器在5秒后开始定时1秒的调用Tick方法。
public static void Main ()
{
//5秒后开始运行,接着每隔1秒的调用Tick方法
Timer tmr = new Timer(Tick, "tick...", 5000, 1000);
Console.ReadLine();
tmr.Dispose();
}
static void Tick(object data)
{
Console.WriteLine(data);
}
.net framework提供的另一个计时器System.Timers.Timer.简单的对System.Threading.Timer进行了包装。增加了下面几个特性。
实现了Component,所以可以在设计器显示。 代替Change方法的一个Interval属性 代替callback委托的一个Elapsed事件 启动和停止timer的Enabled属性,默认是false。 为了避免Enabled造成混乱,提供了Start和Stop方法。 是否在每次指定的间隔结束时引发Elapsed时间,还是仅间隔第一次结束后运行的AutoReset属性。 在WPF或Windows Forms中安全的调用方法的SynchronizingObject对象。
public static void MainThread()
{
Timer tmr = new Timer();
tmr.Interval = 500;
tmr.Elapsed += new ElapsedEventHandler(tmr_Elapsed);
tmr.Start();
Console.ReadLine();
tmr.Stop();
Console.ReadLine();
tmr.Start();
Console.ReadLine();
tmr.Dispose();
}
static void tmr_Elapsed(object sender, ElapsedEventArgs e)
{
Console.WriteLine("Tick...");
}
单线程计时器:
1:System.Windows.Forms.Timer(Windows Forms Timer)
2:System.Windows.Threading.DispatcherTimer(WPF timer);
单线程计时器是被设计成属于他们执行环境的计时器,如果你在一个Windows服务应用程序中使用Windows Forms的Timer,timer 事件并不会被触发,只有在对应的环境下才会被触发。
像System.Timers.Timer一样,他们也提供了相同的成员(Interval,Tick,Start,Stop),但是他们内部的工作原理不同,
WPF和Windows Forms的计时器使用消息循环机制来取代线程池产生消息的机制。
这意味着Tick事件总是在创建timer的那个线程上执行,同时也意味着如果上一个Tick消息还未被处理,即使时间超过了间隔时间,在消息循环中也只存在一个Tick消息。
下面是它们的优点:
你可以忘记线程安全。
一个Tick事件在前一个Tick事件被处理完毕前不会被触发。
你可以直接在Tick事件处理代码中更新控件,不需要调用Control.Invoke或Dispatcher.Invoke.
看下在Winform中使用单线程定时器的效果:
//基于Windows消息循环的单线程计时器
private System.Windows.Forms.Timer timer = new Timer() { };
public Form1()
{
InitializeComponent();
timer.Tick += new EventHandler(timer_Tick);
timer.Enabled = true;
}
void timer_Tick(object sender, EventArgs e)
{
//模拟的做一些耗时的操作
System.Threading.Thread.Sleep(2000);
}
如果运行上面的代码,会发现UI界面响应速度很慢,
原理上面已经介绍了:单线程计时器基于Windows消息循环,应用程序会同步的处理计时器的消息。
解决这个问题的方法是使用多线程计时器:只要修改代码使用多线程计时器即可:
//使用多线程计时器
private System.Timers.Timer timer = new System.Timers.Timer();
public Form1()
{
InitializeComponent();
timer.Elapsed += new System.Timers.ElapsedEventHandler(timer_Elapsed);
timer.Enabled = true;
}
void timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{
//模拟的做一些耗时的操作
System.Threading.Thread.Sleep(2000);
}
上面的例子告诉我们单线程计时器的缺点:
除非Tick事件的处理代码执行的非常快,否则UI界面会变得响应很慢。
所以 WPF和Windows Forms的计时器都非常适合小任务,尤其是界面更新的任务。例如时钟和计数显示。否则,你需要一个多线程计时器。
浅述WinForm多线程编程与Control.Invoke的应用
VS2008、C#3.0
在WinForm开发中,我们通常不希望当窗体上点了某个按钮执行某个业务的时候,窗体就被卡死了,直到该业务执行完毕后才缓过来。一个最直接的方法便是使用多线程。多线程编程的方式在WinForm开发中必不可少。
本文介绍在WinForm开发中如何使用多线程,以及在线程中如何通过Control.Invoke方法返回窗体主线程执行相关操作。
-. WinForm多线程编程
1. new Thread()
新开一个线程,执行一个方法,没有参数传递:
360docimg_1042_360docimg_1043_private void DoWork() 360docimg_1044_{
360docimg_1045_            Thread t = new Thread(new ThreadStart(this.DoSomething));
360docimg_1046_            t.Start();
360docimg_1047_        }
360docimg_1048_360docimg_1049_        private void DoSomething() 360docimg_1050_{
360docimg_1051_            MessageBox.Show("thread start");
360docimg_1052_        }
新开一个线程,执行一个方法,并传递参数:
360docimg_1053_360docimg_1054_private void DoWork() 360docimg_1055_{
360docimg_1056_            Thread t = new Thread(new ParameterizedThreadStart(this.DoSomething));
360docimg_1057_            t.Start("guozhijian");
360docimg_1058_        }
360docimg_1059_360docimg_1060_        private void DoSomething(object o) 360docimg_1061_{
360docimg_1062_            MessageBox.Show(o.ToString());
360docimg_1063_        }
参数定义为object类型。
2. ThreadPool
众所周知,新开一个线程代价是很高昂的,如果我们每个操作都新开一个线程,那么太浪费了,于是,下面使用线程池。
无参数传递:
360docimg_1064_360docimg_1065_private void DoWork() 360docimg_1066_{
360docimg_1067_            ThreadPool.QueueUserWorkItem(new WaitCallback(this.DoSomething));
360docimg_1068_        }
360docimg_1069_360docimg_1070_        private void DoSomething(object o) 360docimg_1071_{
360docimg_1072_            MessageBox.Show("thread start");
360docimg_1073_        }
有参数传递:
360docimg_1074_360docimg_1075_private void DoWork() 360docimg_1076_{
360docimg_1077_            ThreadPool.QueueUserWorkItem(new WaitCallback(this.DoSomething), "guozhijian");
360docimg_1078_        }
360docimg_1079_360docimg_1080_        private void DoSomething(object o) 360docimg_1081_{
360docimg_1082_            MessageBox.Show(o.ToString());
360docimg_1083_        }
使用匿名方法更灵活:
360docimg_1084_360docimg_1085_private void DoWork() 360docimg_1086_{
360docimg_1087_            string name = "guozhijian";
360docimg_1088_360docimg_1089_            ThreadPool.QueueUserWorkItem(new WaitCallback(delegate(object o)360docimg_1090_{
360docimg_1091_                MessageBox.Show(name);
360docimg_1092_            }));
360docimg_1093_        }
在匿名代码段里面可以直接访问局部变量,不用在关心参数传递的问题
二. Invoke
1. this.Invoke
现在,在业务线程里面执行完毕,要改变窗体控件的值了,此时,如果直接通过this得到控件的句柄,然后对它进行操作是会抛异常的,.Net WinForm Application里面是不允许这样的操作的。这是,可以调用Invoke方法
2.Invoke方法签名:
object Control.Invoke(Delegate Method)
object Control.Invoke(Delegate Method, params object[] args)
3.使用自定义委托360docimg_1094_360docimg_1095_private void DoWork() 360docimg_1096_{
360docimg_1097_            WaitCallback wc = new WaitCallback(this.DoSomething);
360docimg_1098_            ThreadPool.QueueUserWorkItem(wc, "Guozhijian");
360docimg_1099_        }
360docimg_1100_
360docimg_1101_        private delegate void MyInvokeDelegate(string name);
360docimg_1102_360docimg_1103_        private void DoSomething(object o) 360docimg_1104_{
360docimg_1105_            this.Invoke(new MyInvokeDelegate(this.ChangeText), o.ToString());
360docimg_1106_        }
360docimg_1107_
360docimg_1108_360docimg_1109_        private void ChangeText(string name) 360docimg_1110_{
360docimg_1111_            this.textBox1.Text = name;
360docimg_1112_        }
哦,太麻烦了,难道我每次都要定义一个委托啊,这样可不行。
4.使用System.Action:
360docimg_1113_360docimg_1114_private void DoWork() 360docimg_1115_{
360docimg_1116_            WaitCallback wc = new WaitCallback(this.DoSomething);
360docimg_1117_            ThreadPool.QueueUserWorkItem(wc, "Guozhijian");
360docimg_1118_        }
360docimg_1119_
360docimg_1120_360docimg_1121_        private void DoSomething(object o) 360docimg_1122_{
360docimg_1123_            this.Invoke(new Action(this.ChangeText), o.ToString());
360docimg_1124_        }
360docimg_1125_
360docimg_1126_360docimg_1127_        private void ChangeText(string name) 360docimg_1128_{
360docimg_1129_            this.textBox1.Text = name;
360docimg_1130_        }
本例传递一个参数,System.Action有很多个重载,可以无参数(非泛型),而最多可以有四个参数,同样采用匿名方法,不使用泛型形式的System.Action,如下:
360docimg_1131_360docimg_1132_private void DoWork() 360docimg_1133_{
360docimg_1134_            WaitCallback wc = new WaitCallback(this.DoSomething);
360docimg_1135_            ThreadPool.QueueUserWorkItem(wc, "Guozhijian");
360docimg_1136_        }
360docimg_1137_
360docimg_1138_360docimg_1139_        private void DoSomething(object o) 360docimg_1140_{
360docimg_1141_360docimg_1142_            this.Invoke(new Action(delegate() 360docimg_1143_{
360docimg_1144_                this.textBox1.Text = o.ToString();
360docimg_1145_            }));
360docimg_1146_        }
5.使用System.Func
如果Invoke调用主窗体操作之后,还希望在调用完得到一个返回值:
360docimg_1147_360docimg_1148_private void DoWork() 360docimg_1149_{
360docimg_1150_            WaitCallback wc = new WaitCallback(this.DoSomething);
360docimg_1151_            ThreadPool.QueueUserWorkItem(wc, "Guozhijian");
360docimg_1152_        }
360docimg_1153_
360docimg_1154_360docimg_1155_        private void DoSomething(object o) 360docimg_1156_{
360docimg_1157_            System.Func f = new Func(this.GetId);
360docimg_1158_            object result = this.Invoke(f,o.ToString());
360docimg_1159_            MessageBox.Show(result.ToString());
360docimg_1160_        }
360docimg_1161_
360docimg_1162_360docimg_1163_        private int GetId(string name) 360docimg_1164_{
360docimg_1165_            this.textBox1.Text = name;
360docimg_1166_360docimg_1167_            if (name == "Guozhijian") 360docimg_1168_{
360docimg_1169_                return 999;
360docimg_1170_            }
360docimg_1171_360docimg_1172_            else 360docimg_1173_{
360docimg_1174_                return 0;
360docimg_1175_            }
360docimg_1176_        }
result的值为 999。
System.Func同样有很多泛形重载,这里不赘述。
6.关于Invoke的拥有者:Control
本文例中都是用this来引用,这里this替换为窗体任何一个控件的句柄都是OK的,因为Control.Invoke含义是将方法委托给拥有该Control的线程去执行。
一个菜鸟写的多线程删除文件的代码,大家给点意见
公司图片服务器某个盘今天又满了,每次都是用一个控制台程序去删文件,我DOS命令不熟,今天突发奇想用上多线程是不是会快一些呢?然后就写了些代码,本菜鸟接触c#2年多工作1年多。代码是一个小时写完的,没有任何优化,只为实现功能,希望大家对于代码给点意见,本以为用了多线程删除的速度就会快上一点,但是与原来一个线程没什么大的变化,是不是IO瓶颈了。
using System;
using System.Collections.Generic;
using System.IO;
using System.Collections;
namespace FiledbWrite
{
class Program
{
static void Main(string[] args)
{
DateTime timeStart = DateTime.Now;
string path = System.Configuration.ConfigurationManager.AppSettings["path"];
List allPath = new List();
int I = 0;
foreach (string file in Directory.GetDirectories(path))
{
deleteFile d = new deleteFile();
d.originalDir = file;
d.tempDir = file;
d.deleteAction = new System.Threading.Thread(d.Action);
d.threadNO = I;
allPath.Add(d);
I++;
}
foreach (deleteFile d in allPath)
{
try
{
d.deleteAction.Start();
}
catch (System.Exception ex)
{
Console.WriteLine(ex.Message);
}
}
while (true)
{
if (getState(allPath))
break;
}
DateTime timeEnd = DateTime.Now;
TimeSpan timeSpan = timeEnd - timeStart;
Console.WriteLine(string.Format("线程数:{0},总用时{1}s", I + 1, timeSpan.Seconds));
Console.ReadLine();
}
//获取当前所有线程状态是否都已停止(删除完)
public static bool getState(List d)
{
bool over = true;
foreach (deleteFile ddd in d)
{
if (ddd.deleteAction.IsAlive)
return false;
}
return over;
}
}
//文件删除功能类 作者头脑简单图省事 使用多个类实现了多线程
public class deleteFile
{
//线程编号
public int threadNO = 0;
public System.Threading.Thread deleteAction;
/// 
/// 临时目录
/// 

public string tempDir = string.Empty;
/// 
/// 原始目录
/// 

public string originalDir = string.Empty;
public void Action()
{
try
{
//删除每个文件夹下文件
DeleteFolderFile();
//删除文件夹
Directory.Delete(originalDir, true);
//停止线程
deleteAction.Abort();
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
}
public void DeleteFolderFile()
{
try
{
System.IO.File.SetAttributes(tempDir, System.IO.FileAttributes.Normal);
foreach (string d in Directory.GetFileSystemEntries(tempDir))
{
System.IO.File.SetAttributes(d, System.IO.FileAttributes.Normal);
if (File.Exists(d))
{
FileInfo fi = new FileInfo(d);
if (fi.Attributes.ToString().IndexOf("ReadOnly") != -1)//避免只读文件
{ fi.Attributes = FileAttributes.Normal; }
Console.WriteLine(string.Format("当前文件:{0},当前线程:{1}", d, threadNO));
File.Delete(d);  //删除文件
}
else
{
try
{
tempDir = d;
DeleteFolderFile();//递归
}
catch (System.Exception ex)
{
Console.WriteLine(ex.Message);
}
}
}
}
catch (System.Exception ex)
{
Console.WriteLine(ex.Message);
}
}
}
}
看了大家的评论我想说几句,关于操作系统的东西不是很了解,希望大家可以不吝赐教,贴出这段代码的主要想法是想让大家看一下代码在逻辑上,线程的使用上,语法上,有没有什么让人看起来不舒服的地方,或者是不是能用到一些设计模式来“美化”以下代码。
多任务多线程(队列)示例代码
Posted on 2009-07-17 11:43szyicol 阅读(2980)评论(26)编辑收藏360docimg_1177_
前几天需要使用一个多线程来完成工作,情况如下:
有一个列表,有N个任务,现在需要M个线程去实现它,不懂代码,没有办法完成,找同事完成了。
执行的过程如下
1、生成N个任务的队列(此队列为基类List,被封装过,提供一个GetNext方法)
2、生成M个线程的数组(线程是经过封装的)
3、启动各个线程,
4、某一个线程完成,通知主线程完成了,并去调用队列的GetNext方法,获取下一个任务,直到所有的完成。GetNext方法有Lock,防止任务分配重复。
5、运行的过程当中,可以调用队列的Add方法,动态增加任务。目前代码中没有写减少任务,加上去是非常简单的。
6、目前没有做的,就是动态增减线程的数量,但也不难,比如给封装过的线程增加Remove方法,在线程内部从最后一个线程往前遍历,需要停止几个就Stop几个。
7、此方法比较适合队列管理的情况,比如像下载软件flashget,迅雷等
8、顺便BS一下很多写blog的人,受中国应试教育的影响,很多是把理论搬上来,不知道想表达什么,cnblogs是该需要分成几块了,资料,心得,论坛,该去哪里去哪里,不免一些人说首页发的文章,自己想看的太少了。
/Files/szyicol/ThreadDemo.rar   C#多线程学习(二) 如何操纵一个线程 下面我们就动手来创建一个线程,使用Thread类创建线程时,只需提供线程入口即可。(线程入口使程序知道该让这个线程干什么事) 在C#中,线程入口是通过ThreadStart代理(delegate)来提供的,你可以把ThreadStart理解为一个函数指针,指向线程要执行的函数,当调用Thread.Start()方法后,线程就开始执行ThreadStart所代表或者说指向的函数。 打开你的VS.net,新建一个控制台应用程序(Console Application),编写完全控制一个线程的代码示例: //ThreadTest.cs using System;using System.Threading; namespace ThreadTest{  public class Alpha  {      public void Beta()      {        while (true)        {          Console.WriteLine("Alpha.Beta is running in its own thread.");        }      }  };   public class Simple  {      public static int Main()      {        Console.WriteLine("Thread Start/Stop/Join Sample");                Alpha oAlpha = new Alpha();        file://这里创建一个线程,使之执行Alpha类的Beta()方法        Thread oThread = new Thread(new ThreadStart(oAlpha.Beta));        oThread.Start();        while (!oThread.IsAlive)               Thread.Sleep(1);        oThread.Abort();        oThread.Join();        Console.WriteLine();        Console.WriteLine("Alpha.Beta has finished");         try         {          Console.WriteLine("Try to restart the Alpha.Beta thread");          oThread.Start();        }        catch (ThreadStateException)         {          Console.Write("ThreadStateException trying to restart Alpha.Beta. ");          Console.WriteLine("Expected since aborted threads cannot be restarted.");          Console.ReadLine();        }        return 0;      }  }} 这段程序包含两个类Alpha和Simple,在创建线程oThread时我们用指向Alpha.Beta()方法的初始化了ThreadStart代理(delegate)对象,当我们创建的线程oThread调用oThread.Start()方法启动时,实际上程序运行的是Alpha.Beta()方法: Alpha oAlpha = new Alpha();  Thread oThread = new Thread(new ThreadStart(oAlpha.Beta));  oThread.Start();  然后在Main()函数的while循环中,我们使用静态方法Thread.Sleep()让主线程停了1ms,这段时间CPU转向执行线程oThread。然后我们试图用Thread.Abort()方法终止线程oThread,注意后面的oThread.Join(),Thread.Join()方法使主线程等待,直到oThread线程结束。你可以给Thread.Join()方法指定一个int型的参数作为等待的最长时间。之后,我们试图用Thread.Start()方法重新启动线程oThread,但是显然Abort()方法带来的后果是不可恢复的终止线程,所以最后程序会抛出ThreadStateException异常。 主线程Main()函数 所有线程都是依附于Main()函数所在的线程的,Main()函数是C#程序的入口,起始线程可以称之为主线程。如果所有的前台线程都停止了,那么主线程可以终止,而所有的后台线程都将无条件终止。所有的线程虽然在微观上是串行执行的,但是在宏观上你完全可以认为它们在并行执行。 Thread.ThreadState 属性 这个属性代表了线程运行时状态,在不同的情况下有不同的值,我们有时候可以通过对该值的判断来设计程序流程。ThreadState 属性的取值如下:Aborted:线程已停止; AbortRequested:线程的Thread.Abort()方法已被调用,但是线程还未停止; Background:线程在后台执行,与属性Thread.IsBackground有关; Running:线程正在正常运行; Stopped:线程已经被停止; StopRequested:线程正在被要求停止; Suspended:线程已经被挂起(此状态下,可以通过调用Resume()方法重新运行);SuspendRequested:线程正在要求被挂起,但是未来得及响应; Unstarted:未调用Thread.Start()开始线程的运行; WaitSleepJoin:线程因为调用了Wait(),Sleep()或Join()等方法处于封锁状态; 上面提到了Background状态表示该线程在后台运行,那么后台运行的线程有什么特别的地方呢?其实后台线程跟前台线程只有一个区别,那就是后台线程不妨碍程序的终止。一旦一个进程所有的前台线程都终止后,CLR(通用语言运行环境)将通过调用任意一个存活中的后台进程的Abort()方法来彻底终止进程。 线程的优先级 当线程之间争夺CPU时间时,CPU 是按照线程的优先级给予服务的。在C#应用程序中,用户可以设定5个不同的优先级,由高到低分别是Highest,AboveNormal,Normal,BelowNormal,Lowest,在创建线程时如果不指定优先级,那么系统默认为ThreadPriority.Normal。 给一个线程指定优先级,我们可以使用如下代码://设定优先级为最低myThread.Priority=ThreadPriority.Lowest; 通过设定线程的优先级,我们可以安排一些相对重要的线程优先执行,例如对用户的响应等等。   .net多线程 介绍          如果你有在任何编程语言下的多线程编程经验的话,你肯定已经非常熟悉一些典型的范例。通常,多线程编程与基于用户界面的应用联系在一起,它们需要在不影响终端用户的情况下,执行一些耗时的操作。取出任何一本参考书,打开有关线程这一章:你能找到一个能在你的用户界面中并行执行数学运算的多线程示例吗? 我的目的不是让你扔掉你的书,不要这样做!多线程编程技术使基于用户界面的应用更完美。实际上, Microsoft .NET框架支持在任何语言编写的窗口下应用多线程编程技术,允许开发人员设计非常丰富的界面,提供给终端用户一个更好的体验。但是,多线程编程技术不仅仅是为了用户界面的应用,在没有任何用户界面的应用中,一样会出现多个执行流的情况。 我们用一个“硬件商店”的客户/服务器应用系统作为例子。客户端是收银机,服务端是运行在仓库里一台独立的机器上的应用系统。你可以想象一下,服务器没有任何的用户界面,如果不用多线程技术你将如何去实现? 服务端通过通道(http, sockets, files 等等)接收来自客户端的请求并处理它们,然后发送一个应答到客户端。图1显示了它是如何运作的。 360docimg_1178_ 图1: 单线程的服务端应用系统 为了让客户端的请求不会遗漏,服务端应用系统实现了某种队列来存放这些请求。图1显示了三个请求同时到达,但只有其中的一个被服务端处理。当服务端开始执行 "Decrease stock of monkey wrench," 这个请求时,其它两个必须在队列中等待。当第一个执行完成后,接着是第二个,以此类推。这种方法普遍用于许多现有的系统,但是这样做系统的资源利用率很低。假设 “decreasing the stock”请求修改磁盘上的一个文件,而这个文件正在被修改中,CPU将不会被使用,即使这个请求正处在待处理阶段。这类系统的一个普遍特征就是低CPU利用时间导致出现很长的响应时间,甚至是在访问压力很大的环境里也这样。          另外一个策略就是在当前的系统中为每一个请求创建不同的线程。当一个新的请求到达之后,服务端为进入的请求创建一个新线程,执行结束时,再销毁它。下图说明了这个过程: 360docimg_1179_          图2:多线程服务端应用系统 就像如图2所示的那样。我们有了较高的CPU利用率。即使它已经不再像原来的那样慢了,但创建线和销毁程也不是最恰当的方法。假设线程的执行操作不复杂,由于需要花额外的时间去创建和销毁线程,所以最终会严重影响系统的响应时间。另外一点就是在压力很大的环境下,这三个线程会给系统带来很多的冲击。多个线程同时执行请求处理将导致CPU的利用率达到100%,而且大多数时间会浪费在上下文切换过程中,甚至会超过处理请求的本身。这类系统的典型特征是大量的访问会导致响应时间呈指数级增长和很高的CUP使用时间。          一个最优的实现是综合前面两种方案而提出的观点----线程池(Thread Pool),当一个请求达到时,应用系统把置入接收队列,一组的线程从队列提取请求并处理之。这个方案如下图所示: 360docimg_1180_ 图3:启用线程池的服务端应用系统 在这个例子中,我们用了一个含有两个线程的线程池。当三个请求到达时,它们立刻安排到队列等待被处理,因为两个线程都是空闲的,所以头两个请求开始执行。当其中任何一个请求处理结束后,空闲的线程就会去提取第三个请求并处理之。在这种场景中,系统不需要为每个请求创建和销毁线程。线程之间能互相利用。而且如果线程池的执行高效的话,它能增加或删除线程以获得最优的性能。例如当线程池在执行两个请求时,而CPU的利用率才达到50%,这表明执行请求正等待某个事件或者正在做某种I/O操作。线程池可以发现这种情况,并增加线程的数量以使系统能在同一时间处理更多的请求。相反的,如果CPU利用率达到100%,线程池可以减少线程的数量以获得更多的CPU时间,而不要浪费在上下文切换上面。 .NET中的线程池          基于上面的例子,在企业级应用系统中有一个高效执行的线程池是至关重要的。Microsoft在.NET框架的开发环境中已经实现了这个,该系统的核心提供了一个现成可用的最优线程池。 这个线程池不仅对应用程序可用,而且还融合到框架中的多数类中。.NET 建立在同一个池上是一个很重要的功能特性。比如 .NET Remoting 用它来处理来自远程对象的请求。          当一个托管应用程序开始执行时,运行时环境(runtime)提供一个线程池,它将在代码第一次访问时被创建。这个池与应用程序所在运行的物理进程关联在一起,当你用.NET框架下的同一进程中运行多个应用程序的功能特性时(称之为应用程序域),这将是一个很重要的细节。在这种情况下,由于它们都使用同样的线程池,一个坏的应用程序会影响进程中的其它应用程序。          你可以通过System.Threading 名称空间的Thread Pool 类来使用线程池,如果你查看一下这个类,就会发现所有的成员都是静态的,而且没有公开的构造函数。这是有理由这样做的,因为每个进程只有一个线程池,并且我们不能创建新的。这个限制的目的是为了把所有的异步编程技术都集中到同一个池中。所以我们不能拥有一个通过第三方组建创建的无法管理的线程池。 线程池中执行的函数 ThreadPool.QueueUserWorkItem 方法运行我们在系统线程池上启动一个函数,它的声明如下: public static bool QueueUserWorkItem (WaitCallback callBack, object state) 第一个参数指明我们将在池中执行的函数,它的声明必须与WaitCallback代理(delegate)互相匹配:public delegate void WaitCallback (object state); State 参数允许任何类型的信息传递到该方法中,它在调用QueueUserWorkItem时传入。 让我们结合这些新概念,看看“硬件商店”的另一个实现。 using System; using System.Threading; namespace ThreadPoolTest {    class MainApp    {       static void Main()       {          WaitCallback callBack;          callBack = new WaitCallback(PooledFunc);          ThreadPool.QueueUserWorkItem(callBack,             "Is there any screw left?");          ThreadPool.QueueUserWorkItem(callBack,             "How much is a 40W bulb?");          ThreadPool.QueueUserWorkItem(callBack,             "Decrease stock of monkey wrench");            Console.ReadLine();       }         static void PooledFunc(object state)       {          Console.WriteLine("Processing request '{0}'", (string)state);          // Simulation of processing time          Thread.Sleep(2000);          Console.WriteLine("Request processed");       }    } } 为了简化例子,我们在Main 类中创建一个静态方法用于处理请求。由于代理的灵活性,我们可以指定任何实例方法去处理请求,只要这些方法的声明与代理相同。在这里范例中,通过调用Thread.Sleep,实现延迟两秒以模拟处理时间。 你如果编译和执行这个范例,将会看到下面的输出: Processing request 'Is there any screw left?' Processing request 'How much is a 40W bulb?' Processing request 'Decrease stock of monkey wrench' Request processed Request processed Request processed 注意,所有的请求都被不同的线程并行处理了。 我们可以通过在两个方法中加入如下的代码,以此看到更多的信息。  // Main method    Console.WriteLine("Main thread. Is pool thread: {0}, Hash: {1}",             Thread.CurrentThread.IsThreadPoolThread,             Thread.CurrentThread.GetHashCode());    // Pool method    Console.WriteLine("Processing request '{0}'." +       " Is pool thread: {1}, Hash: {2}",       (string)state, Thread.CurrentThread.IsThreadPoolThread,       Thread.CurrentThread.GetHashCode());   我们增加了一个Thread.CurrentThread.IsThreadPoolThread的调用。如果目标线程属于线程池,这个属性将返回True。另外,我们还显示了用GetHashCode 方法从当前线程返回的结果。它是唯一标识当前执行线程的值。现在看一看这个输出结果: Main thread. Is pool thread: False, Hash: 2 Processing request 'Is there any screw left?'. Is pool thread: True, Hash: 4 Processing request 'How much is a 40W bulb?'. Is pool thread: True, Hash: 8 Processing request 'Decrease stock of monkey wrench '. Is pool thread: True, Hash: 9 Request processed Request processed Request processed   你可以看到所有的请求都被系统线程池中的不同线程执行。再次运行这个例子,注意系统CPU的利用率,如果你没有任何其它应用程序在后台运行的话,它几乎是0%。因为系统唯一正在做的是每执行2秒后就挂起的处理。          我们来修改一下这个应用,这次我们不挂起处理请求的线程,相反我们会一直让系统忙,为了做到这点,我们用Environment.TickCount. 构建一个每隔两秒就对请求执行一次的循环。 int ticks = Environment.TickCount; while(Environment.TickCount - ticks < 2000); 现在打开任务管理器,看一看CPU的使用率,你将看到应用程序占有了CPU的100%的使用率。再看一下我们程序的输出结果: Processing request 'Is there any screw left?'. Is pool thread: True, Hash: 7 Processing request 'How much is a 40W bulb?'. Is pool thread: True, Hash: 8 Request processed Processing request 'Decrease stock of monkey wrench '. Is pool thread: True, Hash: 7 Request processed Request processed   注意第三个请求是在第一个请求处理结束之后执行的,而且线程的号码仍然用原来的7,这个原因是线程池检测到CPU的使用率已经达到100%,一直等待某个线程空闲。它并不会重新创建一个新的线程,这样就会减少线程间的上下文切换开销,以使总体性能更佳。 使用定时器 假如你曾经开发过Microsoft Win32的应用程序,你知道SetTimer函数是API之一,通过这个函数可以指定的一个窗口接收到来自系统时间周期的WM_TIMER消息。用这个方法遇到的第一个问题是你需要一个窗口去接收消息,所以你不能用在控制台应用程序中。另外,基于消息的实现并不是非常精确,假如你的应用程序正在处理其它消息,情况有可能更糟糕。 相对基于Win32的定时器来说, .NET 中一个很重要的改进就是创建不同的线程,该线程阻塞指定的时间,然后通知一个回调函数。这里的定时器不需要Microsoft的消息系统,所以这样就更精确,而且还能用于控制台应用程序中。以下代码显示了这个技术的一种实现: class MainApp {    static void Main()    {       MyTimer myTimer = new MyTimer(2000);       Console.ReadLine();    } } class MyTimer {    int m_period;    public MyTimer(int period)    {       Thread thread;       m_period = period;       thread = new Thread(new ThreadStart(TimerThread));       thread.Start();    }    void TimerThread()    {       Thread.Sleep(m_period);       OnTimer();    }    void OnTimer()    {       Console.WriteLine("OnTimer");    } } 这个代码一般用于Wn32应用中。每个定时器创建独立的线程,并且等待指定的时间,然后呼叫回调函数。犹如你看到的那样,这个实现的成本会非常高。如果你的应用程序使用了多个定时器,相对的线程数量也会随着使用定时器的数量而增长。 现在我们有.NET 提供的线程池,我们可以从池中改变请求的等待函数,这样就十分有效,而且会提升系统的性能。我们会遇到两个问题: n          假如线程池已满(所有的线程都在运行中),那么这个请求排到队列中等待,而且定时器不在精确。 n          假如创建了多个定时器,线程池会因为等待它们时间片失效而非常忙。 为了避免这些问题,.NET框架的线程池提供了独立于时间的请求。用了这个函数,我们可以不用任何线程就可以拥有成千上万个定时器,一旦时间片失效,这时,线程池将会处理这些请求。 这些特色出现在两个不同的类中:          System.Threading.Timer                    定时器的简单版本,它运行开发人员向线程池中的定期执行的程序指定一个代理(delegate). System.Timers.Timer System.Threading.Timer的组件版本,允许开发人员把它拖放到一个窗口表单(form)中,可以把一个事件作为执行的函数。 这非常有助于理解上述两个类与另外一个称为System.Windows.Forms.Timer.的类。这个类只是封装了Win32中消息机制的计数器,如果你不准备开发多线程应用,那么就可以用这个类。 在下面的例子中,我们将用System.Threading.Timer 类,定时器的最简单实现,我们只需要如下定义的构造方法 public Timer(TimerCallback callback,    object state,    int dueTime,    int period); 对于第一个参数(callback),我们可以指定定时执行的函数;第二个参数是传递给函数的通用对象;第三个参数是计时器开始执行前的延时;最后一个参数period,是两个执行之间的毫秒数。 下面的例子创建了两个定时器,timer1和timer2: class MainApp {    static void Main()    {       Timer timer1 = new Timer(new TimerCallback(OnTimer), 1, 0, 2000);       Timer timer2 = new Timer(new TimerCallback(OnTimer), 2, 0, 3000);       Console.ReadLine();    }    static void OnTimer(object obj)    {       Console.WriteLine("Timer: {0} Thread: {1} Is pool thread: {2}",          (int)obj,          Thread.CurrentThread.GetHashCode(),          Thread.CurrentThread.IsThreadPoolThread);    } } 输出: Timer: 1 Thread: 2 Is pool thread: True Timer: 2 Thread: 2 Is pool thread: True Timer: 1 Thread: 2 Is pool thread: True Timer: 2 Thread: 2 Is pool thread: True Timer: 1 Thread: 2 Is pool thread: True Timer: 1 Thread: 2 Is pool thread: True Timer: 2 Thread: 2 Is pool thread: True 犹如你看到的那样,两个定时器中的所有函数调用都在同一个线程中执行(ID = 2),应用程序使用的资源最小化了。 同步对象的执行 相对于定时器,.NET线程池允许在执行函数上同步对象,为了在多线程环境中的各线程之间共享资源,我们需要用.NET同步对象。 如果我们没有线程,或者线程必须阻塞直到事件收到信号,就像我前面提到一样,这会增加应用程序中总的线程数量,结果导致系统需要更多的资源和CPU时间。 线程池允许我们把请求进行排队,直到某个特殊的同步对象收到信号后执行。如果这个信号没有收到,请求函数将不需要任何线程,所以可以保证系统性能最优化。ThreadPool类提供了下面的方法: public static RegisteredWaitHandle RegisterWaitForSingleObject(    WaitHandle waitObject,    WaitOrTimerCallback callBack,    object state,    int millisecondsTimeOutInterval,    bool executeOnlyOnce); 第一个参数,waitObject 可以是任何继承于WaitHandle的对象:          Mutex      ManualResetEvent      AutoResetEvent 就像你看到的那样,只有系统的同步对象才能用在这里,就是继承自WaitHandle的对象。你不能用其它任何的同步机制,比如moniter 或者 read-write 锁。剩余的参数允许我们指明当一个对象收到信号后执行的函数(callBack);一个传递给函数的状态(state); 线程池等待对象的最大时间 (millisecondsTimeOutInterval) 和一个标识表明对象收到信号时函数只能执行一次, (executeOnlyOnce). 下面的代理声明目的是用在函数的回调: delegate void WaitOrTimerCallback(    object state,    bool timedOut);   如果参数 timeout 设置的最大时间已经失效,但是没有同步对象收到信号的花,这个函数就会被调用。 下面的例子用了一个手工事件和一个互斥量来通知线程池中的执行函数: class MainApp {    static void Main(string[] args)    {       ManualResetEvent evt = new ManualResetEvent(false);       Mutex mtx = new Mutex(true);       ThreadPool.RegisterWaitForSingleObject(evt,          new WaitOrTimerCallback(PoolFunc),          null, Timeout.Infinite, true);       ThreadPool.RegisterWaitForSingleObject(mtx,          new WaitOrTimerCallback(PoolFunc),          null, Timeout.Infinite, true);       for(int i=1;i<=5;i++)       {          Console.Write("{0}...", i);          Thread.Sleep(1000);       }       Console.WriteLine();       evt.Set();       mtx.ReleaseMutex();       Console.ReadLine();    }    static void PoolFunc(object obj, bool TimedOut)    {       Console.WriteLine("Synchronization object signaled, Thread: {0} Is pool: {1}",          Thread.CurrentThread.GetHashCode(),          Thread.CurrentThread.IsThreadPoolThread);    } } 结束显示两个函数都在线程池的同一线程中执行: 1...2...3...4...5... Synchronization object signaled, Thread: 6 Is pool: True Synchronization object signaled, Thread: 6 Is pool: True 异步I/O操作 线程池常见的应用场景就是I/O操作。多数应用系统需要读磁盘,数据发送到Sockets,因特网连接等等。所有的这些操作都有一些特征,直到他们执行操作时,才需要CPU时间。.NET 框架为所有这些可能执行的异步操作提供了I/O类。当这些操作执行完后,线程池中特定的函数会执行。尤其是在服务器应用程序中执行多线程异步操作,性能会更好。 在第一个例子中,我们将把一个文件异步写到硬盘中。看一看FileStream 的构造方法是如何使用的: public FileStream(    string path,    FileMode mode,    FleAccess access,    FleShare share,    int bufferSize,    bool useAsync); 最后一个参数非常有趣,我们应该对异步执行文件的操作设置useAsync为True。如果我们没有这样做,即使我们用了异步函数,它们的操作仍然会被主叫线程阻塞。 下面的例子说明了用一旦FileStream BeginWrite方法写文件操作结束,线程池中的一个回调函数将会被执行。注意我们可以在任何时候访问IAsyncResult接口,它可以用来了解当前操作的状态。我们可以用CompletedSynchronously 属性指示一个异步操作是否完成,而当一个操作结束时,IsCompleted 属性会设上一个值。IAsyncResult 提供了很多有趣的属性,比如:AsyncWaitHandle ,一旦操作完成,一个异步对象将会被通知。 class MainApp {    static void Main()    {       const string fileName = "temp.dat";       FileStream fs;       byte[] data = new Byte[10000];       IAsyncResult ar;         fs = new FileStream(fileName,          FileMode.Create,          FileAccess.Write,          FileShare.None,          1,          true);       ar = fs.BeginWrite(data, 0, 10000,          new AsyncCallback(UserCallback), null);       Console.WriteLine("Main thread:{0}",          Thread.CurrentThread.GetHashCode());       Console.WriteLine("Synchronous operation: {0}",          ar.CompletedSynchronously);       Console.ReadLine();    }    static void UserCallback(IAsyncResult ar)    {       Console.Write("Operation finished: {0} on thread ID:{1}, is pool: {2}",          ar.IsCompleted,          Thread.CurrentThread.GetHashCode(),          Thread.CurrentThread.IsThreadPoolThread);    } } 输出的结果显示了操作是异步执行的,一旦操作结束后,用户的函数就在线程池中执行。 Main thread:9 Synchronous operation: False Operation finished: True on thread ID:10, is pool: True 在应用Sockets的场景中,由于I/O操作通常比磁盘操作慢,这时用线程池就显得尤为重要。过程跟前面提到的差不多,Socket 类提供了多个方法用于执行异步操作:          BeginRecieve          BeginSend          BeginConnect          BeginAccept 假如你的服务器应用使用了Socket来与客户端通讯,一定会用到这些方法。这种方法取代了对每个客户端连接都启用一个线程的做法,所有的操作都在线程池中异步执行。          下面的例子用另外一个支持异步操作的类,HttpWebRequest。用这个类,我们可以建立一个到Web服务器的连接。这个方法叫BeginGetResponse, 但在这个例子中有一个很重要的区别。在上面最后一个示例中,我们没有用到从操作中返回的结果。但是,我们现在需要当一个操作结束时从Web服务器返回的响应,为了接收到这个信息,.NET中所有提供异步操作的类都提供了成对的方法。在HttpWebRequest这个类中,这个成对的方法就是:BeginGetResponse 和EndGetResponse。用了End版本,我们可以接收操作的结果。在我们的示例中,EndGetResponse 会从Web服务器接收响应。 虽然可以在任何时间调用EndGetResponse 方法,但在我们的例子中是在回调函数中做的。仅仅是因为我们想知道已经做了异步请求。如果我们在之前调用EndGetResponse ,这个调用将一直阻塞到操作完成。          在下面的例子中,我们发送一个请求到Microsoft Web,然后显示了接收到响应的大小。 class MainApp {    static void Main()    {       HttpWebRequest request;       IAsyncResult ar;         request = (HttpWebRequest)WebRequest.CreateDefault(          new Uri("http://www.microsoft.com"));       ar = request.BeginGetResponse(new AsyncCallback(PoolFunc), request);       Console.WriteLine("Synchronous: {0}", ar.CompletedSynchronously);       Console.ReadLine();    }    static void PoolFunc(IAsyncResult ar)    {       HttpWebRequest request;       HttpWebResponse response;         Console.WriteLine("Response received on pool: {0}",          Thread.CurrentThread.IsThreadPoolThread);       request = (HttpWebRequest)ar.AsyncState;       response = (HttpWebResponse)request.EndGetResponse(ar);       Console.WriteLine(" Response size: {0}",          response.ContentLength);    } } 下面刚开始结果信息表明,异步操作正在执行: Synchronous: False 过了一会儿,响应接收到了。下面的结果显示: Response received on pool: True    Response size: 27331 就像你看到的那样,一旦收到响应,线程池的异步函数就会执行。 监视线程池 ThreadPool 类提供了两个方法用来查询线程池的状态。第一个是我们可以从线程池获取当前可用的线程数量: public static void GetAvailableThreads(    out int workerThreads,    out int completionPortThreads); 从方法中你可以看到两种不同的线程:          WorkerThreads        工作线程是标准系统池的一部分。它们是被.NET框架托管的标准线程,多数函数是在这里执行的。显式的用户请求(QueueUserWorkItem方法),基于异步对象的方法(RegisterWaitForSingleObject)和定时器(Timer类) CompletionPortThreads 这种线程常常用来I/O操作,Windows NT, Windows 2000 和 Windows XP提供了一个步执行的对象,叫做IOCompletionPort。把API和异步对象关联起来,用少量的资源和有效的方法,我们就可以调用系统线程池的异步I/O操作。但是在Windows 95, Windows 98, 和 Windows Me有一些局限。比如: 在某些设备上,没有提供IOCompletionPorts 功能和一些异步操作,如磁盘和邮件槽。在这里你可以看到.NET框架的最大特色:一次编译,可以在多个系统下运行。根据不同的目标平台,.NET 框架会决定是否使用IOCompletionPorts API,用最少的资源达到最好的性能。 这节包含一个使用Socket 类的例子。在这个示例中,我们将异步建立一个连接到本地的Web服务器,然后发送一个Get请求。通过这个例子,我们可以很容易地鉴别这两种不同的线程。 using System; using System.Threading; using System.Net; using System.Net.Sockets; using System.Text;   namespace ThreadPoolTest {    class MainApp    {       static void Main()       {          Socket s;          IPHostEntry hostEntry;          IPAddress ipAddress;          IPEndPoint ipEndPoint;                   hostEntry = Dns.Resolve(Dns.GetHostName());          ipAddress = hostEntry.AddressList[0];          ipEndPoint = new IPEndPoint(ipAddress, 80);          s = new Socket(ipAddress.AddressFamily,             SocketType.Stream, ProtocolType.Tcp);          s.BeginConnect(ipEndPoint, new AsyncCallback(ConnectCallback),s);                    Console.ReadLine();       }       static void ConnectCallback(IAsyncResult ar)       {          byte[] data;          Socket s = (Socket)ar.AsyncState;          data = Encoding.ASCII.GetBytes("GET /"n");            Console.WriteLine("Connected to localhost:80");          ShowAvailableThreads();          s.BeginSend(data, 0,data.Length,SocketFlags.None,             new AsyncCallback(SendCallback), null);       }       static void SendCallback(IAsyncResult ar)       {          Console.WriteLine("Request sent to localhost:80");          ShowAvailableThreads();       }       static void ShowAvailableThreads()       {          int workerThreads, completionPortThreads;            ThreadPool.GetAvailableThreads(out workerThreads,             out completionPortThreads);          Console.WriteLine("WorkerThreads: {0}," +             " CompletionPortThreads: {1}",             workerThreads, completionPortThreads);       }    } } 如果你在Microsoft Windows NT, Windows 2000, or Windows XP 下运行这个程序,你将会看到如下结果: Connected to localhost:80 WorkerThreads: 24, CompletionPortThreads: 25 Request sent to localhost:80 WorkerThreads: 25, CompletionPortThreads: 24 如你所看到地那样,连接用了工作线程,而发送数据用了一个完成端口(CompletionPort),接着看下面的顺序: 1.   我们得到一个本地IP地址,然后异步连接到那里。 2.   Socket在工作线程上执行异步连接操作,因为在Socket上,不能用Windows 的IOCompletionPorts来建立连接。 3.   一旦连接建立了,Socket类调用指明的函数ConnectCallback,这个回调函数显示了线程池中可用的线程数量。我们可以看到这些是在工作线程中执行的。 4.   在用ASCII码对Get请求进行编码后,我们用BeginSend方法从同样的函数ConnectCallback 中发送一个异步请求。 5.   Socket上的发送和接收操作可以通过IOCompletionPort 来执行异步操作,所以当请求做完后,回调函数就会在一个CompletionPort类型的线程中执行。因为函数本身显示了可用的线程数量,所以我们可以通过这个来查看,对应的完成端口数已经减少了多少。 如果我们在Windows 95, Windows 98, 或者 Windows Me平台上运行相同的代码,会出现相同的连接结果,请求将被发送到工作线程,而非完成端口。你应该知道的很重要的一点就是,Socket类总是会利用最优的可用机制,所以你在开发应用时,可以不用考虑目标平台是什么。        你已经看到在上面的例子中每种类型的线程可用的最大数是25。我们可以用GetMaxThreads返回这个值: public static void GetMaxThreads(    out int workerThreads,    out int completionPortThreads); 一旦到了最大的数量,就不会创建新线程,所有的请求都将被排队。假如你看过ThreadPool类的所有方法,你将发现没有一个允许我们更改最大数的方法。就像我们前面提到的那样,线程池是每个处理过程的唯一共享资源。这就是为什么不可能让应用程序域去更改这个配置的原因。想象一下出现这种情况的后果,如果有第三方组件把线程池中线程的最大数改为1,整个应用都会停止工作,甚至在进程中其它的应用程序域都将受到影响。同样的原因,公共语言运行时的宿主也有可能去更改这个配置。比如:ASP.NET允许系统管理员更改这个数字。   死锁 在你的应用程序使用线程池之前,还有一个东西你应该知道:死锁。在线程池中执行一个实现不好的异步对象可能导致你的整个应用系统中止运行。        设想你的代码中有个方法,它需要通过Socket连接到一个Web服务器上。一个可能的实现就是用Socket 类中的BeginConnect方法异步打开一个连接,然后用EndConnect方法等待连接的建立。代码如下:         class ConnectionSocket {    public void Connect()    {       IPHostEntry ipHostEntry = Dns.Resolve(Dns.GetHostName());       IPEndPoint ipEndPoint = new IPEndPoint(ipHostEntry.AddressList[0],          80);       Socket s = new Socket(ipEndPoint.AddressFamily, SocketType.Stream,          ProtocolType.Tcp);       IAsyncResult ar = s.BeginConnect(ipEndPoint, null, null);       s.EndConnect(ar);    } } 多快,多好。调用BeginConnect使异步操作在线程池中执行,而EndConnect一直阻塞到连接被建立。        如果线程池中的一个执行函数中用了这个类的方法,将会发生什么事情呢?设想线程池的大小只有两个线程,然后用我们的连接类创建了两个异步对象。当这两个函数同时在池中执行时,线程池已经没有用于其它请求的空间了,除非直到某个函数结束。问题是这些函数调用了我们类中的Connect方法,这个方法在线程池中又发起了一个异步操作。但线程池一直是满的,所以请求就一直等待任何空闲线程的出现。不幸的是,这将永远不会发生,因为使用线程池的函数正等待队列函数的结束。结论就是:我们的应用系统已经阻塞了。        我们以此推断25个线程的线程池的行为。假如25个函数都等待异步对象操作的结束。结果将是一样的,死锁一样会出现。        在下面的代码片断中,我们使用了这个类来说明问题: class MainApp {    static void Main()    {       for(int i=0;i<30;i++)       {          ThreadPool.QueueUserWorkItem(new WaitCallback(PoolFunc));       }       Console.ReadLine();    }      static void PoolFunc(object state)   {       int workerThreads,completionPortThreads;       ThreadPool.GetAvailableThreads(out workerThreads,          out completionPortThreads);       Console.WriteLine("WorkerThreads: {0}, CompletionPortThreads: {1}",          workerThreads, completionPortThreads);         Thread.Sleep(15000);       ConnectionSocket connection = new ConnectionSocket();       connection.Connect();    } } 如果你运行这个例子,你将看到池中的线程是如何把线程的可用数量减少到零的,接着应用中止,死锁出现了。        如果你想在你的应用中避免出现死锁,永远不要阻塞正在等待线程池中的其它函数的线程。这看起来很容易,但记住这个规则意味着有两条: n          不要创建这样的类,它的同步方法在等待异步函数。因为这种类可能被线程池中的线程调用。 n          不要在任何异步函数中使用这样的类,如果它正等待着这个异步函数。 如果你想检测到应用中的死锁情况,那么就当你的系统挂起时,检查线程池中的线程可用数。线程的可用数量已经没有并且CPU的使用率为0 ,这是很明显的死锁症状。你应该检查你的代码,以确定哪个在线程中执行的函数正在等待异步操作,然后删除它。        有关安全性          如果你再看看ThreadPool类,你会看到有两个方法我们没有用到,UnsafeQueueUserWorkItem 和UnsafeRegisterWaitForSingleObject。 为了完全理解这些方法,首先,我们必须回忆 .NET框架中安全策略是怎么运作的。           Windows安全机制是关注资源。操作系统本身允许对文件,用户,注册表键值和任何其它的系统资源设定权限。这种方法对应用系统的用户认证非常有效,但当出现用户对他使用的系统产生不信任的情况时,这就会有些局限性。例如这些程序是从Internet下载的。在这种情况下,一旦用户安装了这个程序,它就可以执行用户权限范围内的任何操作。举个例子,假如用户可以删除他公司内的任何共享文件,任何从Internet下载的程序也都可以这样做。          .NET 提供了应用到程序的安全性策略,而不是用户。这就是说,在用户权限的范围内,我们可以限制任何执行单元(程序集)使用的资源。通过MMC,我们可以根据条件定义一组程序集,然后为每组设置不同的策略,一个典型的例子就是限制从Internet下载的程序访问磁盘的权限。          为了让这个功能运转起来,.NET 框架必须维护一个不同程序集之间的调用栈。假设一个应用没有权限访问磁盘,但是它调用了一个对整个系统都可以访问的类库,当第二个程序集执行一个磁盘的操作时,设置到这个程序集的权限允许这样做,但是权限不会被应用到主叫程序集,.NET不仅要检查当前程序集的权限,而且会检查整个调用栈的权限。这个栈已经被高度优化了,但是它们给两个不同程序集之间的调用增加了额外的负担。          UnsafeQueueUserWorkItem , UnsafeRegisterWaitForSingleObject与 QueueUserWorkItem , RegisterWaitForSingleObject两个方法类似。由于是非安全版本不会维护它们执行函数之间的调用栈,所以非安全版本运行的更快些。但是回调函数将只在当前程序集的安全策略下执行,它就不能应用权限到整个调用栈中的程序集。          我的建议是仅在性能非常重要的、安全已经控制好的极端情况下才用非安全版本。例如,你构建的应用程序不会被其它的程序集调用,或者仅被很明确清楚的程序集使用,那么你可以用非安全版本。如果你开发的类库会被第三方应用程序中使用,那么你就不应该用这些方法,因为它们可能用你的库获取访问系统资源的权限。          在下面例子中,你可以看到用UnsafeQueueUserWorkItem方法的风险。我们将构建两个单独的程序集,在第一个程序集中我们将在线程池中创建一个文件,然后我们将导出一个类以使这个操作可以被其它的程序集执行。 using System; using System.Threading; using System.IO; namespace ThreadSecurityTest {    public class PoolCheck    {       public void CheckIt()       {          ThreadPool.QueueUserWorkItem(new WaitCallback(UserItem), null);       }       private void UserItem(object obj)       {          FileStream fs = new FileStream("test.dat", FileMode.Create);          fs.Close();          Console.WriteLine("File created");       }    } } 第二个程序集引用了第一个,并且用了CheckIt 方法去创建一个文件: using System; namespace ThreadSecurityTest {    class MainApp    {       static void Main()       {          PoolCheck pc = new PoolCheck();          pc.CheckIt();          Console.ReadLine();       }    } } 编译这两个程序集,然后运行main应用。默认情况下,你的应用被配置为允许执行磁盘操作,所以系统成功生成文件。      File created 现在,打开.NET框架的配置。为了简化这个例子,我们仅创建一个代码组关联到main应用。接着展开 运行库安全策略/ 计算机/ 代码组/ All_Code /,增加一个叫ThreadSecurityTest的组。在向导中,选择Hash 条件并导入Hash到我们的应用中,设置为Internet 级别,并选择“该策略级别将只具有与此代码组关联的权限集中的权限”选项。 运行应用程序,看看会发生什么情况: Unhandled Exception: System.Security.SecurityException: Request for the    permission of type System.Security.Permissions.FileIOPermission,       mscorlib, Version=1.0.3300.0, Culture=neutral,          PublicKeyToken=b77a5c561934e089 failed. 我们的策略开始工作,系统已经不能创建文件了。这是因为.NET框架为我们维护了一个调用栈才使它成为了可能,虽然创建文件的库有权限去访问系统。 现在把库中的QueueUserWorkItem替换为UnsafeQueueUserWorkItem,再次编译程序集,然后运行Main程序。现在的结果是: File created 即使我们的系统没有足够的权限去访问磁盘,但我们已经创建了一个向整个系统公开它的功能的库,却没有维护它的调用栈。记住一个金牌规则: 仅在你的代码不允许让其它的应用系统调用,或者当你想要严格限制访问很明确清楚的程序集,才使用非安全的函数。   结束          在这篇文章中,我们知道了为什么在我们的服务器应用中需要使用线程池来优化资源和CPU的利用。我们学习了一个线程池是如何实现的,需要考虑多个因素如:CPU使用的百分比,队列请求或者系统的处理器数量。          .NET提供了丰富的线程池的功能以让我们的应用程序使用, 并且与.NET框架的类紧密地集成在一起。这个线程池是高度优化了的,它只需要最少的CPU时间和资源,而且总能适应目标平台。          因为与框架集成在一起,所以框架中的大部分类都提供了使用线程池的内在功能,给开发人员提供了集中管理和监视应用中的线程池的功能。鼓励第三方组件使用线程池,这样它们的客户就可以享受.NET所提供的全部功能。允许执行用户函数,定时器,I/O操作和同步对象。          假如你在开发服务器应用系统,只要有可能就在你的请求处理系统中使用线程池。或者你开发了一个让服务器程序使用的库,那么尽可能提供系统线程池的异步对象处理。