C#中的线程之Abort陷阱
.简介
C#中通常使用线程类Thread来进行线程的创建与调度,博主在本文中将分享多年C#开发中遇到的Thread使用陷阱。
Thread调度其实官方文档已经说明很详细了。本文只简单说明,不做深入探讨。 如下代码展示了一个线程的创建与启动 static void Main(string[] args)
{
Thread thd = new Thread(new ThreadStart(TestThread));
thd.IsBackground = false;
thd.Start();
}
static void TestThread()
{
while (true)
{
Thread.Sleep();
}
}
我们可以通过
Thread.ThreadState 判断指定线程状态
Thread.Yield 切换线程
Thread .Interrupt 引发阻塞线程的中断异常
Thread .Join 等待线程完成
Thread.Abort 引发线程上的ThreadAborting异常 .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
{
static TcpClient m_TcpClient = new TcpClient();
static void Main(string[] args)
{
m_TcpClient.Connect("192.168.31.100" , ); Thread thd = new Thread(new ThreadStart(TestThread));
thd.IsBackground = false;
thd.Start(); Console.ReadKey();
Console.Write("线程Abort!");
thd.Abort();
Console.ReadKey(); }
static void TestThread()
{
while (true)
{
byte[] sdDat = new byte[ * ];
try
{
Thread.Sleep();
m_TcpClient.GetStream().Write(sdDat, , sdDat.Length);
}
catch (Exception ex)
{
// 异常处理
m_TcpClient.Close();
}
}
}
}
以上代码创建了一个Tcp连接,然后不间断向服务端发送数据流。此时若服务端某种原因使用了Thread.Abort终止发送数据(只是终止发送数据,并不是要断开连接),那执行的结果与期望便大相径庭了。
这里正确的使用方式是在发生SocketException异常和ThreadAbortException 异常时分别处理
catch (SocketException sckEx)
{
//socket异常处理
m_TcpClient.Close();
}catch(ThreadAbortException thAbortEx)
{
} 在项目中大家都会遇到对第三方IO库的调用,如果恰好第三方库缺少对ThreadAbortException的异常处理,那你的代码使用ThreadAbort出现BUG的概率便大大提高了。(实际上不光是第三方库,.NetFramework中API也并非完全考虑了此异常)陷阱二就说明了一个系统API对此异常的处理缺陷。
- 陷阱二:文件操作
同样我使用测试代码说明文件操作API的一个异常情况。
开启一个线程,对某个文件写数据(不断循环)代码如下: class Program
{
static void Main(string[] args)
{ Thread thd = new Thread(new ThreadStart(TestThread));
thd.IsBackground = false;
thd.Start(); Thread.Sleep(); //等待,确保代码已经开始执行
while (true)
{
if (thd.IsAlive)
{
thd.Abort();
Thread.Sleep();
}
else
{
Console.WriteLine("线程已经退出!");
break;
}
}
Console.ReadKey(); }
static void TestThread()
{
while (true)
{
byte[] sdDat = new byte[];
try
{
using (FileStream fs = File.Open("D:\\1.dat", FileMode.OpenOrCreate, FileAccess.ReadWrite))
{
fs.Write(sdDat, , );
Thread.Sleep(); // 根据自己的运行环境调节休眠时间
}
}
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:\.dat”正由另一进程使用,因此该进程无法访问此文件。
线程退出
线程已经退出! 这次代码里我使用ResetAbort处理ThreadAbortException异常,将Abort状态恢复并继续执行循环,而在IO异常与其它异常时候直接退出循环。
我们可以看到在 ThreadAbortException打印了三次后,出发了IO异常: IO exception:文件“D:\.dat”正由另一进程使用,因此该进程无法访问此文件。 这个异常是如何产生的呢?各位不妨看看代码,分析下可能的原因。
首先,这段代码“看起来”的确是没有问题,大家知道在using里的new 的对象在代码段结束的时候,会自动调用Dispose方法释放资源。重最开始的两次ThreadAbort异常被触发可以看出,即使在这种情况下,被占用的文件资源也已经被释放掉了。当然, “看起来”与实际的效果还是有差距,在第四次执行就触发IOException了。说明第三次的文件被占用后没有释放。
问题的关键就在第三次占用文件后为什么没有被释放?
我猜测有可能是fs的对象引用在赋值到fs之前就触发了ThreadAbortException异常,而File.Open代码中占用了文件资源后并在返回之前没有处理ThreadAbortException,导致在using代码段结束释放时,fs为空引用,那自然就无法调用其释放的代码了。当然这些只是我的大胆猜测,为此我修改了本例中TestThread方法的代码,验证猜测。 static void TestThread()
{
byte[] sdDat = new byte[];
FileStream fs = null;
while (true)
{
try
{
fs = null;
using (fs = File.Open("D:\\1.dat", FileMode.OpenOrCreate, FileAccess.ReadWrite))
{
fs.Write(sdDat, , );
Thread.Sleep();
}
}
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:\.dat”正由另一进程使用,因此该进程无法访问此文件。
线程退出
线程已经退出! 多次执行程序就可以看出,每次IOException异常发送的时候,上次打印的 fs都为空。那怎么解释中间有一次fs为空但没有触发IOException呢?记得我上面的分析吗?File.Open在占用了文件资源后并在返回之前的Exception没有处理会出现问题,那么在占用文件资源之前出现Exception是不会出现占用资源未释放的问题的。所以,问题的原因正如我分析的那样。一个需要释放的类(资源)是不太适宜在可能会被Abort的线程中创建并释放的。因为你不太可能完全保证资源占用的时候类赋值之前不会触发ThreadAbortException的。 .结尾
在项目开发中,Thread类就如同一把双刃剑,功能强大得不得了,但是给代码理解与调试带来了一定程度上的困难。如果非要问我在多线程开发上有什么建议的话,我想说,除非你已经在千锤百炼的开发经验中完全掌握了多线程,否则能不用它就不要用它吧!
C#中的线程之Abort陷阱的更多相关文章
- python中的线程之semaphore信号量
semaphore是一个内置的计数器 每当调用acquire()时,内置计数器-1 每当调用release()时,内置计数器+1 计数器不能小于0,当计数器为0时,acquire()将阻塞线程直到其他 ...
- 多线程之pthread, NSThread, NSOperation, GCD
关于多线程会有一系列如下:多线程之概念解析 多线程之pthread, NSThread, NSOperation, GCD 多线程之NSThread 多线程之NSOperation 多线程之GCD p ...
- iOS多线程之8.NSOPeration的其他用法
本文主要对NSOPeration的一些重点属性和方法做出介绍,以便大家可以更好的使用NSOPeration. 1.添加依赖 - (void)addDependency:(NSOperation * ...
- python 线程之 threading(四)
python 线程之 threading(三) http://www.cnblogs.com/someoneHan/p/6213100.html中对Event做了简单的介绍. 但是如果线程打算一遍一遍 ...
- python 线程之_thread
python 线程之_thread _thread module: 基本用法: def child(tid): print("hello from child",tid) _thr ...
- Java多线程之ConcurrentSkipListMap深入分析(转)
Java多线程之ConcurrentSkipListMap深入分析 一.前言 concurrentHashMap与ConcurrentSkipListMap性能测试 在4线程1.6万数据的条件下, ...
- 【C#】线程之Parallel
在一些常见的编程情形中,使用任务也许能提升性能.为了简化变成,静态类System.Threading.Tasks.Parallel封装了这些常见的情形,它内部使用Task对象. Parallel.Fo ...
- iOS多线程之GCD小记
iOS多线程之GCD小记 iOS多线程方案简介 从各种资料中了解到,iOS中目前有4套多线程的方案,分别是下列4中: 1.Pthreads 这是一套可以在很多操作系统上通用的多线程API,是基于C语言 ...
- 多线程之RunLoop
*:first-child { margin-top: 0 !important; } body > *:last-child { margin-bottom: 0 !important; } ...
随机推荐
- ADO.NET 七(一个例子)
通过一个完整的实例实现课程信息管理功能的操作,包括查询.修改.删除课程信息等操作. 1) 创建课程信息表 create table StuCourse ( id int primary key ide ...
- JavaScript之变量
var a; // 声明变量a,变量:值可以改变的,相当于数学x,y,z... a=10; // 将10赋值给a var test; var Test; /* 变量命名规则: 1.不能以数字开头 2. ...
- 谈一谈做iOS播放器库开发所涉及的知识点
在自己研究生毕业的时候,想着能找上一份做视频编解码的工作,可惜没有如愿,最后到了一家iOS游戏渠道公司去做游戏支付业务的SDK开发,我的iOS正式开发生涯就这么开始了. 在那家iOS游戏渠道没做上一年 ...
- yum源仓库的三种搭建方式
yum源的三种搭建方式 一. 本地yum仓库的搭建 1.1.获取软件包资源 将iso镜像挂载在本地目录中,此次挂载目录为/var/www/html/repo/,此目录本身不存在,需要创建.软件宝资源 ...
- CentOS7清理老旧内核
CentOS7如果是一步步内核升级上来的将会面临一个问题(一般安装了图形化界面的系统常见),由于内核增多/boot/的容量减少,导致启动变慢.这时就需要清理老旧内核,释放/boot/空间. 使用una ...
- 李航-统计学习方法-笔记-3:KNN
KNN算法 基本模型:给定一个训练数据集,对新的输入实例,在训练数据集中找到与该实例最邻近的k个实例.这k个实例的多数属于某个类,就把输入实例分为这个类. KNN没有显式的学习过程. KNN使用的模型 ...
- linux系统编程之信号(五)
今天继续对信号进行学习,开始正入正题: sigaction函数: 安装信号之前我们已经学过一个函数:signal,它最早是在unix上出现的,它是对不可靠信号进行安装的,之后出现了可靠信号和实时信号, ...
- Office2016专业增强版永久激活
Office2016专业增强版永久激活码:Microsoft Office 2016 Pro Plus Retail Mak序列号XNTT9-CWMM3-RM2YM-D7KB2-JB6DVBHXN7- ...
- python字符转数字
print(float("260.01478420521632365364543423")) 260.0147842052163
- python 赋值与深浅拷贝
https://www.cnblogs.com/Eva-J/p/5534037.html