C#多线程学习笔记之(abort与join配合使用)

合集下载
  1. 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
  2. 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
  3. 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。

C#多线程学习笔记之(abort与join配合使用)
using System;
using System.Threading;
namespace biovision.ihospital.his.Basic
{
public class Simple
{
public static int Main()
{
Console.WriteLine("Thread Start/stop/join sample");
Alpha alpha = new Alpha();
Thread t = new Thread(new ThreadStart(alpha.Beta));
t.Start();
while (!t.IsAlive) Thread.Sleep(1);
t.Abort();
//t.Join();
Console.WriteLine("alpha.Beta 已经结束,执行状态为" + t.IsAlive.ToString() + "线程状态为:" + t.ThreadState.ToString());
try
{
Console.WriteLine("试图重新启动 alpha.Beta");
t.Start();
}
catch (ThreadStateException)
{
Console.WriteLine("ThreadStateException 试图重新启动t线程"); Console.WriteLine("t线程终止后不能被重启");
Console.ReadLine();
} return 0;
}
}
public class Alpha
{
public void Beta()
{
while (true)
{
Console.WriteLine("Alpha.Beta 正在运行");
}
}
}
}
这段代码非常好懂,但是有一点我不懂的是我以前一直认为abort()方法会使线程终止,为什么还要调用线程t的join方法呢?官方给的解释是abort():以开始终止此线程的过程,调用此方法通常会终止此线程,join():阻止调用某个线程,直到某个线程终止为止。

当时死都想不通这两句话,直到把t.IsAlive(表示当前线程的执行状态)和t.ThreadState(该值包含线程的状态)打印出来后再想明白,现在先看看上面代码的运行结果:
奇怪,为什么线程abort()后isalive执行状态还是为TRUE呢,再看线程状态为AbortRequested,这意思是说线程已调用abort,但线程还未停止,于是再想想线程没停止,isalive属性为TRUE就变得合理了,那下面TRY语句块内再对线程重启就会产生错误了,都没停止何来产生重启呢?
但是问题还没有解决,如何才能让线程终止呢,难道abort不能让纯种终止吗?好了,先不要想那么多,我们把上述代码中t.join()注释符号给去掉,再来运行一下程序,结果如下:
好了,现在看看执行关态和线程状态我们得知线程终止了,而我们知道,当一个线程再调用abort()后是不能再start()了,所以同样会抛出异常,执行catch语句。

综合两种情况来看,开始我还以为,难道只用abort()不用join()就不能终止线程吗?但是再对着两种结果和两个方法给出的介绍仔细想想就不难得出以下结论:
1.abort()的功能是用来终止调用此方法的线程的,只是在多数情况下,它需要一点时间,有些延迟(可能在短时间内此线程还在执行)...
2.join()方法它的功能不是终止线程,而是在t线程终止之前,阻止正在结束(调用了abort()方法但还未结束)的t线程执行,同时使主线程等待,直到t线程终止(也就是abort()方法终止过程完毕)了再执行下面的代码,打印出来的结果,执行状态就为FALSE,线程状态也为停止了
注意:在没有调用JOIN方法前,这段代码的执行结果可能为图中两种结果,都会存在,这可能根据电脑的不同,abort()执行时间有关,所以,这里join保证了我们在执行下面的代码时,t线程实现了“真正”的终止,我想这就是join用在abort()后的妙处吧!
C#线程类Thread初步
.NET 基础类库的System.Threading命名空间提供了大量的类和接口支持多线程。

这个命名空间有很多的类。

System.Threading.Thread类是创建并控制线程,设置其优先级并获取其状态最为常用的类。

他有很多的方法,在这里我们将就比较常用和重要的方法做一下介绍: Thread.Start():启动线程的执行;
Thread.Suspend():挂起线程,或者如果线程已挂起,则不起作用;
Thread.Resume():继续已挂起的线程;
Thread.Interrupt():中止处于 Wait或者Sleep或者Join 线程状态的线程;
Thread.Join():阻塞调用线程,直到某个线程终止时为止
Thread.Sleep():将当前线程阻塞指定的毫秒数;
Thread.Abort():以开始终止此线程的过程。

如果线程已经在终止,则不能通过Thread.Start()来启动线程。

通过调用Thread.Sleep,Thread.Suspend或者Thread.Join可以暂停/阻塞线程。

调用Sleep()和Suspend()方法意味着线程将不再得到CPU时间。

这两种暂停线程的方法是有区
别的,Sleep()使得线程立即停止执行,但是在调用Suspend()方法之前,公共语言运行时必须到达一个安全点。

一个线程不能对另外一个线程调用Sleep()方法,但是可以调用Suspend()方法使得另外一个线程暂停执行。

