多线程详解
1.使用线程的情况
①.程序需要执行和两个和多个任务
②.程序要等待某事件的发生:例如用户输入、文件操作、网络操作和搜索
③.后台程序
2.多线程的并发执行
如果有多个线程在执行,单CPU只有一个,到底执行的哪个?
①.如果一个线程连续占用CPU资源时间过长,其它的资源得不到执行,
则系统会强制的切换执行其它线程。(强制剥夺)
②.如果一个线程没事可做、CPU可执行其它线程。(主动放弃)
③.这是由操作系统的调度机制决定的,不同的操作系统调度机制不一样。
一般无法精确的预料多线程的执行顺序,在程序设计的时候应特别注意
3.创建并启动线程
ThreadStart 线程启动委托名=new ThreadStart(方法名);
Thread 线程实例名=new Thread(线程启动委托名);
线程实例名.Start();
4.终止线程
①.线程实例名.Abort();用此方法的后果是不可恢复的终止线程。
②.线程实例名.Interrupt();中断后可恢复
5.休眠线程
①.线程实例名.Sleep();
当线程Sleep时,系统就立即退出执行队列一段时间,当睡眠结束时,系统会产生一个时钟中断,从而
使线程回到执行队列中,从而恢复线程的执行。
6.挂起/恢复线程
①.线程实例名.Suspend();挂起
与线程休眠不同,线程的挂起不会使线程立即停止执行,直到线程到达安全点之后它才可以将该
线程挂起,如果线程尚未启动或已经停止,则它将不能挂起。
②.线程实例名.Resume();恢复
将使一个线程跳出挂起状态并使该线程继续执行。
一个线程不能对另一个线程调用Sleep() ,但是一个线程可以对另一个线程调用Suspend()。
还可以使用许多其它的方式来阻塞线程。例如,可以通过调用 Thread.Join 使一个线程等待另一个线程 (子线程)停止。使用Monitor.Wait使一个线程等待访问一个同步对象。
7.串行化线程
①.线程实例名.jion();
例如在主线程中插入t.jion();
主线程执行到这条语句后,主线程(当前线程)立即进入阻塞状态.直到t运行完后阻塞状态才解除。
相当于把t的任务插入或串联到主线程中,把两条线索串联成一条线索
8.线程的锁定机制
线程的锁定机制可以保证每次只有一个线程可以访问共享资源。
使用关键字lock
①.lock语句的语法
lock(对象引用)语句块;
②.lock语句的功能
当对象被lock 锁定时,访问该线程的其它线程会进入等待的状态。
③.对象锁机制保证了对象访问的完整性:只有一个线程完成操作后,其它的线程才能进行操作。
④.一般情况下,当一个线程写某个变量,而同时可能有其它的线程读或写这个变量时,为了保持数据的一
致性应该使用锁定机制。
⑤.线程的安全性
线程安全性就是保护的类的成员和代码的安全,从而使他们不会同时被几个线程中断,使用锁定机制。
⑥.多线程公用一个对象时,就不应该使用lock关键字了,这里Monitor,Monitor提供了使线程共享资源的方 案。
Monitor类可以锁定一个对象,一个线程只有得到这把锁才可以对该对象进行操作。
如:
Monitor.Enter(obj);
Monitor.Exit(obj);
⑦.临界区和锁
当谈论多线程应用程序的时候,首先应该想到的就是并发性问题。尽管这对于同时执行多个任务的程序是很有用的,但通常都是危险的。为了解决这个问题,在C#中提出了临界区和锁的概念。在程序设计中,临界区是一块在任何时候只能有一个进程进入的区域。在C#中通过语句lock来声明临界区。lock声明后面的代码,不管是以行还是一块代码,在同一时间最多只能有一个进程执行。
9.线程的优先级具有不可靠性,就是说不能用优先级来控制线程的执行顺序。
10.后台线程
①.什么是后台线程?比起应用程序的主图形用户界面(GUI)线程来说,这些线程以较低的优先权在不同的过程中
运行着。对于不能立即执行结束, 又不想一直等待的任务,后台线程能很好的胜任。在C#中,把线程对象的
IsBackground属性设为true,该线程即为后台线程。
后台线程跟前台线程只有一个区别,那就是后台线程不妨碍程序的终止。一旦一个进程所有的前台线程都终止后,
CLR将通过调用任意一个存活中的后台进程的Abort()方法来彻底终止进程。
注意:后台线程不能直接操作所在进程之外的数据引用。
②.怎样与后台线程通讯?运用MethodInvoker委派实体。
要使用MethodInvoker委派,需要三个条件:
a.一个创建委派的后台线程
Thread thread=new Thread(new ThreadStart(Run));
thread.IsBackground=true;//把Thread设为后台线程
thread.Start();
b.一个用作后台线程与前台可视化单元的接口的类级方法
public void Run()
{
int count=0;
try
{
MethodInvoker mi=new MethodInvoker(this.UpdateLabel);
//创建一个委托,UpdateLabel是该委托所托管的代码,必须是声明为void 且不接受任何参数的任何方法。
while(true)
{
count++;
//this.Invoke(mi);//同步执行委托
this.BeginInvoke(mi);//异步执行委托
Thread.Sleep(500);
}
}
catch(ThreadInterruptedException e)
{
Console.WriteLine("Interruption Exception in Thread:{0}",e);
}
catch(Exception ex)
{
Console.WriteLine("Exception in Thread:{0}",ex);
}
}
c.一个应用程序中可以更新的可视化单元
public void UpdateLabel()
{
label1.Text=count.ToString();
}
例1:使用多线程实现的打字练习(VS2005)
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.Threading;
/*
* 编写一个基于Windows窗体的应用程序,实现打字练习功能,要使用多线程,在主线程里用
* Timer控件定时产生Label控件,每个Label控件显示一个随机产生的字母,每产生一个Label
* 就新开一个线程(辅助线程),这个线程用于控制Label控件的向下移动,主线程监视键盘
* 输入,如果键盘输入文本与这个Label控件的Text相同,Label就消失
*/
namespace TypewriteExcercise
{
public partial class frmTyped : Form
{
public frmTyped()
{
InitializeComponent();
}
private void frmTyped_Load(object sender, EventArgs e)
{
timer1.Start();//启动时钟
}
private void timer1_Tick(object sender, EventArgs e)
{
Label label = new Label();//创建标签对象
label.Width = 12;
label.Height = 12;
label.ForeColor = Color.White;//把标签的前景色设为白色
label.BackColor = this.BackColor;//把窗体的背景色设置黑色为标签的背景色
this.Controls.Add(label);//把标签添加到窗体中
System.Random random = new Random(DateTime.Now.Second * DateTime.Now.Second);
//当前系统时间的秒数的平方作为随机种子
label.Left = random.Next(this.Width);//标签随机数[0,窗体的宽度),
Letter letter = new Letter(label, this);//把标签和窗体传递给letter对象
ThreadStart threadstart = new ThreadStart(letter.Run);//创建线程启动委托,注意括号里面是方法名
Thread thread = new Thread(threadstart);//创建线程实例
thread.Start();//启动线程
}
private void frmTyped_KeyPress(object sender, KeyPressEventArgs e)
{
foreach(Label label in this.Controls)
{
if(label==null)//没有标签
{
break;//退出
}
if(label.Text[0]==e.KeyChar)
//label.Text返回的是字符串,label.Text[0]返回第一个字符。e.KeyChar返回的是字符。这样都为字符才能判断
{
label.Dispose();//释放由label所使用的所有资源
this.Controls.Remove(label);//从frmTyped中移除label
}
}
}
}
public class Letter
{
private Label _label;
private Form _container;
private int _speed = 2;
public Letter(Label label, Form container) //构造函数接受标签和窗体
{
_label = label;//初始化标签
_container = container;//初始化窗体
Random random = new Random(DateTime.Now.Second);//使用当前系统时间的秒数作为随机种子
_speed = random.Next(5) + 1;//产生一个[1,6)的随机数用来表示速度
_label.Text = Convert.ToChar(65 + random.Next(57)).ToString();
/*
* a-z的ASCII码97-122 A-Z 的ASCII码65-90 [:91 \:92 ]:93 ^:94 _:95 `:96
*/
switch(_speed)
{
case 1:
_label.ForeColor = Color.Red;//将标签的前景色设为红色
break;
case 2:
_label.ForeColor = Color.Yellow;
break;
case 3:
_label.ForeColor = Color.Blue;
break;
case 4:
_label.ForeColor = Color.Green;
break;
case 5:
_label.ForeColor = Color.White;
break;
default:
_label.ForeColor = Color.White;
break;
}
}
public void Run()
{
try
{
while (_label.Top <= this._container.Height + 100)//标签的的上边距小于或等于窗体的高度+100像素
{
if (_label == null)//没有产生标签这种情况
{
Thread.CurrentThread.Abort();//就终止当前的线程
}
_label.Top += 1;//有标签,上边距就加1像素;
Thread.Sleep(_speed * 5);//让线程休眠(速度越快,休眠的时间越短。以毫秒为单位);
}
if(Thread.CurrentThread.IsAlive)//如果当前线程还是存活的
{
Thread.CurrentThread.Abort();//就终止当前的线程
}
}
catch (Exception ex)
{
Console.WriteLine("错误:" + ex.Message);//获取描述当前异常的消息
Console.WriteLine("错误:" + ex.StackTrace);//获取当前异常发生时调用堆栈上的帧的字符串表达形式
}
finally //释放资源,不管是否发生异常,finally都要五条件的执行
{
if(!_label.Disposing)//如果标签没有释放到进程中
{
_label.Dispose();//释放由标签使用的所有资源
}
_container.Controls.Remove(_label);//移除在窗体中产生的所有标签
}
}
}
}
①.程序需要执行和两个和多个任务
②.程序要等待某事件的发生:例如用户输入、文件操作、网络操作和搜索
③.后台程序
2.多线程的并发执行
如果有多个线程在执行,单CPU只有一个,到底执行的哪个?
①.如果一个线程连续占用CPU资源时间过长,其它的资源得不到执行,
则系统会强制的切换执行其它线程。(强制剥夺)
②.如果一个线程没事可做、CPU可执行其它线程。(主动放弃)
③.这是由操作系统的调度机制决定的,不同的操作系统调度机制不一样。
一般无法精确的预料多线程的执行顺序,在程序设计的时候应特别注意
3.创建并启动线程
ThreadStart 线程启动委托名=new ThreadStart(方法名);
Thread 线程实例名=new Thread(线程启动委托名);
线程实例名.Start();
4.终止线程
①.线程实例名.Abort();用此方法的后果是不可恢复的终止线程。
②.线程实例名.Interrupt();中断后可恢复
5.休眠线程
①.线程实例名.Sleep();
当线程Sleep时,系统就立即退出执行队列一段时间,当睡眠结束时,系统会产生一个时钟中断,从而
使线程回到执行队列中,从而恢复线程的执行。
6.挂起/恢复线程
①.线程实例名.Suspend();挂起
与线程休眠不同,线程的挂起不会使线程立即停止执行,直到线程到达安全点之后它才可以将该
线程挂起,如果线程尚未启动或已经停止,则它将不能挂起。
②.线程实例名.Resume();恢复
将使一个线程跳出挂起状态并使该线程继续执行。
一个线程不能对另一个线程调用Sleep() ,但是一个线程可以对另一个线程调用Suspend()。
还可以使用许多其它的方式来阻塞线程。例如,可以通过调用 Thread.Join 使一个线程等待另一个线程 (子线程)停止。使用Monitor.Wait使一个线程等待访问一个同步对象。
7.串行化线程
①.线程实例名.jion();
例如在主线程中插入t.jion();
主线程执行到这条语句后,主线程(当前线程)立即进入阻塞状态.直到t运行完后阻塞状态才解除。
相当于把t的任务插入或串联到主线程中,把两条线索串联成一条线索
8.线程的锁定机制
线程的锁定机制可以保证每次只有一个线程可以访问共享资源。
使用关键字lock
①.lock语句的语法
lock(对象引用)语句块;
②.lock语句的功能
当对象被lock 锁定时,访问该线程的其它线程会进入等待的状态。
③.对象锁机制保证了对象访问的完整性:只有一个线程完成操作后,其它的线程才能进行操作。
④.一般情况下,当一个线程写某个变量,而同时可能有其它的线程读或写这个变量时,为了保持数据的一
致性应该使用锁定机制。
⑤.线程的安全性
线程安全性就是保护的类的成员和代码的安全,从而使他们不会同时被几个线程中断,使用锁定机制。
⑥.多线程公用一个对象时,就不应该使用lock关键字了,这里Monitor,Monitor提供了使线程共享资源的方 案。
Monitor类可以锁定一个对象,一个线程只有得到这把锁才可以对该对象进行操作。
如:
Monitor.Enter(obj);
Monitor.Exit(obj);
⑦.临界区和锁
当谈论多线程应用程序的时候,首先应该想到的就是并发性问题。尽管这对于同时执行多个任务的程序是很有用的,但通常都是危险的。为了解决这个问题,在C#中提出了临界区和锁的概念。在程序设计中,临界区是一块在任何时候只能有一个进程进入的区域。在C#中通过语句lock来声明临界区。lock声明后面的代码,不管是以行还是一块代码,在同一时间最多只能有一个进程执行。
9.线程的优先级具有不可靠性,就是说不能用优先级来控制线程的执行顺序。
10.后台线程
①.什么是后台线程?比起应用程序的主图形用户界面(GUI)线程来说,这些线程以较低的优先权在不同的过程中
运行着。对于不能立即执行结束, 又不想一直等待的任务,后台线程能很好的胜任。在C#中,把线程对象的
IsBackground属性设为true,该线程即为后台线程。
后台线程跟前台线程只有一个区别,那就是后台线程不妨碍程序的终止。一旦一个进程所有的前台线程都终止后,
CLR将通过调用任意一个存活中的后台进程的Abort()方法来彻底终止进程。
注意:后台线程不能直接操作所在进程之外的数据引用。
②.怎样与后台线程通讯?运用MethodInvoker委派实体。
要使用MethodInvoker委派,需要三个条件:
a.一个创建委派的后台线程
Thread thread=new Thread(new ThreadStart(Run));
thread.IsBackground=true;//把Thread设为后台线程
thread.Start();
b.一个用作后台线程与前台可视化单元的接口的类级方法
public void Run()
{
int count=0;
try
{
MethodInvoker mi=new MethodInvoker(this.UpdateLabel);
//创建一个委托,UpdateLabel是该委托所托管的代码,必须是声明为void 且不接受任何参数的任何方法。
while(true)
{
count++;
//this.Invoke(mi);//同步执行委托
this.BeginInvoke(mi);//异步执行委托
Thread.Sleep(500);
}
}
catch(ThreadInterruptedException e)
{
Console.WriteLine("Interruption Exception in Thread:{0}",e);
}
catch(Exception ex)
{
Console.WriteLine("Exception in Thread:{0}",ex);
}
}
c.一个应用程序中可以更新的可视化单元
public void UpdateLabel()
{
label1.Text=count.ToString();
}
例1:使用多线程实现的打字练习(VS2005)
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.Threading;
/*
* 编写一个基于Windows窗体的应用程序,实现打字练习功能,要使用多线程,在主线程里用
* Timer控件定时产生Label控件,每个Label控件显示一个随机产生的字母,每产生一个Label
* 就新开一个线程(辅助线程),这个线程用于控制Label控件的向下移动,主线程监视键盘
* 输入,如果键盘输入文本与这个Label控件的Text相同,Label就消失
*/
namespace TypewriteExcercise
{
public partial class frmTyped : Form
{
public frmTyped()
{
InitializeComponent();
}
private void frmTyped_Load(object sender, EventArgs e)
{
timer1.Start();//启动时钟
}
private void timer1_Tick(object sender, EventArgs e)
{
Label label = new Label();//创建标签对象
label.Width = 12;
label.Height = 12;
label.ForeColor = Color.White;//把标签的前景色设为白色
label.BackColor = this.BackColor;//把窗体的背景色设置黑色为标签的背景色
this.Controls.Add(label);//把标签添加到窗体中
System.Random random = new Random(DateTime.Now.Second * DateTime.Now.Second);
//当前系统时间的秒数的平方作为随机种子
label.Left = random.Next(this.Width);//标签随机数[0,窗体的宽度),
Letter letter = new Letter(label, this);//把标签和窗体传递给letter对象
ThreadStart threadstart = new ThreadStart(letter.Run);//创建线程启动委托,注意括号里面是方法名
Thread thread = new Thread(threadstart);//创建线程实例
thread.Start();//启动线程
}
private void frmTyped_KeyPress(object sender, KeyPressEventArgs e)
{
foreach(Label label in this.Controls)
{
if(label==null)//没有标签
{
break;//退出
}
if(label.Text[0]==e.KeyChar)
//label.Text返回的是字符串,label.Text[0]返回第一个字符。e.KeyChar返回的是字符。这样都为字符才能判断
{
label.Dispose();//释放由label所使用的所有资源
this.Controls.Remove(label);//从frmTyped中移除label
}
}
}
}
public class Letter
{
private Label _label;
private Form _container;
private int _speed = 2;
public Letter(Label label, Form container) //构造函数接受标签和窗体
{
_label = label;//初始化标签
_container = container;//初始化窗体
Random random = new Random(DateTime.Now.Second);//使用当前系统时间的秒数作为随机种子
_speed = random.Next(5) + 1;//产生一个[1,6)的随机数用来表示速度
_label.Text = Convert.ToChar(65 + random.Next(57)).ToString();
/*
* a-z的ASCII码97-122 A-Z 的ASCII码65-90 [:91 \:92 ]:93 ^:94 _:95 `:96
*/
switch(_speed)
{
case 1:
_label.ForeColor = Color.Red;//将标签的前景色设为红色
break;
case 2:
_label.ForeColor = Color.Yellow;
break;
case 3:
_label.ForeColor = Color.Blue;
break;
case 4:
_label.ForeColor = Color.Green;
break;
case 5:
_label.ForeColor = Color.White;
break;
default:
_label.ForeColor = Color.White;
break;
}
}
public void Run()
{
try
{
while (_label.Top <= this._container.Height + 100)//标签的的上边距小于或等于窗体的高度+100像素
{
if (_label == null)//没有产生标签这种情况
{
Thread.CurrentThread.Abort();//就终止当前的线程
}
_label.Top += 1;//有标签,上边距就加1像素;
Thread.Sleep(_speed * 5);//让线程休眠(速度越快,休眠的时间越短。以毫秒为单位);
}
if(Thread.CurrentThread.IsAlive)//如果当前线程还是存活的
{
Thread.CurrentThread.Abort();//就终止当前的线程
}
}
catch (Exception ex)
{
Console.WriteLine("错误:" + ex.Message);//获取描述当前异常的消息
Console.WriteLine("错误:" + ex.StackTrace);//获取当前异常发生时调用堆栈上的帧的字符串表达形式
}
finally //释放资源,不管是否发生异常,finally都要五条件的执行
{
if(!_label.Disposing)//如果标签没有释放到进程中
{
_label.Dispose();//释放由标签使用的所有资源
}
_container.Controls.Remove(_label);//移除在窗体中产生的所有标签
}
}
}
}