什么是应用程序域?
应用程序域可以看成是程序集的“容器”,应用程序域可以被主动创建,可以被卸载,并且很快被GC(垃圾回收器)回收。
在.net平台托管代码运行时,CLR(公共语言运行库)还会额外的创建“系统域”和“共享域”,存放应用程序需要的资源,这样就能减少 进程的总数,提高系统性能,减轻进度调度的压力;
#region 应用程序域
private void button8_Click(object sender, EventArgs e)
{
int countq = System.Diagnostics.Process.GetProcesses().Length;
Console.WriteLine("【加载应用程序域之前】进程总数"+countq);
//创建应用程序域
var newDomain = AppDomain.CreateDomain("newAppDomain");
//加载程序集
newDomain.ExecuteAssembly("D:\\NZP\\NZP_WindowsForms\\NZP_WindowsForms\\bin\\Debug\\NZP_WindowsForms.exe");
Console.WriteLine("【加载应用程序域之后】进程总数" + countq);
//卸载应用程序域
AppDomain.Unload(newAppDomain);
//效果:
//打开两个应用,只开启一个进程。
}
#endregion
线程状态
-
挂起(Suspend):
-
亦称“暂停”。挂起是用户主动发起的行为,所以可以恢复;线程被挂起的时候,CPU资源不被释放;如果任务优先级高,其他任务则靠边站。
-
阻塞(Blocked):
-
是一种被动的行为,但是任务会释放CPU,其他任务可以运行;阻塞一般是在等待某些资源或信号量的时候出现,所以不确定什么时候恢复。
-
休眠(Sleep):
- 方法没有释放锁,线程调用的时候,CPU资源一直占有(占着茅坑不拉屎)
-
等待(Wait):
- 方法会释放锁,其他线程可以使用资源。
Sleep()和Wait()都可以让程序等待多少毫秒,如:
sleep(2000)表示:占用CPU。程序休眠2秒。
Wait(2000)表示:不占用CPU。程序等待2秒。
线程开销
线程在内存空间上的的开销
【1】Thread=内核数据:将基于时间片把当前线程中的资源随时保存至Contex(存放CPU寄存器相关的变量),然后线程休眠,再次开启其他线程的调度。
【2】Thread环境块:
- 主线程
- 终结器(也就是GC用来回收资源)
- 应用程序线程
【3】用户堆栈模式:用户程序的“局部变量”和“参数传递”所使用的堆栈。
经常见到StackOverFlowException,内存溢出的基本原因其实就是“堆栈溢出”。
【4】内核堆栈模式:在CLR(公共语言运行库)的线程操作的时候,通常最后会调用操作系统(底层win32函数),用户模式中的参数需要传递到内核模式中
线程在时间上的开销
【1】资源使用通知的开销
我们在运行一个程序,通常会加载很多的托管的、非托管DLL、exe、资源、元数据
【2】时间片切换开销
程序的线程数,会远远超过系统的线程数量,即使很多线程在休眠状态;在这样情况下,时间片需要在不同的线程中频繁切换,从而导致时间耗费。
单线程示例
#region ① 单线程做菜
/// <summary>
/// ① 单线程做菜:执行任务时,什么操作都动不了.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void SingleThread_Click(object sender, EventArgs e)
{
Thread.Sleep(3000);
MessageBox.Show("素菜做好了","友情提示");
Thread.Sleep(3000);
MessageBox.Show("荤菜做好了", "友情提示");
}
#endregion
单线程_效果
做菜(先做完素菜,再做荤菜)时,当前窗口无法操作(移动、拉伸等)。
多线程概述
字面理解:多个线程同时工作的过程。
句柄:可以理解为程序里面的所有对象资源的一个别名,类似于指针;比如一个窗口它就是一个句柄,窗体里面的控件也是一个句柄。
为什么要用多线程?
1 提升CPU利用率:每个CPU在每个时间片内只能执行一个线程,而多线程技术可以使多个任务并发执行,充分利用系统资源。
2 改善程序架构:复杂程序可以分解多个线程,而这些线程可以独立或半独立地运行,使得程序更易于理解和修改。
3 提高应用程序响应:对于图形界面的程序,可以将耗时的操作放在后台线程中处理,程序界面仍然可以保持响应
切记:线程越多,上下文切换的开销也越大;线程间同步容易出错,且不易于调试。
多线程如何实现?
1 基于委托异步实现
- 异步编程是建立在委托的基础上实现的多线程方法。
- 异步调用的每个方法都是独立的线程中进行的,因此异步编程就是多线程一种,可以说是一种简化的多线程。
- 比较适合在后台耗费时间较长,但是任务简单的情况,并且任务之间是独立的。
- 如果后台要求访问共享资源,并且要按照某些顺序执行,则异步不适合;应该采用多线程去完成。
private static Random random = new Random();//随机数实例
//定义一个任务,基于委托实现求一个数的平方
private Func<int, int> Operation = (num) =>
{
Thread.Sleep(random.Next(5) * 1000);
return num * num;
};
/// <summary>
从这里开始看程序------同时执行多个任务
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void button3_Click(object sender, EventArgs e)
{
for (int i = 1; i < 11; i++)
{
//Operation(1);//同步调用
//异步调用
//i * 10:委托输入参数
//(result):表示回调函数AsyncCallBack;就是异步执行完后,自动调用的方法。
//i :给回调函数字段AsyncState赋值,如果参数很多则将参数定义成结构或者对象
//返回值:IAsyncResult-异步操作状态接口,封装了异步中执行的参数
Operation.BeginInvoke(i*10,AutoCallBack,i);
//简写版
//Operation.BeginInvoke(i * 10,
//(result) =>
//{
// //EndInvoke 通过IAsyncResult接口对象,在任务执行中,不断的去监测异步调用是否结束
// Console.WriteLine($"第{result.AsyncState}个计算结果:{Operation.EndInvoke(result)}");
//}, i);
}
}
/// <summary>
/// 回调函数
/// </summary>
/// <param name="result"></param>
private void AutoCallBack(IAsyncResult result)
{
//EndInvoke 通过IAsyncResult接口对象,在任务执行中,不断的去监测异步调用是否结束
Console.WriteLine($"第{result.AsyncState}个计算结果:{Operation.EndInvoke(result)}");
}
显示结果:
//第2个计算结果:400
//第10个计算结果:10000
//第3个计算结果:900
//第5个计算结果:2500
//第8个计算结果:6400
//第4个计算结果:1600
//第6个计算结果:3600
//第7个计算结果:4900
//第1个计算结果:100
//第9个计算结果:8100
2 Thread
只要我们需要异步任务,都会开启一个Thread,执行完后不会立即被GC回收,而是从Thread.InternalFinalize()进入终结器,按照CLR的机制进入终结器后面还有可能被激活,如果没有被激活则下次会被GC回收,所以必然存在时间和空间开销。
Thread类:每一个Thread对象都代表这是一个托管线程,每个托管线程都对应这一个函数,属于“逻辑线程”。
ProcessThread类:用于操作系统中真实的本地线程。
关于前台线程和后台线程:
- 一个程序如果有前台线程,必须在所有的前台线程都结束后,才能退出。
- 一个程序如果开启的都是后天线程,则程序关闭的时候,后台线程也就自动全部退出。
#region ② Thread做菜
/// <summary>
/// ② 多线程做菜:执行任务时,可以对其他功能操作
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void multiThreading_Click(object sender, EventArgs e)
{
Thread thread = new Thread(() =>
{
Thread.Sleep(3000);
MessageBox.Show("素菜做好了", "友情提示");
Thread.Sleep(5000);
MessageBox.Show("荤菜做好了", "友情提示");
});
thread.IsBackground = true ; //设置为后台线程,(通常要这样设置)
thread.Start();
}
#endregion
Thread_效果:
做菜(先做完素菜,再做荤菜)时,当前窗口进可以行操作(移动、拉伸等)。
Thread实例方法_开启、暂停、恢复、终止、中断、等待
#region Thread版本的线程生命周期控制
private Thread childThread;
private int i;
/// <summary>
/// 开启
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void button9_Click(object sender, EventArgs e)
{
childThread = new Thread(() =>
{
try
{
for (i = 1; i <= 100; i++)
{
Thread.Sleep(500);
progressBar1.Invoke(new Action(() =>
{
progressBar1.Value = i;//将异步任务进度百分比赋值给进度条
}));
label2.Invoke(new Action(() =>
{
label2.Text = (i.ToString() + "%");//将异步任务进度百分比赋值给标签组件显示进度数值
}));
}
}
catch (Exception ex)
{
MessageBox.Show($"错误信息:{ex.Message} 异常发生的数字位置:{i}");
}
});
childThread.Start();
}
/// <summary>
/// 暂停 亦称“挂起”
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void button10_Click(object sender, EventArgs e)
{
//正在运行的线程或者休眠的线程,都可以暂停;已经暂停的,不用暂停了。
if (childThread.ThreadState == ThreadState.Running || childThread.ThreadState == ThreadState.WaitSleepJoin)
{
childThread.Suspend();
}
}
/// <summary>
/// 继续 即在暂停状态下进行恢复
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void button11_Click(object sender, EventArgs e)
{
if (childThread.ThreadState == ThreadState.Suspended)
{
childThread.Resume();
}
}
/// <summary>
/// 终止 亦称“取消”
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void button12_Click(object sender, EventArgs e)
{
childThread.Abort();
}
/// <summary>
/// 中断 会抛出异常,提示线程中断
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void button13_Click(object sender, EventArgs e)
{
childThread.Interrupt();
}
/// <summary>
/// Join 会等待子线程执行完后,再执行下面的主线程的内容
/// 不使用则是主线程不会等待子线程执行
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void button14_Click(object sender, EventArgs e)
{
Thread thread = new Thread(() =>
{
Thread.Sleep(5000);
Console.WriteLine("当前线程是子线程。。。。。。");
});
thread.Start();
thread.Join();
Console.WriteLine("当前线程是主线程。。。。。。");
//当前线程是子线程。。。。。。
// 线程 '[线程已销毁]'(14124) 已退出,返回值为 0(0x0)。
//当前线程是主线程。。。。。。
}
#endregion
Thread静态方法_观察Debug和Release两种情况性能差别
运行时间差别
private void button17_Click(object sender, EventArgs e)
{
List<long> dataList = new List<long>();
Stopwatch stopwatch = new Stopwatch();//监测运行时间对象
Random random = new Random();
//随机生成10000个10000以内的数据
for (int i = 0; i < 10000; i++)
{
long num= random.Next(10000);
dataList.Add(num);
Console.Write(num+" ");
}
Console.WriteLine(" ");
Thread.Sleep(1000);
//10次冒泡排序
for (int i = 0;i < 10; i++)
{
stopwatch.Start();
List<long> data = BubbleSolt(dataList);
stopwatch.Stop();
Console.WriteLine($"第{i}次:\t{stopwatch.ElapsedMilliseconds}") ;
}
}
/// <summary>
/// 冒泡排序
/// </summary>
/// <param name="dataList"></param>
/// <returns></returns>
static List<long> BubbleSolt(List<long> dataList)
{
long tempData;//临时数据
for (int a = 0;a<dataList.Count-1;a++)
{
for (int b = dataList.Count-1;b>a;b--)
{
if (dataList[b-1] > dataList[b])//如果前面一个数大于后面一个数则交换
{
tempData = dataList[b-1];
dataList[b-1] = dataList[b];
dataList[b] = tempData;
}
}
}
return dataList;
}
Debug效果:
第0次: 453
第1次: 599
第2次: 752
第3次: 904
第4次: 1055
第5次: 1208
第6次: 1362
第7次: 1516
第8次: 1664
第9次: 1817
Release效果:
第0次: 204
第1次: 268
第2次: 332
第3次: 397
第4次: 454
第5次: 515
第6次: 588
第7次: 646
第8次: 709
第9次: 776
结论:使用debug和release大概相差2-3倍,因为Release做了性能优化
赋值/取值差别
private void button17_Click(object sender, EventArgs e)
{
bool stop1 = false;//主线程定义的一个变量
int stop2 = 0;//主线程定义的一个变量
int a1 = 0;
int a2 = 0;
Thread thread = new Thread(() =>
{
//=============================方法1====================================
while (!stop1)//在子线程中访问主线的变量
{
//在这个方法之前的内存写入都要及时从CPU的cache中更新到memory中,然后从memory中读取,而不是cpu cache
Thread.MemoryBarrier();//同步内存访问
a1++;
}
//=============================方法2====================================
while (stop2==0)//在子线程中访问主线的变量
{
Thread.VolatileRead(ref stop2);//在memory中获取最新值
a2++;
}
});
thread.Start();
thread.IsBackground = false;
Thread.Sleep(1000);
//stop1 = true; 方法1
stop2 = 1; //方法2
thread.Join();
Console.WriteLine("主线程执行完毕-a1:"+ a1);
Console.WriteLine("主线程执行完毕-a2:"+ a2);
}
//效果:
//主线程执行完毕-a1:90702182
//主线程执行完毕-