对已经挂起的线程调用Thread.Resume()方法会使其继续执行。

不管使用多少次Suspend()方法来阻塞一个线程,只需一次调用Resume()方法就可以使得线程继续执行。

已经终止的和还没有开始执行的线程都不能使用挂起。

Thread.Sleep(int x)使线程阻塞x毫秒。

只有当该线程是被其他的线程通过调用Thread.Interrupt()或者Thread.Abort()方法,才能被唤醒。

如果对处于阻塞状态的线程调用Thread.Interrupt()方法将使线程状态改变,但是会抛出ThreadInterupptedException异常,你可以捕获这个异常并且做出处理,也可以忽略这个异常而让运行时终止线程。

在一定的等待时间之内,Thread.Interrupt()和Thread.Abort ()都可以立即唤醒一个线程。

我们可以通过使用Thread.Abort()方法来永久销毁一个线程,而且将抛出ThreadAbortException异常。

使终结的线程可以捕获到异常但是很难控制恢复,仅有的办法是调用Thread.ResetAbort()来取消刚才的调用,而且只有当这个异常是由于被调用线程引起的异常。

对于A和B两个线程,A线程可以正确的使用Thread.Abort()方法作用于B线程,但是B线程却不能调用Thread.ResetAbort()来取消Thread.Abort()操作。

Thread.Abort()方法使得系统悄悄的销毁了线程而且不通知用户。

一旦实施Thread.Abort()操作,该线程不能被重新启动。

调用了这个方法并不是意味着线程立即销毁,因此为了确定线程是否被销毁,我们可以调用Thread.Join()来确定其销毁,Thread.Join()是一个阻塞调用,直到线程的确是终止了才返回。

但是有可能一个线程调用Thread.Interrupt()方法来中止另外一个线程,而这个线程正在等待Thread.Join()调用的返回。

尽可能的不要用Suspend()方法来挂起阻塞线程,因为这样很容易造成死锁。

假设你挂起了一个线程,而这个线程的资源是其他线程所需要的,会发生什么后果。

因此,我们尽可能的给重要性不同的线程以不同的优先级,用Thread.Priority()方法来代替使用Thread.Suspend()方法。

Thread类有很多的属性,这些重要的属性是我们多线程编程必须得掌握的。

Thread.IsAlive属性:获取一个值,该值指示当前线程的执行状态。

如果此线程已启动并且尚未正常终止或中止,则为 true;否则为false。

属性:获取或设置线程的名称。

Thread.Priority 属性:获取或设置一个值,该值指示线程的调度优先级。

Thread.ThreadState 属性:获取一个值,该值包含当前线程的状态。

Thread状态
System.Threading.Thread.ThreadState属性定义了执行时线程的状态。

线程从创建到线程终止,它一定处于其中某一个状态。

当线程被创建时,它处在Unstarted状态,Thread类的Start() 方法将使线程状态变为Running状态,线程将一直处于这样的状态,除非我们调用了相应的方法使其挂起、阻塞、销毁或者自然终止。

如果线程被挂起,它将处于Suspended状态,除非我们调用resume()方法使其重新执行,这时候线程将重新变为Running状态。

一旦线程被销毁或者终止,线程处于Stopped状态。

处于这个状态的线程将不复存在,正如线程开始启动,线程将不可能回到Unstarted状态。

线程还有一个Background状态,它表明线程运行在前台还是后台。

在一个确定的时间,线程可能处于
多个状态。

据例子来说,一个线程被调用了Sleep而处于阻塞,而接着另外一个线程调用Abort方法于这个阻塞的线程,这时候线程将同时处于WaitSleepJoin和AbortRequested状态。

一旦线程响应转为Sle阻塞或者中止,当销毁时会抛出ThreadAbortException异常。

线程优先级
System.Threading.Thread.Priority枚举了线程的优先级别,从而决定了线程能够得到多少CPU时间。

高优先级的线程通常会比一般优先级的线程得到更多的CPU时间,如果不止一个高优先级的线程,操作系统将在这些线程之间循环分配CPU时间。

低优先级的线程得到的CPU时间相对较少,当这里没有高优先级的线程,操作系统将挑选下一个低优先级的线程执行。

一旦低优先级的线程在执行时遇到了高优先级的线程,它将让出CPU 给高优先级的线程。

新创建的线程优先级为一般优先级,我们可以设置线程的优先级别的值,如下面所示:
Highest
AboveNormal
Normal
BelowNormal
Lowest
C#中的线程之Abort陷阱
1.简介
C#中通常使用线程类Thread来进行线程的创建与调度,博主在本文中将分享多年C#开发中遇到的Thread使用陷阱。

