一、在使用DataTable.select进行排序时,直接写DataRow[] drr = dt.Select("POSTPARID='" + node.Value.ToString() + "' order by DISPINDEX ASC");
这样写会报如题错误:“order”运算符后缺乏操做数。
改为DataRow[] drr = dt.Select("POSTPARID='" + node.Value.ToString() + "'", " DISPINDEX ASC")便可
3、自定义属性
描述
属性提供功能强大的方法以将声明信息与 C# 代码(类型、方法、属性等)相关联。属性与程序实体关联后,便可在运行时使用名为“反射”的技术查询属性。
属性以两种形式出现:
-
一种是在公共语言运行库 (CLR) 中定义的属性。
-
另外一种是能够建立的用于向代码中添加附加信息的自定义属性。此信息可在之后以编程方式检索。
自定义属性的做用
有时候咱们须要给一个类或者类中的成员加上一些属性或者附加信息,让类或者变量的功能更明确可控制的细粒度更高,打个简单的比方:数据库里面的一张表,表中的每个字段都有不少属性,如是否主键,默认值,注释信息等等,咱们在编写实体类的时候,如何表示这些信息呢?经过自定义属性能够实现。
自定义属性的实现步骤
一、声明一个类,并将 AttributeUsageAttribute 属性应用到该类中。类的名称即为新属性的名称
二、声明该类从 System.Attribute 继承:
三、定义 Private 字段来存储属性值:
四、须要时,请为属性建立构造函数:
五、为属性 (Attribute) 定义方法、字段和属性 (Property):
实例一个:
属性类(和相关枚举)
/// <summary>
/// 数据库字段的用途。
/// </summary>
public enum EnumDBFieldUsage
{
/// <summary>
/// 未定义。
/// </summary>
None = 0x00,
/// <summary>
/// 用于主键。
/// </summary>
PrimaryKey = 0x01,
/// <summary>
/// 用于惟一键。
/// </summary>
UniqueKey = 0x02,
/// <summary>
/// 由系统控制该字段的值。
/// </summary>
BySystem = 0x04
}
[AttributeUsage(AttributeTargets.Property, Inherited = true)]
public class DBFieldAttribute:Attribute
{
EnumDBFieldUsage m_usage;
string m_strFieldName;
string m_strDescription;
object m_defaultValue;
public DBFieldAttribute(string strFieldName,object defaultValue,EnumDBFieldUsage usage,string strDescription)
{
m_strFieldName = strFieldName;
m_defaultValue = defaultValue;
m_usage = usage;
m_strDescription = strDescription;
}
public DBFieldAttribute(string fieldName) : this(fieldName,null, EnumDBFieldUsage.None,null)
{ }
public DBFieldAttribute(string fieldName, EnumDBFieldUsage usage) : this(fieldName, null,usage, null)
{ }
// 获取该成员映射的数据库字段名称。
public string FieldName
{
get
{
return m_strFieldName;
}
set
{
m_strFieldName = value;
}
}
// 获取该字段的默认值
public object DefaultValue
{
get
{
return m_defaultValue;
}
set
{
m_defaultValue = value;
}
}
}
此代码说明了如何制做自定义属性类。其实跟通常的类的区别就是此类继承自Attribute,加上AttributeUsage是属性上的属性,是可选的。
数据访问层实体类:
class DalObj
{
string m_strTableName;
int m_nID;
string m_strName;
string m_password;
public DalObj(string strTableName)
{
m_strTableName = strTableName;
}
[DBField("id",EnumDBFieldUsage.PrimaryKey)]
public int ID
{
get { return m_nID; }
set { m_nID = value; }
}
[DBField("name",DefaultValue="游客")]
public string Name
{
get { return m_strName; }
set { m_strName = value; }
}
[DBField("pwd")]
public string PassWord
{
get { return m_password; }
set { m_password = value; }
}
}
此代码说明了如何使用自定义的属性。有两点须要注意的地方
第一:类名能够跟自定义的类名同样,也能够加上或减去后面的Attribute,本例子中就是使用的时候跟自定义的类名减小了“Attribute”。
第二:属性参数填写方法,若是自定义属性类(例子中DBFieldAttribute)本身的构造函数带参数,那么这些参数是必选的,能够重载构造函数以知足不一样组合,必选参数填完以后,能够继续给自定义属性类中的公共成员带命名地赋值,如例子中的 DefaultValue="游客" 一句就是命名参数。
遍历自定义属性的代码:
DalObj dalObj = new DalObj("users");
StringBuilder sb = new StringBuilder();
foreach (PropertyInfo proInfo in dalObj.GetType().GetProperties())
{
object[] attrs = proInfo.GetCustomAttributes(typeof(DBFieldAttribute), true);
if (attrs.Length == 1)
{
DBFieldAttribute attr = (DBFieldAttribute)attrs[0];
sb.Append(attr.FieldName + ":" + (attr.DefaultValue == null ? "null" : attr.DefaultValue.ToString()) + "\r\n");
}
}
MessageBox.Show(sb.ToString());
此代码说明了如何检索自定义属性的值,主要用到了GetCustomAttributes来获取属性值。
4、多线程
1.对于Thread操做的异常处理
public static void Main()
{
try
{
Thread th = new Thread(DoWork);
th.Start();
}
catch (Exception ex)
{
// Non-reachable code
Console.WriteLine ("Exception!");
}
}
static void DoWork()
{
……
throw null; // Throws a NullReferenceException
}
在DoWork函数里抛出的异常时不会被主线程的try,catch捕捉的,各个线程应该有本身的try,catch去处理线程异常。
正确写法:
public static void Main()
{
Thread th = new Thread(DoWork);
th.Start();
}
static void DoWork()
{
try
{
……
throw null; // The NullReferenceException will be caught below
}
catch (Exception ex)
{
Typically log the exception, and/or signal another thread
that we've come unstuck
...
}
}
2. 异步函数的异常处理
例如如 WebClient中的 UploadStringAsync,它的异常会在UploadStringCompleted的参数error里
static void Main(string[] args)
{
WebClient webClient = new WebClient();
webClient.UploadStringCompleted += new UploadStringCompletedEventHandler((sender, e) =>
{
if (e.Error != null)
{
Console.WriteLine(e.Error.Message);
}
});
webClient.UploadStringAsync(new Uri("http://www.baidu.com"), "1111");
Console.ReadKey();
}
3 Task的异常处理
把try包含task.wait()函数,就能捕捉task的异常
Task task = Task.Run (() => { throw null; });
try
{
task.Wait();
}
catch (AggregateException aex)
{
if (aex.InnerException is NullReferenceException)
Console.WriteLine ("Null!");
else
throw;
}
或者 在continue函数里处理TaskContinuationOptions.OnlyOnFaulted的状态
Task<List<int>> taskWithFactoryAndState =
Task.Factory.StartNew<List<int>>((stateObj) =>
{
List<int> ints = new List<int>();
for (int i = 0; i < (int)stateObj; i++)
{
ints.Add(i);
if (i > 100)
{
InvalidOperationException ex =
new InvalidOperationException("oh no its > 100");
ex.Source = "taskWithFactoryAndState";
throw ex;
}
}
return ints;
}, 2000);
//and setup a continuation for it only on when faulted
taskWithFactoryAndState.ContinueWith((ant) =>
{
AggregateException aggEx = ant.Exception;
Console.WriteLine("OOOOPS : The Task exited with Exception(s)");
foreach (Exception ex in aggEx.InnerExceptions)
{
Console.WriteLine(string.Format("Caught exception '{0}'",
ex.Message));
}
}, TaskContinuationOptions.OnlyOnFaulted);
//and setup a continuation for it only on ran to completion
taskWithFactoryAndState.ContinueWith((ant) =>
{
List<int> result = ant.Result;
foreach (int resultValue in result)
{
Console.WriteLine("Task produced {0}", resultValue);
}
}, TaskContinuationOptions.OnlyOnRanToCompletion);
Console.ReadLine();
4 c# 5.0 中的async ,await 异常捕捉
static async Task ThrowAfter(int timeout, Exception ex)
{
await Task.Delay(timeout);
throw ex;
}
static async Task MissHandling()
{
var t1 = ThrowAfter(1000, new NotSupportedException("Error 1"));
try
{
await t1;
}
catch (NotSupportedException ex)
{
Console.WriteLine(ex.Message);
}
}
5、线程同步
原文地址:http://www.cnblogs.com/baoconghui/archive/2013/05/18/3085253.html
CSharp中的多线程——线程同步基础
1、同步要领
1.阻止 (Blocking)
当一个简易阻止方法、锁系统、信号系统等方式处于等待或暂停的状态,被称为被阻止。一旦被阻止,线程马上放弃它被分配的CPU时间,将 它的ThreadState属性添加为WaitSleepJoin状态,不在安排时间直到中止阻止。中止阻止在任意四种状况下发生(关掉电 脑的电源可不算!):
- 阻止的条件已获得知足
- 操做超时(若是timeout被指定了)
- 经过Thread.Interrupt中断了
- 经过Thread.Abort放弃了
当线程经过(不建议)Suspend 方法暂停,不认为是被阻止了。
2.休眠 和 轮询
调用Thread.Sleep阻止当前的线程指定的时间(或者直到中断):
static void Main() { Thread.Sleep (0); // 释放CPU时间片 Thread.Sleep (1000); // 休眠1000毫秒 Thread.Sleep (TimeSpan.FromHours (1)); // 休眠1小时 Thread.Sleep (Timeout.Infinite); // 休眠直到中断 }
更确切地说,Thread.Sleep放弃了占用CPU,请求不在被分配时间直到给定的时间通过。Thread.Sleep(0)放弃CPU的时间 刚刚够其它在时间片队列里的活动线程(若是有的话)被执行。
线程类同时也提供了一个SpinWait方法,它使用轮询CPU而非放弃CPU时间的方式,保持给定的迭代次数进行“无用地繁 忙”。
一个处于spin-waiting的线程的ThreadState不是WaitSleepJoin状态,而且也不会被其它的线程过早的中 断(Interrupt)。
SpinWait不多被使用,它的做用是等待一个在极短期(可能小于一微秒)内可准备好的可预期的资源,而 不用调用Sleep方法阻止线程而浪费CPU时间。不过,这种技术的优点只有在多处理器计算机:对单一处理器的电脑,直到轮 询的线程结束了它的时间片以前,一个资源没有机会改变状态,这有违它的初衷。而且调用SpinWait常常会花费较长的时间这 自己就浪费了CPU时间。
3.阻止vs.轮询
线程能够等待某个肯定的条件来明确轮询,好比 while (!proceed); 或 while (DateTime.Now < nextStartTime);这是很是浪费CPU时间的:对于CLR和操做系统而言,线程进行了一个重要的计算,因此分配了相应的资源!在这种状态下的 轮询线程不算是阻止,不像一个线程等待一个EventWaitHandle(通常使用这样的信号任务来构建)。
阻止和轮询组合使用能够产生一些变换: while (!proceed) Thread.Sleep (x); // "轮询休眠!" x越大,CPU效率越高,折中方案是增大潜伏时间,任何20ms的花费是微不足道的,除非循环中的条件是极其复杂的。
除了稍有延迟,这种轮询和休眠的方式能够结合的很是好。
4.使用Join等待一个线程完成
能够经过Join方法阻止线程直到另外一个线程结束:
class JoinDemo { static void Main() { Thread t = new Thread (delegate() { Console.ReadLine(); }); t.Start(); t.Join(); // 等待直到线程完成 Console.WriteLine ("Thread t's ReadLine complete!"); } }
Join方法也接收一个使用毫秒或用TimeSpan类的超时参数,当Join超时是返回false,若是线程已终止,则返回true 。
2、锁和线程安全
锁实现互斥的访问,被用于确保在同一时刻只有一个线程能够进入特殊的代码片断,考虑下面的类:
class ThreadUnsafe { static int val1, val2; static void Go() { if (val2 != 0) Console.WriteLine (val1 / val2); val2 = 0; } }
这不是线程安全的:若是Go方法被两个线程同时调用,可能会获得在某个线程中除数为零的错误, 由于val2可能被一个线程 设置为零,而另外一个线程恰好执行到if和Console.WriteLine语句。
下面用lock来修正这个问题:
class ThreadSafe { static object locker = new object(); static int val1, val2; static void Go() { lock (locker) { if (val2 != 0) Console.WriteLine (val1 / val2); val2 = 0; } } }
一个等候竞争锁的线程被阻止时,其ThreadState 状态值为WaitSleepJoin。
C#的lock 语句其实是调用Monitor.Enter和Monitor.Exit,中间夹杂try-finally语句的简略版,下面是实际发生在以前例子中的Go方法:
Monitor.Enter (locker); try { if (val2 != 0) Console.WriteLine (val1 / val2); val2 = 0; } finally { Monitor.Exit (locker); }
在同一个对象上,在调用第一个以前Monitor.Enter而先调用了Monitor.Exit将引起异常。Monitor 也提供了TryEnter方法来实现一个超时功能——也用毫秒或TimeSpan,若是得到了锁返回true,反之没有得到返 回false,由于超时了。TryEnter也能够没有超时参数,“测试”一下锁,若是锁不能被获取的话就马上超时。
选择同步对象
任何对全部有关系的线程均可见的对象均可以做为同步对象,但要服从一个硬性规定:它必须是引用类型。
也强烈建议同步对象最好私有在类里面(好比一个私有实例字段),防止无心间从外部锁定相同的对象。
好比下面List :
class ThreadSafe { List <string> list = new List <string>(); void Test() { lock (list) { list.Add ("Item 1"); ...
一个专门字段是经常使用的(如在先前的例子中的locker) , 由于它能够精确控制锁的范围和粒度。
锁并无以任何方式阻止对同步对象自己的访问,换言之,x.ToString()不会因为另外一个线程调用lock(x) 而被 阻止,二者都要调用lock(x) 来完成阻止工做。
嵌套锁定
线程能够重复锁定相同的对象,能够经过屡次调用Monitor.Enter或lock语句来实现。当对应编号的Monitor.Exit被调用或 最外面的lock语句完成后,对象那一刻被解锁。这就容许最简单的语法实现一个方法的锁调用另外一个锁:
static object x = new object(); static void Main() { lock (x) { Console.WriteLine ("I have the lock"); Nest(); Console.WriteLine ("I still have the lock"); } //在这锁被释放 } static void Nest() { lock (x) { ... } //释放了锁?没有彻底释放! }
线程只能在最开始的锁或最外面的锁时被阻止。
什么时候进行锁定
做为一项基本规则,任何和多线程有关的会进行读和写的字段应当加锁。甚至是极日常的事情——单一字段的赋值操做,都必须考虑到同步问题。
锁和原子操做
若是有不少变量在一些锁中老是进行读和写的操做,那么你能够称之为原子操做。咱们假设x 和 y不停地读和赋值,他们 在锁内经过locker锁定:
lock (locker) { if (x != 0) y /= x; }
你能够认为x 和 y 经过原子的方式访问,由于代码段没有被其它的线程分开 或 抢占,别的线程改变x 和 y是无效的输出,你永 远不会获得除数为零的错误,保证了x 和 y老是被相同的排他锁访问。
性能考量
锁定自己是很是快的,一个锁在没有堵塞的状况下通常只需几十纳秒(十亿分之一秒)。若是发生堵塞,任务切换带来的开销 接近于数微秒(百万分之一秒)的范围内,尽管在线程重组实际的安排时间以前它可能花费数毫秒(千分之一秒)。而相反, 与此相形见绌的是该使用锁而没使用的结果就是带来数小时的时间,甚至超时。
若是耗尽并发,锁定会带来副作用,死锁和争用锁,耗尽并发因为太多的代码被放置到锁语句中了,引发其它线程没必要要的被 阻止。死锁是两线程彼此等待被锁定的内容,致使二者都没法继续下去。争用锁是两个线程任一个均可以锁定某个内容,如 果“错误”的线程获取了锁,则致使程序错误。
对于太多的同步对象死锁是很是容易出现的症状,一个好的规则是开始于较少的锁,在一个可信的状况下涉及过多的阻止出现 时,增长锁的粒度。
线程安全
线程安全的代码是指在面对任何多线程状况下,这代码都没有不肯定的因素。线程安全首先完成锁,而后减小在线程间交互的可能性。
一个线程安全的方法,在任何状况下能够可重入式调用。
线程安全与.NET Framework类型
锁定可被用于将非线程安全的代码转换成线程安全的代码。在.NET framework方面,几乎全部非初始类型的实例都 不是线程安全的,而若是全部的访问给定的对象都经过锁进行了保护的话,他们能够被用于多线程代码中.看这个例子,两个 线程同时为相同的List增长条目,而后枚举它:
class ThreadSafe { static List <string> list = new List <string>(); static void Main() { new Thread (AddItems).Start(); new Thread (AddItems).Start(); } static void AddItems() { for (int i = 0; i < 100; i++) lock (list) list.Add ("Item " + list.Count); string[] items; lock (list) items = list.ToArray(); foreach (string s in items) Console.WriteLine (s); } }
在这种状况下,咱们锁定了list对象自己,这个简单的方案是很好的。若是咱们有两个相关的list,也许咱们就要锁定一个共同 的目标——多是单独的一个字段,若是没有其它的list出现,显然锁定它本身是明智的选择。
枚举.NET的集合也不是线程安全的,在枚举的时候另外一个线程改动list的话,会抛出异常。胜于直接锁定枚举过程,在这个例子 中,咱们首先将项目复制到数组当中,这就避免了固定住锁由于咱们在枚举过程当中有潜在的耗时。.NET framework一个广泛模式——静态成员是线程 安全的,而一个实例成员则不是。
3、Interrupt 和 Abort
一个被阻止的线程能够经过两种方式被提早释放:
经过 Thread.Interrupt
经过 Thread.Abort
这必须经过另外活动的线程实现,等待的线程是没有能力对它的被阻止状态作任何事情的。
1.Interrupt方法
在一个被阻止的线程上调用Interrupt 方法,将强迫释放它,抛出ThreadInterruptedException异常,以下:
class Program { static void Main() { Thread t = new Thread (delegate() { try { Thread.Sleep (Timeout.Infinite); } catch (ThreadInterruptedException) { Console.Write ("Forcibly "); } Console.WriteLine ("Woken!"); }); t.Start(); t.Interrupt(); } }
中断一个线程仅仅释放它的当前的(或下一个)等待状态:它并不结束这个线程(固然,除非未处 理ThreadInterruptedException异常)。
若是Interrupt被一个未阻止的线程调用,那么线程将继续执行直到下一次被阻止时,它抛 出ThreadInterruptedException异常。用下面的测试避免这个问题:
if ((worker.ThreadState & ThreadState.WaitSleepJoin) > 0) worker.Interrupt();
这不是一个线程安全的方式,由于在if语句和worker.Interrupt间可能被抢占了。
2.Abort方法
被阻止的线程也能够经过Abort方法被强制释放,这与调用Interrupt类似,除了用ThreadAbortException异常代替 了ThreadInterruptedException异常,此外,异常将在catch里被从新抛出(在试图以有好方式处理异常的时候),直 到Thread.ResetAbort在catch中被调用;在这期间线程的ThreadState为AbortRequested。
在Interrupt 与 Abort 之间最大不一样在于它们调用一个非阻止线程所发生的事情。Interrupt继续工做直到下一次阻止发生,Abort在线程当前所执行的位置(可能甚至不在你的代码中)抛出异常。
4、线程状态
你能够经过ThreadState属性获取线程的执行状态。图1将ThreadState列举为“层”。ThreadState被设计的很恐怖,它以 按位计算的方式组合三种状态“层”,每种状态层的成员它们间都是互斥的,下面是全部的三种状态“层”:
- 运行 (running) / 阻止 (blocking) / 终止 (aborting) 状态(图1显示)
- 后台 (background) / 前台 (foreground) 状态 (ThreadState.Background)
- 不建议使用的Suspend 方法(ThreadState.SuspendRequested 和 ThreadState.Suspended)挂起的过程
总的来讲,ThreadState是按位组合零或每一个状态层的成员!一个简单的ThreadState例子:
Unstarted
Running
WaitSleepJoin
Background, Unstarted
SuspendRequested, Background, WaitSleepJoin
|
(所枚举的成员有两个历来没被用过,至少是当前CLR实现上:StopRequested 和 Aborted。)
还有更加复杂的,ThreadState.Running潜在的值为0 ,所以下面的测试不工做:
if ((t.ThreadState & ThreadState.Running) > 0) ...
你必须用按位与非操做符来代替,或者使用线程的IsAlive属性。可是IsAlive可能不是你想要的,它在被阻止或挂起的时候返 回true(只有在线程未开始或已结束时它才为false)。
假设你避开不推荐使用的Suspend 和 Resume方法,你能够写一个helper方法除去全部除了第一种状态层的成员,容许简单 测试计算完成。线程的后台状态能够经过IsBackground 独立地得到,因此实际上只有第一种状态层拥有有用的信息。
public static ThreadState SimpleThreadState (ThreadState ts) { return ts & (ThreadState.Aborted | ThreadState.AbortRequested | ThreadState.Stopped | ThreadState.Unstarted | ThreadState.WaitSleepJoin); }
ThreadState对调试或程序概要分析是无价之宝,与之不相称的是多线程的协同工做,由于没有一个机制存在:经过判 断ThreadState来执行信息,而不考虑ThreadState期间的变化。
5、等待句柄
lock语句(也称为Monitor.Enter / Monitor.Exit)是线程同步结构的一个例子。当lock对一段代码或资源实施排他访问 时, 有些同步任务是笨拙的或难以实现的,好比说传输信号给等待的工做线程开始任务。
Win32 API拥有丰富的同步系统,这在.NET framework以EventWaitHandle, Mutex 和 Semaphore类展露出来。而一些比较经常使用:例如Mutex类,在EventWaitHandle提供惟一的信号功能时,大多会成倍提升lock的效率。
EventWaitHandle有两个子类:AutoResetEvent 和 ManualResetEvent(不涉及到C#中的事件或委托)。这两个类都派生自它们的基类:它们仅有的不一样是它们用不一样的参数调用基类的构造函数。
性能方面,使用Wait Handles系统开销会花费在较小(微秒间),不会在它们使用的上下文中产生什么后果。
AutoResetEvent
AutoResetEvent就像一个用票经过的旋转门:插入一张票,让正确的人经过。类名字里的“auto”实际上就是旋转门自动关闭 或“从新安排”后来的人让其经过。一个线程等待或阻止经过在门上调用WaitOne方法(直到等到这个“one”,门才开) ,票的 插入则由调用Set方法。若是由许多线程调用WaitOne,在门前便造成了队列,一张票可能来自任意某个线程——换言之,任 何(非阻止)线程要经过AutoResetEvent对象调用Set方法来释放一个被阻止的的线程。
若是Set调用时没有任何线程处于等待状态,那么句柄保持打开直到某个线程调用了WaitOne 。可是在没人 等的时候重复地在门上调用Set方法不会容许在一队人都经过,在他们到达的时候:仅有下一我的能够经过,多余的票都被“浪费了"。
WaitOne接受一个可选的超时参数——当等待以超时结束时这个方法将返回false,为了不过多的阻止发生,WaitOne在等待整段时间里也能够通知离开当前的同步内容。
Reset方法提供在没有任何等待或阻止的时候关闭旋转门。
AutoResetEvent能够经过2种方式建立,第一种是经过构造函数:
EventWaitHandle wh = new AutoResetEvent (false);
若是布尔参数为真,Set方法在构造后马上被自动的调用,另外一个方法是经过它的基类EventWaitHandle:
EventWaitHandle wh = new EventWaitHandle (false, EventResetMode.Auto);
EventWaitHandle的构造器也容许建立ManualResetEvent(用EventResetMode.Manual定义). 在Wait Handle不在须要时候,你应当调用Close方法来释放操做系统资源。可是,若是一个Wait Handle将被用于程序(就像这一节的大多例子同样)的生命周期中,咱们就能够省略这个步骤,它将在程序域销毁时自动的被销毁。
接下来这个例子,一个线程开始等待直到另外一个线程发出信号。
class BasicWaitHandle { static EventWaitHandle wh = new AutoResetEvent (false); static void Main() { new Thread (Waiter).Start(); Thread.Sleep (1000); // 等一会... wh.Set(); // OK ——唤醒它 } static void Waiter() { Console.WriteLine ("Waiting..."); wh.WaitOne(); // 等待通知 Console.WriteLine ("Notified"); } }
执行过程为:先输入Waiting... 过1妙钟后,输出Notified.
建立跨进程的EventWaitHandle
EventWaitHandle的构造器容许以“命名”的方式进行建立,它有能力跨多个进程。名称是个简单的字符串,可能会无心地与别的冲突!若是名字使用了,你将引用相同潜在的EventWaitHandle,除非操做系统建立一个新的,看这个例子:
EventWaitHandle wh = new EventWaitHandle (false, EventResetMode.Auto,"MyCompany.MyApp.SomeName");
若是有两个程序都运行这段代码,他们将彼此能够发送信号,等待句柄能够跨这两个进程中的全部线程。
任务确认
设想咱们但愿在后台完成任务,不在每次咱们获得任务时再建立一个新的线程。咱们能够经过一个轮询的线程来完成:等待一 个任务,执行它,而后等待下一个任务。这是一个广泛的多线程方案。也就是在建立线程上切份内务操做,任务执行被序列 化,在多个工做线程和过多的资源消耗间排除潜在的不想要的操做。
咱们必须决定要作什么,可是,若是当新的任务来到的时候,工做线程已经在忙以前的任务了,设想这种情形下咱们需选择阻 止调用者直到以前的任务被完成。像这样的系统能够用两个AutoResetEvent对象实现:一个“ready”AutoResetEvent, 当准备好的时候,它被工做线程调用Set方法;和“go”AutoResetEvent,当有新任务的时候,它被调用线程调用Set方法。 在下面的例子中,一个简单的string字段被用于决定任务(使用了volatile 关键字声明,来确保两个线程均可以看到相同版 本):
class AcknowledgedWaitHandle { static EventWaitHandle ready = new AutoResetEvent (false); static EventWaitHandle go = new AutoResetEvent (false); static volatile string task; static void Main() { new Thread (Work).Start(); // 给工做线程发5次信号 for (int i = 1; i <= 5; i++) { ready.WaitOne(); // 首先等待,直到工做线程准备好了 task = "a".PadRight (i, 'h'); // 给任务赋值 go.Set(); // 告诉工做线程开始执行! } // 告诉工做线程用一个null任务来结束 ready.WaitOne(); task = null; go.Set(); } static void Work() { while (true) { ready.Set(); // 指明咱们已经准备好了 go.WaitOne(); // 等待被踢脱... if (task == null) return; // 优雅地退出 Console.WriteLine (task); } } }
输出结果:
ah
ahh
ahhh
ahhhh
注意咱们要给task赋null来告诉工做线程退出。假若咱们先调用ready.WaitOne的话,在工做线程上调用Interrupt 或Abort 效果是同样的。由于在调用ready.WaitOne后咱们就知道工做线程的确切位置,避免了中断任意代码的复杂性。调用Interrupt 或 Abort须要咱们在工做线程中捕捉异常。
生产者/消费者队列
另外一个广泛的线程方案是在后台工做进程从队列中分配任务。这叫作生产者/消费者队列:在工做线程中生产者入列任务,消费 者出列任务。在下面例子里,一个单独的AutoResetEvent被用于通知工做线程,它只有在用完任务时(队列为空)等待。一个通用的集 合类被用于队列,必须经过锁控制它的访问以确保线程安全。工做线程在队列为null任务时结束:
using System; using System.Threading; using System.Collections.Generic; class ProducerConsumerQueue : IDisposable { EventWaitHandle wh = new AutoResetEvent (false); Thread worker; object locker = new object(); Queue<string> tasks = new Queue<string>(); public ProducerConsumerQueue() { worker = new Thread (Work); worker.Start(); } public void EnqueueTask (string task) { lock (locker) tasks.Enqueue (task); wh.Set(); } public void Dispose() { EnqueueTask (null); // 告诉消费者退出 worker.Join(); // 等待消费者线程完成 wh.Close(); // 释听任何OS资源 } void Work() { while (true) { string task = null; lock (locker) if (tasks.Count > 0) { task = tasks.Dequeue(); if (task == null) return; } if (task != null) { Console.WriteLine ("Performing task: " + task); Thread.Sleep (1000); // 模拟工做... } else wh.WaitOne(); // 没有任务了——等待信号 } } }
下面是一个主方法测试这个队列:
class Test { static void Main() { using (ProducerConsumerQueue q = new ProducerConsumerQueue()) { q.EnqueueTask ("Hello"); for (int i = 0; i < 10; i++) q.EnqueueTask ("Say " + i); q.EnqueueTask ("Goodbye!"); } // 使用using语句的调用q的Dispose方法, // 它入列一个null任务,并等待消费者完成 } }
执行结果:
Performing task: Hello
Performing task: Say 1
Performing task: Say 2
Performing task: Say 3
...
...
Performing task: Say 9
Goodbye!
注意咱们明确的关闭了Wait Handle在ProducerConsumerQueue被销毁的时候,由于在程序的生命周期中咱们可能潜在地 建立和销毁许多这个类的实例。
ManualResetEvent
ManualResetEvent是AutoResetEvent变化的一种形式,它的不一样之处在于:在线程被WaitOne的调用而经过的时 候,它不会自动地reset,这个过程就像大门同样——调用Set打开门,容许任何数量的已执行WaitOne的线程经过;调 用Reset关闭大门,可能会引发一系列的“等待者”直到下次门打开。
你能够用一个布尔字段"gateOpen" (用 volatile 关键字来声明)与"spin-sleeping" – 方式结合——重复地检查标志,而后让 线程休眠一段时间的方式,来模拟这个过程。
ManualResetEvent有时被用于给一个完成的操做发送信号,又或者一个已初始化正准备执行工做的线程。
互斥(Mutex)
Mutex提供了与C#的lock语句一样的功能,这使它大多时候变得的冗余了。它的优点在于它能够跨进程工做——提供了一计算机范围的锁而胜于程序范围的锁。
对于一个Mutex类,WaitOne获取互斥锁,当被抢占后时发生阻止。互斥锁在执行了ReleaseMutex以后被释放,就 像C#的lock语句同样,Mutex只能从获取互斥锁的这个线程上被释放。
Mutex在跨进程的广泛用处是确保在同一时刻只有一个程序的的实例在运行,下面演示如何使用:
class OneAtATimePlease { // 使用一个应用程序的惟一的名称(好比包括你公司的URL) static Mutex mutex = new Mutex (false, "oreilly.com OneAtATimeDemo"); static void Main() { //进程中的的另外一个实例关闭以后等待5秒若是存在竞争——存在程序在 if (!mutex.WaitOne (TimeSpan.FromSeconds (5), false)) { Console.WriteLine ("Another instance of the app is running. Bye!"); return; } try { Console.WriteLine ("Running - press Enter to exit"); Console.ReadLine(); } finally { mutex.ReleaseMutex(); } } }
Mutex有个好的特性是,若是程序结束时而互斥锁没经过ReleaseMutex首先被释放,CLR将自动地释放Mutex。
Semaphore
Semaphore就像一个夜总会:它有固定的容量,这由保镖来保证,一旦它满了就没有任何人能够再进入这个夜总会,而且在 其外会造成一个队列。而后,当人一我的离开时,队列头的人即可以进入了。构造器须要至少两个参数——夜总会的活动的空 间,和夜总会的容量。
Semaphore 的特性与Mutex 和 lock有点相似,除了Semaphore没有“全部者”——它是不可知线程的,任何 在Semaphore内的线程均可以调用Release,而Mutex 和 lock仅有那些获取了资源的线程才能够释放它。
class SemaphoreTest { static Semaphore s = new Semaphore (3, 3); // Available=3; Capacity=3 static void Main() { for (int i = 0; i < 10; i++) new Thread (Go).Start(); } static void Go() { while (true) { s.WaitOne(); Thread.Sleep (100); // 每次只有3个线程能够到达这里 s.Release(); } } }
WaitAny, WaitAll 和 SignalAndWait
除了Set 和 WaitOne方法外,在类WaitHandle中还有一些用来建立复杂的同步过程的静态方法。
WaitAny, WaitAll 和 SignalAndWait使跨多个可能为不一样类型的等待句柄变得容易。 SignalAndWait多是最有用的了:他在某个WaitHandle上调用WaitOne,并在另外一个WaitHandle上自动地调 用Set。 你能够在一对EventWaitHandle上装配两个线程,而让它们在某个时间点“相遇” 第一个线程像这样: WaitHandle.SignalAndWait (wh1, wh2); 同时第二个线程作相反的事情:WaitHandle.SignalAndWait (wh2, wh1);
WaitHandle.WaitAny等待一组等待句柄任意一个发出信号,WaitHandle.WaitAll等待全部给定的句柄发出信号。与票据 旋转门的例子相似,这些方法可能同时地等待全部的旋转门——经过在第一个打开的时候(WaitAny状况下),或者等待直到 它们全部的都打开(WaitAll状况下)。
6、同步环境
与手工的锁定相比,你能够进行说明性的锁定,用衍生自ContextBoundObject 并标以Synchronization特性的类,它告 诉CLR自动执行锁操做,看这个例子:
using System; using System.Threading; using System.Runtime.Remoting.Contexts; [Synchronization] public class AutoLock : ContextBoundObject { public void Demo() { Console.Write ("Start..."); Thread.Sleep (1000); // 咱们不能抢占到这 Console.WriteLine ("end"); // 感谢自动锁! } } public class Test { public static void Main() { AutoLock safeInstance = new AutoLock(); new Thread (safeInstance.Demo).Start(); // 并发地 new Thread (safeInstance.Demo).Start(); // 调用Demo safeInstance.Demo(); // 方法3次 } }
CLR确保了同一时刻只有一个线程能够执行 safeInstance中的代码。它建立了一个同步对象来完成工做,并在每次调 用safeInstance的方法和属性时在其周围只可以行锁定。锁的做用域——这里是safeInstance对象,被称为同步环境。
那么,它是如何工做的呢?Synchronization特性的命名空间:System.Runtime.Remoting.Contexts是一个线 索。ContextBoundObject能够被认为是一个“远程”对象,这意味着全部方法的调用是被监听的。CLR自动的返回了一个具备相同方法和属性的AutoLock对象的代理对象,它扮演着一个中间者的 角色,这让监听成为可能。监听在每一个方法调用时增长了数微秒的时间。
自动同步不能用于静态类型的成员,和非继承自 ContextBoundObject(例如:Windows Form)的类。
若是AutoLock是一个集合类,好比说,咱们仍然须要一个像下面同样的锁,假设 运行在另外一个类里:
if (safeInstance.Count > 0) safeInstance.RemoveAt (0);
除非使用这代码的类自己是一个同步的ContextBoundObject!
同步环境能够扩展到超过一个单独对象的区域。默认地,若是一个同步对象被实例化从在另外一段代码以内,它们拥有共享相同 的同步环境(换言之,一个大锁!)。这个行为能够由改变Synchronization特性的构造器的参数来指定。使 用SynchronizationAttribute类定义的常量之一:
因此若是SynchronizedA的实例被实例化于SynchronizedB的对象中,若是SynchronizedB像下面这样声明的话,它们将有分离 的同步环境:
[Synchronization (SynchronizationAttribute.REQUIRES_NEW)]
public class SynchronizedB : ContextBoundObject { ...
越大的同步环境越容易管理,可是减小机会对有用的并发。 分离的同步环境会形成死锁,看这个例子:
[Synchronization] public class Deadlock : ContextBoundObject { public DeadLock Other; public void Demo() { Thread.Sleep (1000); Other.Hello(); } void Hello() { Console.WriteLine ("hello"); } } public class Test { static void Main() { Deadlock dead1 = new Deadlock(); Deadlock dead2 = new Deadlock(); dead1.Other = dead2; dead2.Other = dead1; new Thread (dead1.Demo).Start(); dead2.Demo(); } }
为每一个Deadlock的实例在Test内建立——一个非同步类,每一个实例将有它本身的同步环境,所以,有它本身的锁。当它们 彼此调用的时候,不会花太多时间就会死锁(确切的说是一秒!)。在死锁显而易见的状况下,这与使用明确的锁的方式造成鲜明的对比。
可重入性问题
线程安全方法有时候也被称为可重入式的,由于在它执行的时候能够被抢占部分线路,在另外的线程调用也不会带来坏效果。 从某个意义上讲,术语线程安全 和 可重入式的是同义的或者是贴义的。
不过在自动锁方式上,若是Synchronization的参数可重入式的 为true的话,可重入性会有潜在的问题:[Synchronization(true)] 同步环境的锁在执行离开上下文时被临时地释放。在以前的例子里,这将能预防死锁的发生;很明显很须要这样的功能。然而 一个反作用是,在这期间,任何线程均可以自由的调用在目标对象(“重进入”的同步上下文)的上任何方法,而很是复杂的多线 程中试图避免不释放资源是排在首位的。这就是可重入性的问题。 虽然可重入性是危险的,但有些时候它是不错的选择。好比:设想一个在其内部实现多线程同步的类,将逻辑工做线程运行在 不一样的语境中。在没有可重入性问题的状况下,工做线程在它们彼此之间或目标对象之间可能被无理地阻碍。 超过适用的大范围的锁定带来了其它状况没有带来的巨大麻烦——死锁,可重入性问题和被阉割的并发,使另外一个更简单的方案——手动的锁定变得更为合适。