一:Threading 线程描述
在 C# 语言中线程(Thread)是包含在进程中的,它位于 System.Threading 命名空间中。
与线程有关的类同样也都在 System.Threading 命名空间中,主要的类如下表所示。
类名 | 说明 |
---|---|
Thread | 在初始的应用程序中创建其他的线程 |
ThreadState | 指定 Thread 的执行状态,包括开始、运行、挂起等 |
ThreadPriority | 线程在调度时的优先级枚举值,包括 Highest、AboveNormal、Normal、BelowNormal、Lowest |
ThreadPool | 提供一个线程池,用于执行任务、发送工作项、处理异步I/O等操作 |
Monitor | 提供同步访问对象的机制 |
Mutex | 用于线程间同步的操作 |
ThreadAbortException | 调用Thread类中的Abort方法时出现的异常 |
ThreadStateException | Thead处于对方法调用无效的ThreadState时出现的异常 |
Thread 类主要用于实现线程的创建以及执行,其常用的属性和方法如下表所示。
属性或方法 | 说明 |
---|---|
Name | 属性,获取或设置线程的名称 |
Priority | 属性,获取或设置线程的优先级 |
ThreadState | 属性,获取线程当前的状态 |
IsAlive | 属性,获取当前线程是否处于启动状态 |
IsBackground | 属性,获取或设置值,表示该线程是否为后台线程 |
CurrentThread | 属性,获取当前正在运行的线程 |
Start() | 方法,启动线程 |
Sleep(int millisecondsTimout) | 方法,将当前线程暂停指定的毫秒数 |
Suspend() | 方法,挂起当前线程(已经被弃用) |
Join() | 方法,阻塞调用线程,直到某个线程终止为止 |
Interrupt() | 方法,中断当前线程 |
Resume() | 方法,继续已经挂起的线程(已经被弃用) |
Abort() | 方法,终止线程 |
二:线程的创建和使用
static void Main(string[] args)
{
//创建线程,并指定线程开始执行时要调用的方法。也可以指定一个委托或者Lambda表达式
Thread thread = new Thread(Function);
//指定线程为后台线程,如果不指定为前台线程。
thread.IsBackground = true;
//指定线程优先级别
/*
* 线程在C#中有5个优先级:(从低到高)Lowest -> BelowNormal -> Normal ->AboveNormal -> Highest
* 默认状态下是:Normal。在单核CPU中效果明显,多核CPU中效果不太明显,如果希望线程尽快执行,可以把
* 优先级设定高一点。
*/
thread.Priority = ThreadPriority.AboveNormal;
//启动线程。(实际操作中,并不是立即启动,而是告诉CPU,这个线程准备好了,具体什么时候启动有CPU统一调度)
thread.Start();
Console.ReadKey();
}
private static void Function()
{
while(true)
{
Console.WriteLine(DateTime.Now.ToString());
Thread.Sleep(1000);
}
}
运行结果:
三:线程的启动执行
警惕:线程不会立即执行
现在的大多数系统不是一个实时的操作系统,Window也是如此。所以不要奢望线程能够立即启动。Window内部会实现特殊的算法进行线程之间的调度。在某个时刻它会决定当前运行那个线程。这反映到最底层就是某个线程分配到一定的CPU时间,可以用来执行一小段工作(由于被分配的CPU时间很短,所以操作系统中运行者上千个线程,我们也会觉得这些应用程序也是在同时执行的)。Window会选择适当的时间根据自己的算法决定下一段的CPU时间如何调度。
**实例:**将 0 - 9 分别传入10个不同的线程,结果却事与愿违。
static int id = 0;
static void Main(string[] args)
{
for (int i = 0; i < 10; i++,id++)
{
Thread thread = new Thread(() =>
{
Console.WriteLine(string.Format("{0}:{1}", Thread.CurrentThread.Name, id));
});
thread.Name = string.Format("Thread{0}", i);
thread.IsBackground = true;
thread.Start();
}
Console.ReadLine();
}
怎样改变这种状况,使用同步传参方法:
static int id = 0;
static void Main(string[] args)
{
for (int i = 0; i < 10; i++,id++)
{
//同步传参
NewMethod(i, id);
}
Console.ReadLine();
}
private static void NewMethod(int i,int realTimeId)
{
Thread thread = new Thread(() =>
{
Console.WriteLine(string.Format("{0}:{1}", Thread.CurrentThread.Name, realTimeId));
});
thread.Name = string.Format("Thread{0}", i);
thread.IsBackground = true;
thread.Start();
}
四:线程的停止
线程终止涉及两个问题:
- 正如线程不能立即启动一样,线程不是说停就停。无论采用什么样的方式通知线程停止,线程都会执行完毕后,在合适的情况下停止。 thread.Abort()方法,如果线程正在执行的是一段非托管代码,那么,CLR 就不会抛出 ThreadAbortException,只有当代码继续回到 CLR 时,才会引发ThreadAbortException。
- 要正确停止线程,不在于调用者采用了什么行为,而是更多依赖工作线程是否主动响应调用者的停止请求,大体的机制是,如果线程需要被停止,那么线程本身就应该负责给调用者开放这样的接口,Canclen。线程在工作的同时,还要以某种频率检查Canclen标识。若检测到Canclen,线程才会停止,对于 ThreadPool(线程池)同样。
CancellationTokenSource: 线程中协作式取消关键类型。
它有一个关键属性 Token ,是一个名为 CancellationToken 的值类型。CancellationToken继而提进一步提供了布尔值类型的属性 IsCancellationRequested 作为需要取消工作的标识。CancellationToken 还有一个方法值得注意,Register方法,它负责传递一个 Action 委托,在线程停止的时候被调用。
实例Dome
public partial class MainFrom : Form
{
public MainFrom()
{
InitializeComponent();
}
private Thread thread;
/// <summary>
/// 终止线程标识。构造函数中可以给一个时间段或毫秒数时间跨度。
/// </summary>
CancellationTokenSource tokenSource = new CancellationTokenSource();
private void ButStart_Click(object sender, EventArgs e)
{
thread = new Thread(() => {
while(true)
{
if (tokenSource.IsCancellationRequested)//检测信号
{
break;
}
string str = DateTime.Now.ToString();
//操作UI控件,因为不是同一个线程创建的对象,要用委托方法。
this.Invoke(new Action(() => {
ListMsg.Items.Add(str);
}));
Thread.Sleep(1000);
}
});
thread.IsBackground = true;
thread.Start();
}
/// <summary>
/// 终止线程,销毁线程不能再重新启动
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void ButStop_Click(object sender, EventArgs e)
{
/*
* 线程终止涉及两个问题:
* 一: 正如线程不能立即启动一样,线程不是说停就停。无论采用什么样的方式通知线程停止,线程都会执行
* 完毕后,在合适的情况下停止。 thread.Abort()方法,如果线程正在执行的是一段非托管代码,
* 那么,CLR 就不会抛出 ThreadAbortException,只有当代码继续回到 CLR 时,才会引发
* ThreadAbortException。
* 二: 要正确停止线程,不在于调用者采用了什么行为,而是更多依赖工作线程是否主动响应调用者的停止请
* 求,大体的机制是,如果线程需要被停止,那么线程本身就应该负责给调用者开放这样的接口,
* Canclen。线程在工作的同时,还要以某种频率检查Canclen标识。若检测到Canclen,线程才会推出
* 对于 ThreadPool同样。
*/
thread.Abort();
}
/// <summary>
/// 安全终止线程
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void ButSafelyStop_Click(object sender, EventArgs e)
{
// 注册一个将在取消此 System.Threading.CancellationToken 时调用的委托。
tokenSource.Token.Register(() => {
txMsg.Text = "线程被终止!";
});
//传达取消命令
tokenSource.Cancel();
}
#region 弃用的方法
[Obsolete]
private void ButSleep_Click(object sender, EventArgs e)
{
//如果线程被销毁,则引发异常
thread.Suspend();
txMsg.Text = "线程暂停";
}
[Obsolete]
private void ButGoOn_Click(object sender, EventArgs e)
{
thread.Resume();
txMsg.Text = "线程继续";
}
#endregion
/// <summary>
/// 重启线程
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void ButRestart_Click(object sender, EventArgs e)
{
try
{
thread.Start();
}
catch(Exception ex)
{
txMsg.Text ="线程启动异常:"+ex.Message;
}
}
}
点击开始:会不停的输出当前时间。
线程被停止后就不能被重启,会抛出异常。
使用被弃用的方法:Suspend() (暂停); Resume() (继续);则可以。(注意,如果线程被销毁后调用这两个方法则有异常)
五:唤醒休眠的线程
Interrupt 用于提前唤醒一个在Sleep的线程,Sleep方法会抛出ThreadInterruptedException异常
必须抓住这个异常,然后做唤醒后的线程处理。
Sleep 是静态方法,只能是自己主动要求休眠,别人不能命令他休眠。
例:
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private Thread t1;
private void button1_Click(object sender, EventArgs e)
{
t1 = new Thread(() =>
{
while (true)
{
try
{
MessageBox.Show("hello word");
Thread.Sleep(3000);
}
catch (ThreadInterruptedException) //必须抓住异常,否则程序结束
{
MessageBox.Show("谁叫我!!!");
}
}
});
t1.IsBackground = true;
t1.Start(); //开始
}
private void button2_Click(object sender, EventArgs e)
{
t1.Interrupt(); //唤醒t1线程
}
}