Thread调度其实官方文档已经说明很详细了。

本文只简单说明,不做深入探讨。

如下代码展示了一个线程的创建与启动
staticvoid Main(string[] args)
{
Thread thd = newThread(newThreadStart(TestThread));
thd.IsBackground = false;
thd.Start();
}
staticvoidTestThread()
{
while (true)
{
Thread.Sleep(1);
}
}
我们可以通过
Thread.ThreadState 判断指定线程状态
Thread.Yield 切换线程
Thread .Interrupt 引发阻塞线程的中断异常
Thread .Join 等待线程完成
Thread.Abort 引发线程上的ThreadAborting异常
2.Abort陷阱的产生
本文要谈的是Thread.Abort。

有一定多线程开发经验的朋友一定听说过它。

官方文档如此描述:
在调用此方法的线程上引发ThreadAbortException,以开始终止此线程的过程。

调用此方法通常会终止线程。

这在实际中是非常有用的,相信大部分人都会迫不及待地在项目中用上Thread.Abort来终止线程(博主就是迫不及待地用到项目中了)。

不过对于不熟悉的API,使用之前一定先看懂文档(这是博主在吃过不少亏后的感言)
Abort调用还分为线程自身调用:
当线程对自身调用Abort 时,效果类似于引发异常;ThreadAbortException 会立刻发生,并且结果是可预知的。

但是,如果一个线程对另一个线程调用Abort,则将中断运行的任何代码。

还有一种可能就是静态构造函数被终止。

在极少数情况下,这可以防止在该应用程序域中创建该类的实例。

在 .NET Framework 1.0 版和1.1 版中,在finally 块运行时线程可能会中止,在这种情况下,finally 块将被中止。

被其它线程调用:
如果正在中止的线程是在受保护的代码区域,如catch 块、finally 块或受约束的执行区域,可能会阻止调用Abort 的线程。

如果调用Abort 的线程持有中止的线程所需的锁定,则会发生死锁。

由官方文档上的说明可知:Abort方法调用是需要特别注意避免静态构造函数的终止和锁的使用,这是通过文档我们能够获得的信息。

但不是全部!
陷阱一:线程中代码ThreadAbortException异常的处理
举个栗子
class Program
{
staticTcpClientm_TcpClient = newTcpClient();
staticvoid Main(string[] args)
{
m_TcpClient.Connect("192.168.31.100" , 8888);
Thread thd = newThread(newThreadStart(TestThread));
thd.IsBackground = false;
thd.Start();
Console.ReadKey();
Console.Write("线程Abort!");
thd.Abort();
Console.ReadKey();
}
staticvoidTestThread()
{
while (true)
{
byte[] sdDat = newbyte[10000 * 1024];
try
{
Thread.Sleep(1);
m_TcpClient.GetStream().Write(sdDat, 0, sdDat.Length);
}
catch (Exception ex)
{
// 异常处理
m_TcpClient.Close();
}
}
}
}
以上代码创建了一个Tcp连接,然后不间断向服务端发送数据流。

此时若服务端某种原因使用了Thread.Abort终止发送数据(只是终止发送数据,并不是要断开连接),那执行的结果与期望便大相径庭了。

这里正确的使用方式是在发生SocketException异常和ThreadAbortException 异常时分别处理
catch (SocketExceptionsckEx)
{
//socket异常处理
m_TcpClient.Close();
}catch(ThreadAbortExceptionthAbortEx)
{
}
在项目中大家都会遇到对第三方IO库的调用,如果恰好第三方库缺少对ThreadAbortException的异常处理,那你的代码使用ThreadAbort出现BUG的概率便大大提高了。

(实际上不光是第三方库,.NetFramework中API也并非完全考虑了此异常)陷阱二就说明了一个系统API对此异常的处理缺陷。

- 陷阱二:文件操作
同样我使用测试代码说明文件操作API的一个异常情况。

