1. 多线程
(1) 进程(Process):是WIndows系统中的一个基本概念,它包含着一个运行程序所需要的资源,进程之间是相对独立的,一个进程无法直接访问另一个进程的数据(除非利用分布式计算方法),一个进程运行的失败也不会影响其他进程的运行,windows系统就是利用进程把工作划分为多个独立的区域,进程可以理解为一个程序的基本边界。
1)要解 决的问题:为了使程序能够并发执行(要并发处理就要隔离进程,使进程独立,即每个进程有属于自己的数据段,程序段,进程控制块)
2)进程是隔离不同应用程序的一种资源
3)进程Demo,代码如下:
static void Main(string[] args)
{
//操作进程:GetCurrentProcess读取当前进程
Console.WriteLine(Process.GetCurrentProcess());
//获取操作系统中的所有进程
var process = Process.GetProcesses();
foreach (var item in process)
{
//输出所有操作系统的名称
Console.WriteLine(item.ProcessName);
}
//开启一个应用程序的进程
Process.Start("iexplore.exe", "http://www.cnblogs.com/hanyinglong");
var p = Process.Start("notepad.exe");
//杀掉一个线程
Thread.Sleep(3000);
p.Kill();
}
(2) 线程:线程就是一个代码段的执行流
1)是Windows任务调度的最小单位,线程是程序中的一个执行流,每个线程都有自己的专有寄存器(栈指针,程序计数器等),但代码区是共享的,即不同的线程可以执行同样的函数。
2)解决问题:进程是一个资源的拥有者,因为在进程的创建,撤销和切换的过程中,系统必须为自付出比较大的开销,限制了并发程序的进一步提高
3)一个进程可以有多个线程
4)CPU的核心切换的是线程
5)操作系统:一个CPU同一时间只能执行一个线程,多核,
6)操作系统切换线程的时候需要将线程的资源,状态都要保存起来。
7)线程创建的时候需要线程的控制块(1M),线程栈,线程寄存器。
(3) 托管代码和非托管代码的区别
(4) 线程Demo,创建一个控制台应用程序,代码如下:
namespace ThreadDemo { //当前这个程序的代码是执行在主线程上面的 class Program { static void Main(string[] args) { //打印出当前主线程的信息 Thread currentThread= Thread.CurrentThread; //打印出主线程的ID,ManagedThreadId是托管线程的一个唯一编号 Console.WriteLine("当前默认的主线程的ID是" + currentThread.ManagedThreadId); //创建一个线程,传递一个ThreadStart参数,这个参数转到定义可以看到是一个委托 //创建一个线程对象,并没有真正的分配线程 Thread threadDemo = new Thread(ThreadDemoMethod); //只有调用此方法的时候,才是真正的告诉操作系统线程准备好,请操作系统分配线程并且执行 threadDemo.Start(); //启动线程来执行 Console.WriteLine("主线程执行结束"); Console.ReadKey(); } static void ThreadDemoMethod() { Console.WriteLine("另一个线程正在执行,线程的ID是{0}", Thread.CurrentThread.ManagedThreadId); } } }
这段代码的执行结果是:
当前默认的主线程的ID是1
主线程执行结束
另一个线程正在执行,线程的ID是3
(5) 应用程序域
1)它提供安全而通用的处理单元,公共语言运行库(CLR)可以使用它来提供应用程序之间的隔离。您可以在具有同等隔离级别(存在于单独的进程中)的单个进程中运行几个应用程序域,而不会造成进程间调用或者进程间切换等方面的额外开销,在一个进程内运行多个应用程序的能力显著增强了服务器的可伸缩性
2)应用程序域里面有内存分配的堆,也提供了一种代码安全的边界(两个应用程序域之间的代码是相互隔离的),提供了异常的处理
3)一个应用程序域可以跑多个线程
4)一个线程同一时间只能运行在一个应用程序域中,但是一个应用程序域可以同时拥有多个线程
5)应用程序域Demo
static void Main(string[] args)
{
//打印当前应用程序域
Console.WriteLine(AppDomain.CurrentDomain.FriendlyName);
//创建应用程序域
AppDomain domainDemo = AppDomain.CreateDomain("韩迎龙");
//让当前应用程序域启动执行一个exe程序(可以包含执行一个exe,但是可以有多个程序集dll)
//第一步:将其他项目中的exe文件和pdf文件拷贝一份放到我们这个项目下面
//第二步:将上面拷贝进来的exe文件右键属性,修改复制到输出目录为始终复制
domainDemo.ExecuteAssembly("摇奖机.exe");
}
6)执行结果,如图所示:
(6) 简单线程详细说明
1)线程的调度是由操作系统来操作的,我们的操作只是建议
2)threadDemo.IsBackground = true;
-> 设置当前线程的一个类型,默认是前台线程
-> true代表设置一个后台线程,当前台线程都关闭之后,当前进程就直接关闭了
-> false代表设置一个前台线程,
3)线程设置Demo
class Program { static void Main(string[] args) { Thread threadDemo = new Thread(DemoThreadMethod); //设置当前线程的优先级,只是建议操作系统,给我当前的这个线程,优先级高点 threadDemo.Priority = ThreadPriority.Highest; //如果设置成前台线程,那么就必须当前线程执行完毕之后,整个进程才会推出 threadDemo.IsBackground = true; threadDemo.Start(); Thread.Sleep(3000); //关闭线程 //threadDemo.Abort(); //将当前执行此代码的线程阻塞,等待线程执行完毕 threadDemo.Join(1000); //给线程起一个名字 threadDemo.Name = "线程"; Console.WriteLine("主线程执行完毕"); } static void DemoThreadMethod() { while (true) { Console.WriteLine(DateTime.Now.ToString()); } } }
(7) 带参数的线程说明
1)ParameterizedThreadStart执行方法
->查看Reflect: public delegate void ParameterizedThreadStart(object obj);
2)线程的委托都是没有返回值的,因为我们不知道线程什么时候结束
3)带参数的线程Demo
class Program
{
static void Main(string[] args)
{
//启动一个带参数的线程
Thread threadDemo = new Thread(DemoParamets);
//给线程指定的方法传递参数
//可以传递数组
threadDemo.Start("韩迎龙");
Thread.Sleep(2000);
Console.ReadKey();
}
static void DemoParamets(object data)
{
Console.WriteLine(data.ToString());
}
}
4)将上面这段代码在Reflect中查看代码:如图所示:
2. 线程中如何访问控件
(1) 新建一个Winform应用程序,起名为:MultiThread,给这个新建的WinForm窗体添加一个文本框控件和一个Button按钮
(2)Demo代码
public partial class Form1 : Form { public Form1() { InitializeComponent(); //如果不写的话会报这个错误:线程间操作无效: 从不是创建控件“txtMSg”的线程访问它。 //还有一种方式解决将在下面说到 Control.CheckForIllegalCrossThreadCalls = false; } private void btnThread_Click(object sender, EventArgs e) { //这是一种写法 //new Thread(() => //{ // txtMSg.Text = DateTime.Now.ToString(); //}).Start(); //第二种说法 Thread thread = new Thread(ChangeTxt); thread.Start(); } public void ChangeTxt() { txtMSg.Text = DateTime.Now.ToString(); } }
3. 使用委托实现两个界面之间数据的交互案例
(1) 新建一个Winform程序,将Form1窗体作为主窗体,在新建一个窗体作为子窗体,给父窗体添加一个Button和一个TextBox控件。给子窗体中添加一个Button控件和一个TextBox控件,项目样式在后面有截图:
(2)主窗体中的代码如下: public partial class Form1 : Form { public Form1() { InitializeComponent(); //如果不写的话会报这个错误:线程间操作无效: 从不是创建控件“txtMSg”的线程访问它。 //还有一种方式解决将在下面说到 Control.CheckForIllegalCrossThreadCalls = false; } private void btnThread_Click(object sender, EventArgs e) { while (true) { Thread thread = new Thread(ChangeTxt); thread.Start(); } } public void ChangeTxt() { txtMSg.Text = DateTime.Now.ToString(); } private void btnStart_Click(object sender, EventArgs e) { //使用委托实现 FrmChild frm = new FrmChild(); //把当前主窗体的应用添加到this中 frm.ParentFrm = this; frm.AfterTxtChange = new SetTextDel(SetText); frm.Show(); } //添加一个方法给主窗体的控件赋值 public void SetText(string str) { txtMSg.Text = str; } } (3)子窗体中的代码如下: //使用委托实现这个功能 public delegate void SetTextDel(string str); public partial class FrmChild : Form { //定义了一个父窗体的变量 public Form1 ParentFrm; //定义一个委托类的实例 public SetTextDel AfterTxtChange; public FrmChild() { InitializeComponent(); } private void btnParent_Click(object sender, EventArgs e) { string childtxt = txtChildMsg.Text; //第一种方法 ////将值同步到主窗体上面 //this.ParentFrm.SetText(childtxt); //第二种方法 AfterTxtChange(childtxt); } }
(4)实现结果如图所示:
4. 总结
(1) 为什么要用多线程
1)让计算机"同时"做多件事情,节约能源。
2)多线程可以让一个程序"同时"处理多个事情。
3)后台运行程序,提高程序的运行效率,也不会是主界面出现无响应的情况
(2)产生一个线程的四个步骤
1)编写产生线程所要执行的方法。
2)引入System.Threading命名空间。
3)实例化Thread类,并传入一个指向线程所要运行方法的委托(这时候线程已经产生,但是还没有运行)。
4)调用Thread实例的Start方法,标记该线程可以被CPU执行了,但是具体的执行时间由CPU决定。
(3)注意"方法重入"的问题
1)所谓的方法重入,是一个有关多线程编程的概念,程序中多个线程同时运行时,就可能发生同一个方法被多个线程同时调用的情况,当这个方法中存在一些非线程安全的代码,方法重入就会导致数据不一致的情况,这是一个严重的Bug。
(4)进程和线程
1)一个进程至少有一个线程
2)同一个进程中的多个线程之间可以"并发"执行。
3)线程是程序中的一个执行流,每个线程都有自己的专有寄存器(栈指针、程序计数器等),但代码区是共享的,即不同的线程可以执行同样的函数。多线程是指程序中包含多个执行流,即在一个程序中可以同时运行多个不同的线程来执行不同的任务(代码),也就是说允许单个程序创建多个并行执行的线程来完成各自的任务。
(5)前台线程和后台线程
1)前台线程:只有所有的前台线程都关闭才能完成程序关闭。
2)后台线程:只有所有的前台线程结束,后台线程自动结束。
(6).NET中如何实现多线程?
1)线程肯定也是要执行一段代码的。所以要产生一个线程,必须先为该线程写一个方法,这个方法中的代码就是该线程运行所要执行的代码(找一个人来做这件事情)
2)线程启动时,通过委托调用该方法.(线程启动时,调用传过来的委托,委托就会执行相应的代码,实现线程执行的方法)。
(7)Thread类的一些成员
1)Start()启动线程
2)Abort()终止线程
3)Thread.Sleep(100)静态方法,可以使当前线程停止一段时间在运行
4)Name线程名称
5)Thread.CurrentThread获得当前的线程引用