c# 热身:线程 (I) 下一页
Abort 方法:
其实 Abort 方法并没有像字面上的那么简单,释放并终止调用线程,其实当一个线程调用 Abort方法时,会在调用此方法的线程上引发一个异常:
ThreadAbortException ,让我们一步步深入下对这个方法的理解:
1 首先我们尝试对主线程终止释放
static void Main(string[] args)
{
try
{
Thread.CurrentThread.Abort();
}
catch
{
//Thread.ResetAbort();
Console.WriteLine("主线程接受到被释放销毁的信号");
Console.WriteLine( "主线程的状态:{0}",Thread.CurrentThread.ThreadState);
}
finally
{
Console.WriteLine("主线程最终被被释放销毁");
Console.WriteLine("主线程的状态:{0}", Thread.CurrentThread.ThreadState);
Console.ReadKey();
}
}
从运行结果上看很容易看出当主线程被终止时其实报出了一个ThreadAbortException, 从中我们可以进行捕获,但是注意的是,主线程直到finally语
句块执行完毕之后才真正结束(可以仔细看下主线程的状态一直处于AbortRequest),如果你在finally语句块中执行很复杂的逻辑或者计算的话,那
么只有等待直到运行完毕才真正销毁主线程(也就是说主线程的状态会变成Aborted,但是由于是主线程所以无法看出).
2 尝试终止一个子线程
同样先看下代码:
static void TestAbort()
{
try
{
Thread.Sleep(10000);
}
catch
{
Console.WriteLine("线程{0}接受到被释放销毁的信号",Thread.CurrentThread.Name);
Console.WriteLine("捕获到异常时线程{0}主线程的状态:{1}", Thread.CurrentThread.Name,Thread.CurrentThread.ThreadState);
}
finally
{
Console.WriteLine("进入finally语句块后线程{0}主线程的状态:{1}", Thread.CurrentThread.Name, Thread.CurrentThread.ThreadState);
}
}
Main:
static void Main(string[] args)
{
Thread thread1 = new Thread(TestAbort);
thread1.Name = "Thread1";
thread1.Start();
Thread.Sleep(1000);
thread1.Abort();
thread1.Join();
Console.WriteLine("finally语句块后,线程{0}主线程的状态:{1}", thread1.Name, thread1.ThreadState);
Console.ReadKey();
}
了解了主线程的销毁释放后,再来看下子线程的销毁释放的过程(Start->abortRequested->Aborted->Stop),从最后输出的状态变化来看,
子线程thread1 的状态变化是十分清楚的,几乎和主线程的例子一致,唯一的区别是我们在 main方法中故意让主线程阻塞这样能看见thread 1
在 finally语句块后的状态
3,尝试对尚未启动的线程调用Abort
如果对一个尚未启动的线程调用Abort的话,一旦该线程启动就被停止了
4 尝试对一个挂起的线程调用Abort
如果在已挂起的线程上调用 Abort,则将在调用 Abort 的线程中引发 ThreadStateException,并将 AbortRequested 添加到被中止的线程的
ThreadState 属性中。直到调用 Resume 后,才在挂起的线程中引发 ThreadAbortException。如果在正在执行非托管代码的托管线程上调用 Abort,
则直到线程返回到托管代码才引发 ThreadAbortException。
Interrupt 方法:
Interrupt 方法将当前的调用该方法的线程处于挂起状态,同样在调用此方法的线程上引发一个异常:ThreadInterruptedException,和Abort方法不同的是,
被挂起的线程可以唤醒
static void Main(string[] args)
{
Thread thread1 = new Thread(TestInterrupt);
thread1.Name = "Thread1";
thread1.Start();
Thread.Sleep(1000);
thread1.Interrupt();
thread1.Join();
Console.WriteLine("finally语句块后,线程{0}主线程的状态:{1}", thread1.Name, thread1.ThreadState);
Console.ReadKey();
}
static void TestInterrupt()
{
try
{
Thread.Sleep(3000);
}
catch (ThreadInterruptedException e)
{
Console.WriteLine("线程{0}接受到被Interrupt的信号", Thread.CurrentThread.Name);
Console.WriteLine("捕获到Interrupt异常时线程{0}的状态:{1}", Thread.CurrentThread.Name, Thread.CurrentThread.ThreadState);
}
finally
{
Console.WriteLine("进入finally语句块后线程{0}的状态:{1}", Thread.CurrentThread.Name, Thread.CurrentThread.ThreadState);
}
}
从代码中可以看出,当线程调用Interrupted后,它的状态是已中断的.这个状态对于正在执行join,sleep的线程,却改变了线程的运行结果
.因为它正在某一对象的休息室中,这时如果它的中断状态被改变,那么它就会抛出ThreadInterruptedException异常,意思就是这个线程不能再等待了,其意义就等同于唤醒它了。
让我们想象一下我们将一个线程设置了其长达1星期的睡眠时间,有时后必须唤醒它,上述方法就能实现这点
8 细说下Thread 的 Suspend,Resume方法
Suspend 和Resume方法很奥妙,前者将当前运行的线程挂起,后者能够恢复当钱被挂起的线程
Thread thread1 = new Thread(TestSuspend);
Thread thread2 = new Thread(TestSuspend);
thread1.Name = "Thread1";
thread2.Name = "Thread2";
thread1.Start();
thread2.Start();
//假设在做一些事情
Thread.Sleep(1000);
Console.WriteLine("需要主线程帮忙了");
// throw new NullReferenceException("error!");
thread1.Resume();
thread2.Resume();
static void TestSuspend()
{
Console.WriteLine("Thread:{0} has been suspend!",Thread.CurrentThread.Name);
//这里讲当前线程挂起
Thread.CurrentThread.Suspend();
Console.WriteLine("{0} has been resume", Thread.CurrentThread.Name);
}
如上代码,我们制造两个线程来实现Suspend和Resume的测试,(暂时不考虑临界区共享同步的问题),TestSuspend方法便是两个线程的共用方法,
方法中我们获取当前运行该方法的线程,然后将其挂起操作,那么假设线程1先挂起了,线程1被中止当前的工作,面壁思过去了,可是这并不影响线程
2的工作,于是线程2也急匆匆的闯了进来,结果和线程1一样的悲剧,聪明的你肯定会问,谁能让线程1和线程2恢复工作?其实有很多方法能让他们恢
复工作,但是个人认为,在不创建新线程的条件下,被我们忽视的主线程做不住了,看到自己的兄弟面壁,心里肯定不好受,于是做完他自己的一系列
事情之后,他便去召唤这2个兄弟回来工作了,可是也许会有这种情况,主线程迫于自己的事情太多太杂而甚至报出了异常, 那么完蛋了,这两个线程永
远无法继续干活了,或者直接被回收。。。
这样这次把他们共享区上锁,上面部分的代码保持不变,这样会发生什么情况呢?
static void TestSuspend()
{
lock (lockObj)
{
。。。。
}
}
(由于在TestSuspend方法中加入了锁,所以每次只允许一个线程工作,大伙不必在本文中深究锁机制,后续章节会给大家详细温故下)
尽然在thread2.resume()方法上报错了,仔细分析后发现在thread1离开共享区(testSuspend)方法之后刹那间,thread2进来了,与此同时,主线程
跑的太快了,导致thread2被挂起前去唤醒thread2,悲剧就这么发生了,其实修改这个bug很容易,只要判断下线程的状态,或者主线程中加一个Thread.Sleep()等等,
但是这种错误非常的严重,往往在很复杂的业务里让你发狂,所以微软决定放弃这两个方法,将他们归为过时方法,最后让大家看下微软那个深奥的解释,
相信看完上述例子后大家都能理解这个含义了
9 简单了解下Thread 的 一些常用的重要属性
1 CurrentThread
获取到当前线程的对象
2 IsAlive
判断线程是否处于激活状态
3 IsBackground
设置该线程是否是后台线程,一旦设置true 的话,该线程就被标示为后台线程
再次强调下后台线程的终止不会导致进程的终止
4 IsThreadPoolThread
只读属性标示该线程是否属于线程池的托管线程,一般我通过线程池创建的线程该属性都是true
5 Name
获取到线程的名字,我们可以根据业务或者逻辑来自定义线程的名字
6 Priority
这个属性表示线程的优先级,我们可以用ThreadPriority这个枚举来设置这个属性
ThreadPriority包含有5个优先级大家了解下就行
10 Thread的简单示例
在WPF中实现多线程从一个图片中截取部分图片
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Drawing;
using System.Windows.Interop;
using System.Threading;
namespace ImageFlip
{
/// <summary>
/// WPF 多线程将图片分割
/// </summary>
public partial class MainWindow : Window
{
BitmapSource source;
private object lockObj = new object();
public MainWindow()
{
InitializeComponent();
//首先获取图片
Bitmap orginalImage = new Bitmap(@"G:\Picture\Tamriel_4E.png");
//创建线程1
Thread t1 = new Thread(new ParameterizedThreadStart
(
obj =>
{
//WPF中使用多线程的话最后一定要返回UI线程,否则操作界面控件时会报错
//BeginInvoke方法便是返回UI线程的方法
this.Dispatcher.BeginInvoke((Action)(() =>
{
//通过Parameter类的属性裁剪图片
ClipImageAndBind(obj);
//图片的部分绑定到页面控件
this.TestImage1.Source = source;
}));
}
));
//创建线程2
Thread t2 = new Thread(new ParameterizedThreadStart
(
obj =>
{
//WPF中使用多线程的话最后一定要返回UI线程,否则操作界面控件时会报错
//BeginInvoke方法便是返回UI线程的方法
this.Dispatcher.BeginInvoke((Action)(() =>
{
//通过Parameter类的属性裁剪图片
ClipImageAndBind(obj);
//图片的部分绑定到页面控件
this.TestImage2.Source = source;
//尝试将线程1的启动逻辑放在线程2所持有的方法中
// t1.Start(new Parameter { OrginalImage = orginalImage, ClipHeight = 500, ClipWidth = 500, StartX = 0, StartY = 0 });
}));
}
));
t2.Start(new Parameter { OrginalImage = orginalImage, ClipHeight = 500, ClipWidth = 500, StartX = orginalImage.Width - 500, StartY = orginalImage.Height - 500 });
//尝试下注释掉t2.join方法后是什么情况,其实注释掉之后,两个线程会一起工作,
//去掉注释后,界面一直到两个图片部分都绑定完成后才出现
//t2.Join();
t1.Start(new Parameter { OrginalImage = orginalImage, ClipHeight = 500, ClipWidth = 500, StartX = 0, StartY = 0 });
}
/// <summary>
/// 根据参数类进行剪裁图片,加锁防止共享资源被破坏
/// </summary>
/// <param name="para">Parameter类对象</param>
private void ClipImageAndBind(object para)
{
lock (lockObj)
{
Parameter paraObject = (para as Parameter);
source = this.ClipPartOfImage(paraObject);
Thread.Sleep(5000);
}
}
/// <summary>
/// 具体裁剪图片,大家不必在意这个方法,关键是线程的使用
/// </summary>
/// <param name="para">Parameter</param>
/// <returns>部分图片</returns>
private BitmapSource ClipPartOfImage(Parameter para)
{
if (para == null) { throw new NullReferenceException("para 不能为空"); }
if (para.OrginalImage == null) { throw new NullReferenceException("OrginalImage 不能为空"); }
System.Drawing.Rectangle rect = new System.Drawing.Rectangle(para.StartX, para.StartY, para.ClipWidth, para.ClipHeight);
var bitmap2 = para.OrginalImage.Clone(rect, para.OrginalImage.PixelFormat) as Bitmap;
return ChangeBitmapToBitmapSource(bitmap2);
}
private BitmapSource ChangeBitmapToBitmapSource(Bitmap bmp)
{
BitmapSource returnSource;
try
{
returnSource = Imaging.CreateBitmapSourceFromHBitmap(bmp.GetHbitmap(), IntPtr.Zero, Int32Rect.Empty, BitmapSizeOptions.FromEmptyOptions());
}
catch
{
returnSource = null;
}
return returnSource;
}
}
/// <summary>
/// 参数类
/// </summary>
public class Parameter
{
public Bitmap OrginalImage { get; set; }
public int StartX { get; set; }
public int StartY { get; set; }
public int ClipWidth { get; set; }
public int ClipHeight { get; set; }
}
}
前台
<Window x:Class="ImageFlip.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition></ColumnDefinition>
<ColumnDefinition></ColumnDefinition>
</Grid.ColumnDefinitions>
<Image x:Name="TestImage1" Grid.Column="0"></Image>
<Image x:Name="TestImage2" Grid.Column="1"></Image>
</Grid>
</Window>
11 本章总结
本章介绍了线程一些简单的基础知识和对Thread类进行了详细的介绍,在以后的章节中我会逐步向大家介绍线程同步,异步线程等等有关线程的知识
上一篇: 医美融合,永和医疗打造 "好医生-一人一案 "个性化服务。
下一篇: 从零开始(向过去学习)