开启一个线程,对某个文件写数据(不断循环)代码如下:
class Program
{
staticvoid Main(string[] args)
{
Thread thd = newThread(newThreadStart(TestThread)); thd.IsBackground = false;
thd.Start();
Thread.Sleep(1000); //等待,确保代码已经开始执行
while (true)
{
if (thd.IsAlive)
{
thd.Abort();
Thread.Sleep(10);
}
else
{
Console.WriteLine("线程已经退出!");
break;
}
}
Console.ReadKey();
}
staticvoidTestThread()
{
while (true)
{
byte[] sdDat = newbyte[10240];
try
{
using (FileStream fs = File.Open("D:\\1.dat", FileMode.OpenOrCreate, FileAccess.ReadWrite))
{
fs.Write(sdDat, 0 , 10240);
Thread.Sleep(1); // 根据自己的运行环境调节休眠时间
}
}
catch (IOException ex)
{
Console.WriteLine("IO exception:" + ex.Message);
break;
}
catch (ThreadAbortException )
{
Thread.ResetAbort();
Console.WriteLine("ThreadAbortException ");
}catch(Exception ex)
{
Console.WriteLine("Other exception:" + ex.Message);
break;
}
}
Console.WriteLine("线程退出");
}
}
运行代码得到输出:(实际并非每次输出都一致,取决与执行代码计算机的当前状态,你也可以改变while循环中的休眠时间,输出较多或较少行ThreadAbortException)ThreadAbortException
ThreadAbortException
ThreadAbortException
IO exception:文件“D:\1.dat”正由另一进程使用,因此该进程无法访问此文件。

线程退出
线程已经退出!
这次代码里我使用ResetAbort处理ThreadAbortException异常,将Abort状态恢复并继续执行循环,而在IO异常与其它异常时候直接退出循环。

我们可以看到在ThreadAbortException打印了三次后,出发了IO异常:
IO exception:文件“D:\1.dat”正由另一进程使用,因此该进程无法访问此文件。

这个异常是如何产生的呢?各位不妨看看代码,分析下可能的原因。

首先,这段代码“看起来”的确是没有问题,大家知道在using里的new 的对象在代码段结束的时候,会自动调用Dispose方法释放资源。

重最开始的两次ThreadAbort异常被触发可以看出,即使在这种情况下,被占用的文件资源也已经被释放掉了。

当然,“看起来”与实际的效果还是有差距,在第四次执行就触发IOException了。

说明第三次的文件被占用后没有释放。

问题的关键就在第三次占用文件后为什么没有被释放?
我猜测有可能是fs的对象引用在赋值到fs之前就触发了ThreadAbortException异常,而File.Open代码中占用了文件资源后并在返回之前没有处理ThreadAbortException,导致在using代码段结束释放时,fs为空引用,那自然就无法调用其释放的代码了。

当然这些只是我的大胆猜测,为此我修改了本例中TestThread方法的代码,验证猜测。

staticvoidTestThread()
{
byte[] sdDat = newbyte[10240];
FileStream fs = null;
while (true)
{
try
{
fs = null;
using (fs = File.Open("D:\\1.dat", FileMode.OpenOrCreate, FileAccess.ReadWrite))
{
fs.Write(sdDat, 0, 10240);
Thread.Sleep(1);
}
}
catch (IOException ex)
{
Console.WriteLine("IO exception:" + ex.Message);
break;
}
catch (ThreadAbortException ex)
{
Thread.ResetAbort();
Console.WriteLine("ThreadAbortException (fs == null):[{0}]", fs == null);
}
catch (Exception ex)
{
Console.WriteLine("Other exception:" + ex.Message);
break;
}
}
Console.WriteLine("线程退出");
}
我把fs变量提出到while循环之前,并在每次调用using 代码段之前赋值为null,随后每次触发ThreadAbortException 时都打印出fs是否为空。

执行结果:
ThreadAbortException (fs == null):[False]
ThreadAbortException (fs == null):[True]
ThreadAbortException (fs == null):[False]
ThreadAbortException (fs == null):[False]
ThreadAbortException (fs == null):[False]
ThreadAbortException (fs == null):[False]
ThreadAbortException (fs == null):[False]
ThreadAbortException (fs == null):[False]
ThreadAbortException (fs == null):[True]
IO exception:文件“D:\1.dat”正由另一进程使用,因此该进程无法访问此文件。

线程退出
线程已经退出!
多次执行程序就可以看出,每次IOException异常发送的时候,上次打印的fs都为空。

那怎么解释中间有一次fs为空但没有触发IOException呢?记得我上面的分析吗?
File.Open在占用了文件资源后并在返回之前的Exception没有处理会出现问题,那么在占用文件资源之前出现Exception是不会出现占用资源未释放的问题的。

所以,问题的原因正如我分析的那样。

一个需要释放的类(资源)是不太适宜在可能会被Abort的线程中创建并释放的。

因为你不太可能完全保证资源占用的时候类赋值之前不会触发ThreadAbortException的。

3.结尾
在项目开发中,Thread类就如同一把双刃剑,功能强大得不得了,但是给代码理解与调试带来了一定程度上的困难。

如果非要问我在多线程开发上有什么建议的话,我想说,除非你已经在千锤百炼的开发经验中完全掌握了多线程,否则能不用它就不要用它吧!。

相关文档
最新文档